Fri, 13 Dec 2013 20:01:49 +0200
- changed source file extension from .cpp to .cc
--- a/ldforge.pro Fri Dec 13 00:39:49 2013 +0200 +++ b/ldforge.pro Fri Dec 13 20:01:49 2013 +0200 @@ -15,7 +15,7 @@ MOC_DIR = ./build_shared/ RCC_DIR = ./build_shared/ UI_DIR = ./build_shared/ -SOURCES = src/*.cpp +SOURCES = src/*.cc HEADERS = src/*.h FORMS = ui/*.ui QT += opengl network
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/addObjectDialog.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,405 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QGridLayout> +#include <QCheckBox> +#include <QDialogButtonBox> +#include <QSpinBox> +#include <QLabel> +#include <QListWidget> +#include <QTreeWidget> +#include <QLineEdit> +#include <QPushButton> +#include "gui.h" +#include "addObjectDialog.h" +#include "document.h" +#include "colors.h" +#include "colorSelectDialog.h" +#include "history.h" +#include "widgets.h" +#include "misc.h" +#include "primitives.h" +#include "moc_addObjectDialog.cpp" + +// ============================================================================= +// ----------------------------------------------------------------------------- +class SubfileListItem : public QTreeWidgetItem +{ PROPERTY (public, Primitive*, PrimitiveInfo, NO_OPS, STOCK_WRITE) + + public: + SubfileListItem (QTreeWidgetItem* parent, Primitive* info) : + QTreeWidgetItem (parent), + m_PrimitiveInfo (info) {} + + SubfileListItem (QTreeWidget* parent, Primitive* info) : + QTreeWidgetItem (parent), + m_PrimitiveInfo (info) {} +}; + +// ============================================================================= +// ----------------------------------------------------------------------------- +AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) : + QDialog (parent) +{ setlocale (LC_ALL, "C"); + + int coordCount = 0; + str typeName = LDObject::typeName (type); + + switch (type) + { case LDObject::Comment: + { le_comment = new QLineEdit; + + if (obj) + le_comment->setText (static_cast<LDComment*> (obj)->text); + + le_comment->setMinimumWidth (384); + } break; + + case LDObject::Line: + { coordCount = 6; + } break; + + case LDObject::Triangle: + { coordCount = 9; + } break; + + case LDObject::Quad: + case LDObject::CondLine: + { coordCount = 12; + } break; + + case LDObject::Vertex: + { coordCount = 3; + } break; + + case LDObject::BFC: + { rb_bfcType = new RadioGroup ("Statement", {}, 0, Qt::Vertical); + + for (int i = 0; i < LDBFC::NumStatements; ++i) + { // Separate these in two columns + if (i == LDBFC::NumStatements / 2) + rb_bfcType->rowBreak(); + + rb_bfcType->addButton (LDBFC::statements[i]); + } + + if (obj) + rb_bfcType->setValue ( (int) static_cast<LDBFC*> (obj)->type); + } break; + + case LDObject::Subfile: + { coordCount = 3; + tw_subfileList = new QTreeWidget(); + tw_subfileList->setHeaderLabel (tr ("Primitives")); + + for (PrimitiveCategory* cat : g_PrimitiveCategories) + { SubfileListItem* parentItem = new SubfileListItem (tw_subfileList, null); + parentItem->setText (0, cat->getName()); + QList<QTreeWidgetItem*> subfileItems; + + for (Primitive& prim : cat->prims) + { SubfileListItem* item = new SubfileListItem (parentItem, &prim); + item->setText (0, fmt ("%1 - %2", prim.name, prim.title)); + subfileItems << item; + + // If this primitive is the one the current object points to, + // select it by default + if (obj && static_cast<LDSubfile*> (obj)->getFileInfo()->getName() == prim.name) + tw_subfileList->setCurrentItem (item); + } + + tw_subfileList->addTopLevelItem (parentItem); + } + + connect (tw_subfileList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_subfileTypeChanged())); + lb_subfileName = new QLabel ("File:"); + le_subfileName = new QLineEdit; + le_subfileName->setFocus(); + + if (obj) + { LDSubfile* ref = static_cast<LDSubfile*> (obj); + le_subfileName->setText (ref->getFileInfo()->getName()); + } + } break; + + default: + { critical (fmt ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName)); + } return; + } + + QPixmap icon = getIcon (fmt ("add-%1", typeName)); + LDObject* defaults = LDObject::getDefault (type); + + lb_typeIcon = new QLabel; + lb_typeIcon->setPixmap (icon); + + // Show a color edit dialog for the types that actually use the color + if (defaults->isColored()) + { if (obj != null) + colnum = obj->getColor(); + else + colnum = (type == LDObject::CondLine || type == LDObject::Line) ? edgecolor : maincolor; + + pb_color = new QPushButton; + setButtonBackground (pb_color, colnum); + connect (pb_color, SIGNAL (clicked()), this, SLOT (slot_colorButtonClicked())); + } + + for (int i = 0; i < coordCount; ++i) + { dsb_coords[i] = new QDoubleSpinBox; + dsb_coords[i]->setDecimals (5); + dsb_coords[i]->setMinimum (-10000.0); + dsb_coords[i]->setMaximum (10000.0); + } + + QGridLayout* const layout = new QGridLayout; + layout->addWidget (lb_typeIcon, 0, 0); + + switch (type) + { case LDObject::Line: + case LDObject::CondLine: + case LDObject::Triangle: + case LDObject::Quad: + + // Apply coordinates + if (obj) + { for (int i = 0; i < coordCount / 3; ++i) + for (int j = 0; j < 3; ++j) + dsb_coords[ (i * 3) + j]->setValue (obj->getVertex (i).coord (j)); + } + + break; + + case LDObject::Comment: + layout->addWidget (le_comment, 0, 1); + break; + + case LDObject::BFC: + layout->addWidget (rb_bfcType, 0, 1); + break; + + case LDObject::Subfile: + layout->addWidget (tw_subfileList, 1, 1, 1, 2); + layout->addWidget (lb_subfileName, 2, 1); + layout->addWidget (le_subfileName, 2, 2); + break; + + default: + break; + } + + if (defaults->hasMatrix()) + { LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj); + + QLabel* lb_matrix = new QLabel ("Matrix:"); + le_matrix = new QLineEdit; + // le_matrix->setValidator (new QDoubleValidator); + matrix defaultMatrix = g_identity; + + if (mo) + { for_axes (ax) + dsb_coords[ax]->setValue (mo->getPosition()[ax]); + + defaultMatrix = mo->getTransform(); + } + + le_matrix->setText (defaultMatrix.stringRep()); + layout->addWidget (lb_matrix, 4, 1); + layout->addWidget (le_matrix, 4, 2, 1, 3); + } + + if (defaults->isColored()) + layout->addWidget (pb_color, 1, 0); + + if (coordCount > 0) + { QGridLayout* const qCoordLayout = new QGridLayout; + + for (int i = 0; i < coordCount; ++i) + qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3)); + + layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3); + } + + QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + QWidget::connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept())); + QWidget::connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); + layout->addWidget (bbx_buttons, 5, 0, 1, 4); + setLayout (layout); + setWindowTitle (fmt (tr ("Edit %1"), typeName)); + + setWindowIcon (icon); + delete defaults; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddObjectDialog::setButtonBackground (QPushButton* button, int colnum) +{ LDColor* col = getColor (colnum); + + button->setIcon (getIcon ("palette")); + button->setAutoFillBackground (true); + + if (col) + button->setStyleSheet (fmt ("background-color: %1", col->hexcode)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str AddObjectDialog::currentSubfileName() +{ SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem()); + + if (item->getPrimitiveInfo() == null) + return ""; // selected a heading + + return item->getPrimitiveInfo()->name; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddObjectDialog::slot_colorButtonClicked() +{ ColorSelector::selectColor (colnum, colnum, this); + setButtonBackground (pb_color, colnum); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddObjectDialog::slot_subfileTypeChanged() +{ str name = currentSubfileName(); + + if (name.length() > 0) + le_subfileName->setText (name); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +template<class T> static T* initObj (LDObject*& obj) +{ if (obj == null) + obj = new T; + + return static_cast<T*> (obj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddObjectDialog::staticDialog (const LDObject::Type type, LDObject* obj) +{ setlocale (LC_ALL, "C"); + + // FIXME: Redirect to Edit Raw + if (obj && obj->getType() == LDObject::Error) + return; + + if (type == LDObject::Empty) + return; // Nothing to edit with empties + + const bool newObject = (obj == null); + matrix transform = g_identity; + AddObjectDialog dlg (type, obj); + + assert (!obj || obj->getType() == type); + + if (dlg.exec() == false) + return; + + if (type == LDObject::Subfile) + { QStringList matrixstrvals = dlg.le_matrix->text().split (" ", QString::SkipEmptyParts); + + if (matrixstrvals.size() == 9) + { double matrixvals[9]; + int i = 0; + + for (str val : matrixstrvals) + matrixvals[i++] = val.toFloat(); + + transform = matrix (matrixvals); + } + } + + switch (type) + { case LDObject::Comment: + { LDComment* comm = initObj<LDComment> (obj); + comm->text = dlg.le_comment->text(); + } + break; + + case LDObject::Line: + case LDObject::Triangle: + case LDObject::Quad: + case LDObject::CondLine: + { if (!obj) + obj = LDObject::getDefault (type); + + for (int i = 0; i < obj->vertices(); ++i) + { vertex v; + + for_axes (ax) + v[ax] = dlg.dsb_coords[ (i * 3) + ax]->value(); + + obj->setVertex (i, v); + } + } break; + + case LDObject::BFC: + { LDBFC* bfc = initObj<LDBFC> (obj); + bfc->type = (LDBFC::Type) dlg.rb_bfcType->value(); + } break; + + case LDObject::Vertex: + { LDVertex* vert = initObj<LDVertex> (obj); + + for_axes (ax) + vert->pos[ax] = dlg.dsb_coords[ax]->value(); + } + break; + + case LDObject::Subfile: + { str name = dlg.le_subfileName->text(); + + if (name.length() == 0) + return; // no subfile filename + + LDDocument* file = getDocument (name); + + if (!file) + { critical (fmt ("Couldn't open `%1': %2", name, strerror (errno))); + return; + } + + LDSubfile* ref = initObj<LDSubfile> (obj); + assert (ref); + + for_axes (ax) + ref->setCoordinate (ax, dlg.dsb_coords[ax]->value()); + + ref->setTransform (transform); + ref->setFileInfo (file); + } break; + + default: + break; + } + + if (obj->isColored()) + obj->setColor (dlg.colnum); + + if (newObject) + { int idx = g_win->getInsertionPoint(); + getCurrentDocument()->insertObj (idx, obj); + } + + g_win->doFullRefresh(); +}
--- a/src/addObjectDialog.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,405 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QGridLayout> -#include <QCheckBox> -#include <QDialogButtonBox> -#include <QSpinBox> -#include <QLabel> -#include <QListWidget> -#include <QTreeWidget> -#include <QLineEdit> -#include <QPushButton> -#include "gui.h" -#include "addObjectDialog.h" -#include "document.h" -#include "colors.h" -#include "colorSelectDialog.h" -#include "history.h" -#include "widgets.h" -#include "misc.h" -#include "primitives.h" -#include "moc_addObjectDialog.cpp" - -// ============================================================================= -// ----------------------------------------------------------------------------- -class SubfileListItem : public QTreeWidgetItem -{ PROPERTY (public, Primitive*, PrimitiveInfo, NO_OPS, STOCK_WRITE) - - public: - SubfileListItem (QTreeWidgetItem* parent, Primitive* info) : - QTreeWidgetItem (parent), - m_PrimitiveInfo (info) {} - - SubfileListItem (QTreeWidget* parent, Primitive* info) : - QTreeWidgetItem (parent), - m_PrimitiveInfo (info) {} -}; - -// ============================================================================= -// ----------------------------------------------------------------------------- -AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) : - QDialog (parent) -{ setlocale (LC_ALL, "C"); - - int coordCount = 0; - str typeName = LDObject::typeName (type); - - switch (type) - { case LDObject::Comment: - { le_comment = new QLineEdit; - - if (obj) - le_comment->setText (static_cast<LDComment*> (obj)->text); - - le_comment->setMinimumWidth (384); - } break; - - case LDObject::Line: - { coordCount = 6; - } break; - - case LDObject::Triangle: - { coordCount = 9; - } break; - - case LDObject::Quad: - case LDObject::CondLine: - { coordCount = 12; - } break; - - case LDObject::Vertex: - { coordCount = 3; - } break; - - case LDObject::BFC: - { rb_bfcType = new RadioGroup ("Statement", {}, 0, Qt::Vertical); - - for (int i = 0; i < LDBFC::NumStatements; ++i) - { // Separate these in two columns - if (i == LDBFC::NumStatements / 2) - rb_bfcType->rowBreak(); - - rb_bfcType->addButton (LDBFC::statements[i]); - } - - if (obj) - rb_bfcType->setValue ( (int) static_cast<LDBFC*> (obj)->type); - } break; - - case LDObject::Subfile: - { coordCount = 3; - tw_subfileList = new QTreeWidget(); - tw_subfileList->setHeaderLabel (tr ("Primitives")); - - for (PrimitiveCategory* cat : g_PrimitiveCategories) - { SubfileListItem* parentItem = new SubfileListItem (tw_subfileList, null); - parentItem->setText (0, cat->getName()); - QList<QTreeWidgetItem*> subfileItems; - - for (Primitive& prim : cat->prims) - { SubfileListItem* item = new SubfileListItem (parentItem, &prim); - item->setText (0, fmt ("%1 - %2", prim.name, prim.title)); - subfileItems << item; - - // If this primitive is the one the current object points to, - // select it by default - if (obj && static_cast<LDSubfile*> (obj)->getFileInfo()->getName() == prim.name) - tw_subfileList->setCurrentItem (item); - } - - tw_subfileList->addTopLevelItem (parentItem); - } - - connect (tw_subfileList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_subfileTypeChanged())); - lb_subfileName = new QLabel ("File:"); - le_subfileName = new QLineEdit; - le_subfileName->setFocus(); - - if (obj) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - le_subfileName->setText (ref->getFileInfo()->getName()); - } - } break; - - default: - { critical (fmt ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName)); - } return; - } - - QPixmap icon = getIcon (fmt ("add-%1", typeName)); - LDObject* defaults = LDObject::getDefault (type); - - lb_typeIcon = new QLabel; - lb_typeIcon->setPixmap (icon); - - // Show a color edit dialog for the types that actually use the color - if (defaults->isColored()) - { if (obj != null) - colnum = obj->getColor(); - else - colnum = (type == LDObject::CondLine || type == LDObject::Line) ? edgecolor : maincolor; - - pb_color = new QPushButton; - setButtonBackground (pb_color, colnum); - connect (pb_color, SIGNAL (clicked()), this, SLOT (slot_colorButtonClicked())); - } - - for (int i = 0; i < coordCount; ++i) - { dsb_coords[i] = new QDoubleSpinBox; - dsb_coords[i]->setDecimals (5); - dsb_coords[i]->setMinimum (-10000.0); - dsb_coords[i]->setMaximum (10000.0); - } - - QGridLayout* const layout = new QGridLayout; - layout->addWidget (lb_typeIcon, 0, 0); - - switch (type) - { case LDObject::Line: - case LDObject::CondLine: - case LDObject::Triangle: - case LDObject::Quad: - - // Apply coordinates - if (obj) - { for (int i = 0; i < coordCount / 3; ++i) - for (int j = 0; j < 3; ++j) - dsb_coords[ (i * 3) + j]->setValue (obj->getVertex (i).coord (j)); - } - - break; - - case LDObject::Comment: - layout->addWidget (le_comment, 0, 1); - break; - - case LDObject::BFC: - layout->addWidget (rb_bfcType, 0, 1); - break; - - case LDObject::Subfile: - layout->addWidget (tw_subfileList, 1, 1, 1, 2); - layout->addWidget (lb_subfileName, 2, 1); - layout->addWidget (le_subfileName, 2, 2); - break; - - default: - break; - } - - if (defaults->hasMatrix()) - { LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj); - - QLabel* lb_matrix = new QLabel ("Matrix:"); - le_matrix = new QLineEdit; - // le_matrix->setValidator (new QDoubleValidator); - matrix defaultMatrix = g_identity; - - if (mo) - { for (const Axis ax : g_Axes) - dsb_coords[ax]->setValue (mo->getPosition()[ax]); - - defaultMatrix = mo->getTransform(); - } - - le_matrix->setText (defaultMatrix.stringRep()); - layout->addWidget (lb_matrix, 4, 1); - layout->addWidget (le_matrix, 4, 2, 1, 3); - } - - if (defaults->isColored()) - layout->addWidget (pb_color, 1, 0); - - if (coordCount > 0) - { QGridLayout* const qCoordLayout = new QGridLayout; - - for (int i = 0; i < coordCount; ++i) - qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3)); - - layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3); - } - - QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - QWidget::connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept())); - QWidget::connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); - layout->addWidget (bbx_buttons, 5, 0, 1, 4); - setLayout (layout); - setWindowTitle (fmt (tr ("Edit %1"), typeName)); - - setWindowIcon (icon); - delete defaults; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddObjectDialog::setButtonBackground (QPushButton* button, int colnum) -{ LDColor* col = getColor (colnum); - - button->setIcon (getIcon ("palette")); - button->setAutoFillBackground (true); - - if (col) - button->setStyleSheet (fmt ("background-color: %1", col->hexcode)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str AddObjectDialog::currentSubfileName() -{ SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem()); - - if (item->getPrimitiveInfo() == null) - return ""; // selected a heading - - return item->getPrimitiveInfo()->name; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddObjectDialog::slot_colorButtonClicked() -{ ColorSelector::selectColor (colnum, colnum, this); - setButtonBackground (pb_color, colnum); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddObjectDialog::slot_subfileTypeChanged() -{ str name = currentSubfileName(); - - if (name.length() > 0) - le_subfileName->setText (name); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -template<class T> static T* initObj (LDObject*& obj) -{ if (obj == null) - obj = new T; - - return static_cast<T*> (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddObjectDialog::staticDialog (const LDObject::Type type, LDObject* obj) -{ setlocale (LC_ALL, "C"); - - // FIXME: Redirect to Edit Raw - if (obj && obj->getType() == LDObject::Error) - return; - - if (type == LDObject::Empty) - return; // Nothing to edit with empties - - const bool newObject = (obj == null); - matrix transform = g_identity; - AddObjectDialog dlg (type, obj); - - assert (!obj || obj->getType() == type); - - if (dlg.exec() == false) - return; - - if (type == LDObject::Subfile) - { QStringList matrixstrvals = dlg.le_matrix->text().split (" ", QString::SkipEmptyParts); - - if (matrixstrvals.size() == 9) - { double matrixvals[9]; - int i = 0; - - for (str val : matrixstrvals) - matrixvals[i++] = val.toFloat(); - - transform = matrix (matrixvals); - } - } - - switch (type) - { case LDObject::Comment: - { LDComment* comm = initObj<LDComment> (obj); - comm->text = dlg.le_comment->text(); - } - break; - - case LDObject::Line: - case LDObject::Triangle: - case LDObject::Quad: - case LDObject::CondLine: - { if (!obj) - obj = LDObject::getDefault (type); - - for (int i = 0; i < obj->vertices(); ++i) - { vertex v; - - for (const Axis ax : g_Axes) - v[ax] = dlg.dsb_coords[ (i * 3) + ax]->value(); - - obj->setVertex (i, v); - } - } break; - - case LDObject::BFC: - { LDBFC* bfc = initObj<LDBFC> (obj); - bfc->type = (LDBFC::Type) dlg.rb_bfcType->value(); - } break; - - case LDObject::Vertex: - { LDVertex* vert = initObj<LDVertex> (obj); - - for (const Axis ax : g_Axes) - vert->pos[ax] = dlg.dsb_coords[ax]->value(); - } - break; - - case LDObject::Subfile: - { str name = dlg.le_subfileName->text(); - - if (name.length() == 0) - return; // no subfile filename - - LDDocument* file = getDocument (name); - - if (!file) - { critical (fmt ("Couldn't open `%1': %2", name, strerror (errno))); - return; - } - - LDSubfile* ref = initObj<LDSubfile> (obj); - assert (ref); - - for (const Axis ax : g_Axes) - ref->setCoordinate (ax, dlg.dsb_coords[ax]->value()); - - ref->setTransform (transform); - ref->setFileInfo (file); - } break; - - default: - break; - } - - if (obj->isColored()) - obj->setColor (dlg.colnum); - - if (newObject) - { int idx = g_win->getInsertionPoint(); - getCurrentDocument()->insertObj (idx, obj); - } - - g_win->doFullRefresh(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colorSelectDialog.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,195 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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/>. + * ===================================================================== + * + * colorSelectDialog.cpp: Color selector box. + */ + +#include <QGraphicsScene> +#include <QGraphicsItem> +#include <QMouseEvent> +#include <QScrollBar> + +#include "main.h" +#include "gui.h" +#include "colorSelectDialog.h" +#include "colors.h" +#include "config.h" +#include "misc.h" +#include "ui_colorsel.h" +#include "moc_colorSelectDialog.cpp" + +static const int g_numColumns = 16; +static const int g_squareSize = 32; + +extern_cfg (String, gl_maincolor); +extern_cfg (Float, gl_maincolor_alpha); + +// ============================================================================= +// ----------------------------------------------------------------------------- +ColorSelector::ColorSelector (int defval, QWidget* parent) : QDialog (parent) +{ // Remove the default color if it's invalid + if (!getColor (defval)) + defval = -1; + + m_firstResize = true; + ui = new Ui_ColorSelUI; + ui->setupUi (this); + + m_scene = new QGraphicsScene; + ui->viewport->setScene (m_scene); + setSelection (getColor (defval)); + + // not really an icon but eh + m_scene->setBackgroundBrush (getIcon ("checkerboard")); + drawScene(); + + int width = viewportWidth(); + ui->viewport->setMinimumWidth (width); + ui->viewport->setMaximumWidth (width); + + drawColorInfo(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +ColorSelector::~ColorSelector() +{ delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ColorSelector::drawScene() +{ const int numCols = g_numColumns; + const int square = g_squareSize; + const int g_maxHeight = (numRows() * square); + QRect sceneRect (0, 0, viewportWidth(), g_maxHeight); + + m_scene->setSceneRect (sceneRect); + ui->viewport->setSceneRect (sceneRect); + + const double penWidth = 1.0f; + + // Draw the color rectangles. + m_scene->clear(); + + for (int i = 0; i < MAX_COLORS; ++i) + { LDColor* info = ::getColor (i); + + if (!info) + continue; + + const double x = (i % numCols) * square; + const double y = (i / numCols) * square; + const double w = square - (penWidth / 2); + + QColor col = info->faceColor; + + if (i == maincolor) + { // Use the user preferences for main color here + col = QColor (gl_maincolor); + col.setAlpha (gl_maincolor_alpha * 255.0f); + } + + QPen pen (info->edgeColor, penWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + m_scene->addRect (x, y, w, w, pen, col); + QGraphicsTextItem* numtext = m_scene->addText (fmt ("%1", i)); + numtext->setDefaultTextColor ( (luma (col) < 80) ? Qt::white : Qt::black); + numtext->setPos (x, y); + + if (getSelection() && i == getSelection()->index) + { auto curspic = m_scene->addPixmap (getIcon ("colorcursor")); + curspic->setPos (x, y); + } + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int ColorSelector::numRows() const +{ return (MAX_COLORS / g_numColumns); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int ColorSelector::viewportWidth() const +{ return g_numColumns * g_squareSize + 21; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ColorSelector::drawColorInfo() +{ if (!getSelection()) + { ui->colorLabel->setText ("---"); + return; + } + + ui->colorLabel->setText (fmt ("%1 - %2", getSelection()->index, getSelection()->name)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ColorSelector::resizeEvent (QResizeEvent* ev) +{ // If this is the first resize, check if we need to scroll down to see the + // currently selected color. We cannot do this in the constructor because the + // height is not set properly there. + if (m_firstResize) + { int visibleColors = (ui->viewport->height() / g_squareSize) * g_numColumns; + + if (getSelection() && getSelection()->index >= visibleColors) + { int y = (getSelection()->index / g_numColumns) * g_squareSize; + ui->viewport->verticalScrollBar()->setValue (y); + } + + m_firstResize = false; + } + + (void) ev; + drawScene(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ColorSelector::mousePressEvent (QMouseEvent* event) +{ QPointF scenepos = ui->viewport->mapToScene (event->pos()); + + int x = (scenepos.x() - (g_squareSize / 2)) / g_squareSize; + int y = (scenepos.y() - (g_squareSize / 2)) / g_squareSize; + int idx = (y * g_numColumns) + x; + + LDColor* col = ::getColor (idx); + + if (!col) + return; + + setSelection (col); + drawScene(); + drawColorInfo(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool ColorSelector::selectColor (int& val, int defval, QWidget* parent) +{ ColorSelector dlg (defval, parent); + + if (dlg.exec() && dlg.getSelection() != null) + { val = dlg.getSelection()->index; + return true; + } + + return false; +} \ No newline at end of file
--- a/src/colorSelectDialog.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,195 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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/>. - * ===================================================================== - * - * colorSelectDialog.cpp: Color selector box. - */ - -#include <QGraphicsScene> -#include <QGraphicsItem> -#include <QMouseEvent> -#include <QScrollBar> - -#include "main.h" -#include "gui.h" -#include "colorSelectDialog.h" -#include "colors.h" -#include "config.h" -#include "misc.h" -#include "ui_colorsel.h" -#include "moc_colorSelectDialog.cpp" - -static const int g_numColumns = 16; -static const int g_squareSize = 32; - -extern_cfg (String, gl_maincolor); -extern_cfg (Float, gl_maincolor_alpha); - -// ============================================================================= -// ----------------------------------------------------------------------------- -ColorSelector::ColorSelector (int defval, QWidget* parent) : QDialog (parent) -{ // Remove the default color if it's invalid - if (!getColor (defval)) - defval = -1; - - m_firstResize = true; - ui = new Ui_ColorSelUI; - ui->setupUi (this); - - m_scene = new QGraphicsScene; - ui->viewport->setScene (m_scene); - setSelection (getColor (defval)); - - // not really an icon but eh - m_scene->setBackgroundBrush (getIcon ("checkerboard")); - drawScene(); - - int width = viewportWidth(); - ui->viewport->setMinimumWidth (width); - ui->viewport->setMaximumWidth (width); - - drawColorInfo(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -ColorSelector::~ColorSelector() -{ delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ColorSelector::drawScene() -{ const int numCols = g_numColumns; - const int square = g_squareSize; - const int g_maxHeight = (numRows() * square); - QRect sceneRect (0, 0, viewportWidth(), g_maxHeight); - - m_scene->setSceneRect (sceneRect); - ui->viewport->setSceneRect (sceneRect); - - const double penWidth = 1.0f; - - // Draw the color rectangles. - m_scene->clear(); - - for (int i = 0; i < MAX_COLORS; ++i) - { LDColor* info = ::getColor (i); - - if (!info) - continue; - - const double x = (i % numCols) * square; - const double y = (i / numCols) * square; - const double w = square - (penWidth / 2); - - QColor col = info->faceColor; - - if (i == maincolor) - { // Use the user preferences for main color here - col = QColor (gl_maincolor); - col.setAlpha (gl_maincolor_alpha * 255.0f); - } - - QPen pen (info->edgeColor, penWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); - m_scene->addRect (x, y, w, w, pen, col); - QGraphicsTextItem* numtext = m_scene->addText (fmt ("%1", i)); - numtext->setDefaultTextColor ( (luma (col) < 80) ? Qt::white : Qt::black); - numtext->setPos (x, y); - - if (getSelection() && i == getSelection()->index) - { auto curspic = m_scene->addPixmap (getIcon ("colorcursor")); - curspic->setPos (x, y); - } - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int ColorSelector::numRows() const -{ return (MAX_COLORS / g_numColumns); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int ColorSelector::viewportWidth() const -{ return g_numColumns * g_squareSize + 21; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ColorSelector::drawColorInfo() -{ if (!getSelection()) - { ui->colorLabel->setText ("---"); - return; - } - - ui->colorLabel->setText (fmt ("%1 - %2", getSelection()->index, getSelection()->name)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ColorSelector::resizeEvent (QResizeEvent* ev) -{ // If this is the first resize, check if we need to scroll down to see the - // currently selected color. We cannot do this in the constructor because the - // height is not set properly there. - if (m_firstResize) - { int visibleColors = (ui->viewport->height() / g_squareSize) * g_numColumns; - - if (getSelection() && getSelection()->index >= visibleColors) - { int y = (getSelection()->index / g_numColumns) * g_squareSize; - ui->viewport->verticalScrollBar()->setValue (y); - } - - m_firstResize = false; - } - - (void) ev; - drawScene(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ColorSelector::mousePressEvent (QMouseEvent* event) -{ QPointF scenepos = ui->viewport->mapToScene (event->pos()); - - int x = (scenepos.x() - (g_squareSize / 2)) / g_squareSize; - int y = (scenepos.y() - (g_squareSize / 2)) / g_squareSize; - int idx = (y * g_numColumns) + x; - - LDColor* col = ::getColor (idx); - - if (!col) - return; - - setSelection (col); - drawScene(); - drawColorInfo(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool ColorSelector::selectColor (int& val, int defval, QWidget* parent) -{ ColorSelector dlg (defval, parent); - - if (dlg.exec() && dlg.getSelection() != null) - { val = dlg.getSelection()->index; - return true; - } - - return false; -} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colors.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,78 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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/>. + * ===================================================================== + * + * colors.cpp: LDraw color management. LDConfig.ldr parsing is not here! + * TODO: Make LDColor more full-fledged, add support for direct colors. + * TODO: g_LDColors should probably be a map. + */ + +#include "main.h" +#include "colors.h" +#include "document.h" +#include "misc.h" +#include "gui.h" +#include "ldconfig.h" +#include <QColor> + +static LDColor* g_LDColors[MAX_COLORS]; + +// ============================================================================= +// ----------------------------------------------------------------------------- +void initColors() +{ LDColor* col; + log ("%1: initializing color information.\n", __func__); + + // Always make sure there's 16 and 24 available. They're special like that. + col = new LDColor; + col->faceColor = col->hexcode = "#AAAAAA"; + col->edgeColor = Qt::black; + g_LDColors[maincolor] = col; + + col = new LDColor; + col->faceColor = col->edgeColor = col->hexcode = "#000000"; + g_LDColors[edgecolor] = col; + + parseLDConfig(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDColor* getColor (int colnum) +{ // Check bounds + if (colnum < 0 || colnum >= MAX_COLORS) + return null; + + return g_LDColors[colnum]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void setColor (int colnum, LDColor* col) +{ if (colnum < 0 || colnum >= MAX_COLORS) + return; + + g_LDColors[colnum] = col; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int luma (QColor& col) +{ return (0.2126f * col.red()) + + (0.7152f * col.green()) + + (0.0722f * col.blue()); +}
--- a/src/colors.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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/>. - * ===================================================================== - * - * colors.cpp: LDraw color management. LDConfig.ldr parsing is not here! - * TODO: Make LDColor more full-fledged, add support for direct colors. - * TODO: g_LDColors should probably be a map. - */ - -#include "main.h" -#include "colors.h" -#include "document.h" -#include "misc.h" -#include "gui.h" -#include "ldconfig.h" -#include <QColor> - -static LDColor* g_LDColors[MAX_COLORS]; - -// ============================================================================= -// ----------------------------------------------------------------------------- -void initColors() -{ LDColor* col; - log ("%1: initializing color information.\n", __func__); - - // Always make sure there's 16 and 24 available. They're special like that. - col = new LDColor; - col->faceColor = col->hexcode = "#AAAAAA"; - col->edgeColor = Qt::black; - g_LDColors[maincolor] = col; - - col = new LDColor; - col->faceColor = col->edgeColor = col->hexcode = "#000000"; - g_LDColors[edgecolor] = col; - - parseLDConfig(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDColor* getColor (int colnum) -{ // Check bounds - if (colnum < 0 || colnum >= MAX_COLORS) - return null; - - return g_LDColors[colnum]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void setColor (int colnum, LDColor* col) -{ if (colnum < 0 || colnum >= MAX_COLORS) - return; - - g_LDColors[colnum] = col; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int luma (QColor& col) -{ return (0.2126f * col.red()) + - (0.7152f * col.green()) + - (0.0722f * col.blue()); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/config.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,140 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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/>. + * ===================================================================== + * + * config.cpp: Configuration management. I don't like how unsafe QSettings + * is so this implements a type-safer and identifer-safer wrapping system of + * configuration variables. QSettings is used underlyingly, this is a matter + * of interface. + */ + +#include <errno.h> +#include <QDir> +#include <QTextStream> +#include <QSettings> +#include "main.h" +#include "config.h" +#include "misc.h" +#include "gui.h" +#include "document.h" + +#ifdef _WIN32 +# define EXTENSION ".ini" +#else +# define EXTENSION ".cfg" +#endif // _WIN32 + +Config* g_configPointers[MAX_CONFIG]; +static int g_cfgPointerCursor = 0; + +// ============================================================================= +// Get the QSettings object. A portable build refers to a file in the current +// directory, a non-portable build to ~/.config/LDForge or to registry. +// ----------------------------------------------------------------------------- +static QSettings* getSettingsObject() +{ +#ifdef PORTABLE + return new QSettings (str (APPNAME).toLower() + EXTENSION, QSettings::IniFormat); +#else + return new QSettings; +#endif // PORTABLE +} + +Config::Config (const char* name, const char* defstring) : + name (name), m_defstring (defstring) {} + +// ============================================================================= +// Load the configuration from file +// ----------------------------------------------------------------------------- +bool Config::load() +{ QSettings* settings = getSettingsObject(); + log ("config::load: Loading configuration file from %1\n", settings->fileName()); + + for (Config* cfg : g_configPointers) + { if (!cfg) + break; + + QVariant val = settings->value (cfg->name, cfg->defaultVariant()); + cfg->loadFromVariant (val); + } + + settings->deleteLater(); + return true; +} + +// ============================================================================= +// Save the configuration to disk +// ----------------------------------------------------------------------------- +bool Config::save() +{ QSettings* settings = getSettingsObject(); + log ("Saving configuration to %1...\n", settings->fileName()); + + for (Config* cfg : g_configPointers) + { if (!cfg) + break; + + if (cfg->isDefault()) + continue; + + settings->setValue (cfg->name, cfg->toVariant()); + } + + settings->sync(); + settings->deleteLater(); + return true; +} + +// ============================================================================= +// Reset configuration defaults. +// ----------------------------------------------------------------------------- +void Config::reset() +{ for (Config * cfg : g_configPointers) + { if (!cfg) + break; + + cfg->resetValue(); + } +} + +// ============================================================================= +// Where is the configuration file located at? Note that the Windows build uses +// the registry so only use this with PORTABLE code. +// ----------------------------------------------------------------------------- +str Config::filepath (str file) +{ return Config::dirpath() + DIRSLASH + file; +} + +// ============================================================================= +// Directory of the configuration file. PORTABLE code here as well. +// ----------------------------------------------------------------------------- +str Config::dirpath() +{ QSettings* cfg = getSettingsObject(); + return dirname (cfg->fileName()); +} + +// ============================================================================= +// We cannot just add config objects to a list or vector because that would rely +// on the vector's c-tor being called before the configs' c-tors. With global +// variables we cannot assume that!! Therefore we need to use a C-style array here. +// ----------------------------------------------------------------------------- +void Config::addToArray (Config* ptr) +{ if (g_cfgPointerCursor == 0) + memset (g_configPointers, 0, sizeof g_configPointers); + + assert (g_cfgPointerCursor < MAX_CONFIG); + g_configPointers[g_cfgPointerCursor++] = ptr; +}
--- a/src/config.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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/>. - * ===================================================================== - * - * config.cpp: Configuration management. I don't like how unsafe QSettings - * is so this implements a type-safer and identifer-safer wrapping system of - * configuration variables. QSettings is used underlyingly, this is a matter - * of interface. - */ - -#include <errno.h> -#include <QDir> -#include <QTextStream> -#include <QSettings> -#include "main.h" -#include "config.h" -#include "misc.h" -#include "gui.h" -#include "document.h" - -#ifdef _WIN32 -# define EXTENSION ".ini" -#else -# define EXTENSION ".cfg" -#endif // _WIN32 - -Config* g_configPointers[MAX_CONFIG]; -static int g_cfgPointerCursor = 0; - -// ============================================================================= -// Get the QSettings object. A portable build refers to a file in the current -// directory, a non-portable build to ~/.config/LDForge or to registry. -// ----------------------------------------------------------------------------- -static QSettings* getSettingsObject() -{ -#ifdef PORTABLE - return new QSettings (str (APPNAME).toLower() + EXTENSION, QSettings::IniFormat); -#else - return new QSettings; -#endif // PORTABLE -} - -Config::Config (const char* name, const char* defstring) : - name (name), m_defstring (defstring) {} - -// ============================================================================= -// Load the configuration from file -// ----------------------------------------------------------------------------- -bool Config::load() -{ QSettings* settings = getSettingsObject(); - log ("config::load: Loading configuration file from %1\n", settings->fileName()); - - for (Config* cfg : g_configPointers) - { if (!cfg) - break; - - QVariant val = settings->value (cfg->name, cfg->defaultVariant()); - cfg->loadFromVariant (val); - } - - settings->deleteLater(); - return true; -} - -// ============================================================================= -// Save the configuration to disk -// ----------------------------------------------------------------------------- -bool Config::save() -{ QSettings* settings = getSettingsObject(); - log ("Saving configuration to %1...\n", settings->fileName()); - - for (Config* cfg : g_configPointers) - { if (!cfg) - break; - - if (cfg->isDefault()) - continue; - - settings->setValue (cfg->name, cfg->toVariant()); - } - - settings->sync(); - settings->deleteLater(); - return true; -} - -// ============================================================================= -// Reset configuration defaults. -// ----------------------------------------------------------------------------- -void Config::reset() -{ for (Config * cfg : g_configPointers) - { if (!cfg) - break; - - cfg->resetValue(); - } -} - -// ============================================================================= -// Where is the configuration file located at? Note that the Windows build uses -// the registry so only use this with PORTABLE code. -// ----------------------------------------------------------------------------- -str Config::filepath (str file) -{ return Config::dirpath() + DIRSLASH + file; -} - -// ============================================================================= -// Directory of the configuration file. PORTABLE code here as well. -// ----------------------------------------------------------------------------- -str Config::dirpath() -{ QSettings* cfg = getSettingsObject(); - return dirname (cfg->fileName()); -} - -// ============================================================================= -// We cannot just add config objects to a list or vector because that would rely -// on the vector's c-tor being called before the configs' c-tors. With global -// variables we cannot assume that!! Therefore we need to use a C-style array here. -// ----------------------------------------------------------------------------- -void Config::addToArray (Config* ptr) -{ if (g_cfgPointerCursor == 0) - memset (g_configPointers, 0, sizeof g_configPointers); - - assert (g_cfgPointerCursor < MAX_CONFIG); - g_configPointers[g_cfgPointerCursor++] = ptr; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/configDialog.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,719 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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/>. + * ===================================================================== + * + * configDialog.cpp: Settings dialog and everything related to it. + * Actual configuration core is in config.cpp. + */ + +#include <QGridLayout> +#include <QFileDialog> +#include <QColorDialog> +#include <QBoxLayout> +#include <QKeyEvent> +#include <QGroupBox> +#include <QDoubleSpinBox> +#include <QLineEdit> +#include <QCheckBox> +#include "main.h" +#include "configDialog.h" +#include "document.h" +#include "config.h" +#include "misc.h" +#include "colors.h" +#include "colorSelectDialog.h" +#include "gldraw.h" +#include "ui_config.h" +#include "moc_configDialog.cpp" + +extern_cfg (String, gl_bgcolor); +extern_cfg (String, gl_maincolor); +extern_cfg (Bool, lv_colorize); +extern_cfg (Bool, gl_colorbfc); +extern_cfg (Float, gl_maincolor_alpha); +extern_cfg (Int, gl_linethickness); +extern_cfg (String, gui_colortoolbar); +extern_cfg (Bool, edit_schemanticinline); +extern_cfg (Bool, gl_blackedges); +extern_cfg (Bool, gl_aa); +extern_cfg (Bool, gui_implicitfiles); +extern_cfg (String, net_downloadpath); +extern_cfg (Bool, net_guesspaths); +extern_cfg (Bool, net_autoclose); +extern_cfg (Bool, gl_logostuds); +extern_cfg (String, ld_defaultname); +extern_cfg (String, ld_defaultuser); +extern_cfg (Int, ld_defaultlicense); +extern_cfg (String, prog_ytruder); +extern_cfg (String, prog_rectifier); +extern_cfg (String, prog_intersector); +extern_cfg (String, prog_coverer); +extern_cfg (String, prog_isecalc); +extern_cfg (String, prog_edger2); +extern_cfg (Bool, prog_ytruder_wine); +extern_cfg (Bool, prog_rectifier_wine); +extern_cfg (Bool, prog_intersector_wine); +extern_cfg (Bool, prog_coverer_wine); +extern_cfg (Bool, prog_isecalc_wine); +extern_cfg (Bool, prog_edger2_wine); + +#define act(N) extern_cfg (KeySequence, key_##N); +#include "actions.h" + +const char* g_extProgPathFilter = +#ifdef _WIN32 + "Applications (*.exe)(*.exe);;All files (*.*)(*.*)"; +#else + ""; +#endif + +// ============================================================================= +// ----------------------------------------------------------------------------- +ConfigDialog::ConfigDialog (ConfigDialog::Tab deftab, QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f) +{ + assert (g_win != null); + ui = new Ui_ConfigUI; + ui->setupUi (this); + + // Interface tab + setButtonBackground (ui->backgroundColorButton, gl_bgcolor); + connect (ui->backgroundColorButton, SIGNAL (clicked()), + this, SLOT (slot_setGLBackground())); + + setButtonBackground (ui->mainColorButton, gl_maincolor); + connect (ui->mainColorButton, SIGNAL (clicked()), + this, SLOT (slot_setGLForeground())); + + ui->mainColorAlpha->setValue (gl_maincolor_alpha * 10.0f); + ui->lineThickness->setValue (gl_linethickness); + ui->colorizeObjects->setChecked (lv_colorize); + ui->colorBFC->setChecked (gl_colorbfc); + ui->blackEdges->setChecked (gl_blackedges); + ui->m_aa->setChecked (gl_aa); + ui->implicitFiles->setChecked (gui_implicitfiles); + ui->m_logostuds->setChecked (gl_logostuds); + + int i = 0; +#define act(N) addShortcut (key_##N, ACTION(N), i); +#include "actions.h" + + ui->shortcutsList->setSortingEnabled (true); + ui->shortcutsList->sortItems(); + + connect (ui->shortcut_set, SIGNAL (clicked()), this, SLOT (slot_setShortcut())); + connect (ui->shortcut_reset, SIGNAL (clicked()), this, SLOT (slot_resetShortcut())); + connect (ui->shortcut_clear, SIGNAL (clicked()), this, SLOT (slot_clearShortcut())); + + quickColors = quickColorsFromConfig(); + updateQuickColorList(); + + connect (ui->quickColor_add, SIGNAL (clicked()), this, SLOT (slot_setColor())); + connect (ui->quickColor_remove, SIGNAL (clicked()), this, SLOT (slot_delColor())); + connect (ui->quickColor_edit, SIGNAL (clicked()), this, SLOT (slot_setColor())); + connect (ui->quickColor_addSep, SIGNAL (clicked()), this, SLOT (slot_addColorSeparator())); + connect (ui->quickColor_moveUp, SIGNAL (clicked()), this, SLOT (slot_moveColor())); + connect (ui->quickColor_moveDown, SIGNAL (clicked()), this, SLOT (slot_moveColor())); + connect (ui->quickColor_clear, SIGNAL (clicked()), this, SLOT (slot_clearColors())); + + ui->downloadPath->setText (net_downloadpath); + ui->guessNetPaths->setChecked (net_guesspaths); + ui->autoCloseNetPrompt->setChecked (net_autoclose); + connect (ui->findDownloadPath, SIGNAL (clicked (bool)), this, SLOT (slot_findDownloadFolder())); + + ui->m_profileName->setText (ld_defaultname); + ui->m_profileUsername->setText (ld_defaultuser); + ui->m_profileLicense->setCurrentIndex (ld_defaultlicense); + + initGrids(); + initExtProgs(); + selectPage (deftab); + + connect (ui->buttonBox, SIGNAL (clicked (QAbstractButton*)), + this, SLOT (buttonClicked (QAbstractButton*))); + + connect (ui->m_pages, SIGNAL (currentChanged (int)), + this, SLOT (selectPage (int))); + + connect (ui->m_pagelist, SIGNAL (currentRowChanged (int)), + this, SLOT (selectPage (int))); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +ConfigDialog::~ConfigDialog() +{ delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ConfigDialog::selectPage (int row) +{ ui->m_pagelist->setCurrentRow (row); + ui->m_pages->setCurrentIndex (row); +} + +// ============================================================================= +// Adds a shortcut entry to the list of shortcuts. +// ----------------------------------------------------------------------------- +void ConfigDialog::addShortcut (KeySequenceConfig& cfg, QAction* act, int& i) +{ ShortcutListItem* item = new ShortcutListItem; + item->setIcon (act->icon()); + item->setKeyConfig (&cfg); + item->setAction (act); + setShortcutText (item); + + // If the action doesn't have a valid icon, use an empty one + // so that the list is kept aligned. + if (act->icon().isNull()) + item->setIcon (getIcon ("empty")); + + ui->shortcutsList->insertItem (i++, item); +} + +// ============================================================================= +// Initializes the table of grid stuff +// ----------------------------------------------------------------------------- +void ConfigDialog::initGrids() +{ QGridLayout* gridlayout = new QGridLayout; + QLabel* xlabel = new QLabel ("X"), + *ylabel = new QLabel ("Y"), + *zlabel = new QLabel ("Z"), + *anglabel = new QLabel ("Angle"); + int i = 1; + + for (QLabel* label : initlist<QLabel*> ({xlabel, ylabel, zlabel, anglabel})) + { label->setAlignment (Qt::AlignCenter); + gridlayout->addWidget (label, 0, i++); + } + + for (int i = 0; i < g_NumGrids; ++i) + { // Icon + lb_gridIcons[i] = new QLabel; + lb_gridIcons[i]->setPixmap (getIcon (fmt ("grid-%1", str (g_GridInfo[i].name).toLower()))); + + // Text label + lb_gridLabels[i] = new QLabel (fmt ("%1:", g_GridInfo[i].name)); + + QHBoxLayout* labellayout = new QHBoxLayout; + labellayout->addWidget (lb_gridIcons[i]); + labellayout->addWidget (lb_gridLabels[i]); + gridlayout->addLayout (labellayout, i + 1, 0); + + // Add the widgets + for (int j = 0; j < 4; ++j) + { dsb_gridData[i][j] = new QDoubleSpinBox; + dsb_gridData[i][j]->setValue (g_GridInfo[i].confs[j]->value); + gridlayout->addWidget (dsb_gridData[i][j], i + 1, j + 1); + } + } + + ui->grids->setLayout (gridlayout); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static const struct LDExtProgInfo +{ const str name, iconname; + StringConfig* const path; + mutable QLineEdit* input; + mutable QPushButton* setPathButton; +#ifndef _WIN32 + BoolConfig* const wine; + mutable QCheckBox* wineBox; +#endif // _WIN32 +} g_LDExtProgInfo[] = +{ +#ifndef _WIN32 +# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &prog_##LOWNAME, null, null, &prog_##LOWNAME##_wine, null }, +#else +# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &prog_##LOWNAME, null, null }, +#endif + EXTPROG (Ytruder, ytruder) + EXTPROG (Rectifier, rectifier) + EXTPROG (Intersector, intersector) + EXTPROG (Isecalc, isecalc) + EXTPROG (Coverer, coverer) + EXTPROG (Edger2, edger2) +#undef EXTPROG +}; + +// ============================================================================= +// Initializes the stuff in the ext programs tab +// ----------------------------------------------------------------------------- +void ConfigDialog::initExtProgs() +{ QGridLayout* pathsLayout = new QGridLayout; + int row = 0; + + for (const LDExtProgInfo& info : g_LDExtProgInfo) + { QLabel* icon = new QLabel, + *progLabel = new QLabel (info.name); + QLineEdit* input = new QLineEdit; + QPushButton* setPathButton = new QPushButton; + + icon->setPixmap (getIcon (info.iconname)); + input->setText (info.path->value); + setPathButton->setIcon (getIcon ("folder")); + info.input = input; + info.setPathButton = setPathButton; + + connect (setPathButton, SIGNAL (clicked()), this, SLOT (slot_setExtProgPath())); + + pathsLayout->addWidget (icon, row, 0); + pathsLayout->addWidget (progLabel, row, 1); + pathsLayout->addWidget (input, row, 2); + pathsLayout->addWidget (setPathButton, row, 3); + +#ifndef _WIN32 + QCheckBox* wineBox = new QCheckBox ("Wine"); + wineBox->setChecked (*info.wine); + info.wineBox = wineBox; + pathsLayout->addWidget (wineBox, row, 4); +#endif + + ++row; + } + + ui->extProgs->setLayout (pathsLayout); +} + +// ============================================================================= +// Set the settings based on widget data. +// ----------------------------------------------------------------------------- +void ConfigDialog::applySettings() +{ // Apply configuration + lv_colorize = ui->colorizeObjects->isChecked(); + gl_colorbfc = ui->colorBFC->isChecked(); + gl_blackedges = ui->blackEdges->isChecked(); + gl_maincolor_alpha = ( (double) ui->mainColorAlpha->value()) / 10.0f; + gl_linethickness = ui->lineThickness->value(); + gui_implicitfiles = ui->implicitFiles->isChecked(); + net_downloadpath = ui->downloadPath->text(); + net_guesspaths = ui->guessNetPaths->isChecked(); + net_autoclose = ui->autoCloseNetPrompt->isChecked(); + gl_logostuds = ui->m_logostuds->isChecked(); + ld_defaultuser = ui->m_profileUsername->text(); + ld_defaultname = ui->m_profileName->text(); + ld_defaultlicense = ui->m_profileLicense->currentIndex(); + gl_aa = ui->m_aa->isChecked(); + + // Rebuild the quick color toolbar + g_win->setQuickColors (quickColors); + gui_colortoolbar = quickColorString(); + + // Set the grid settings + for (int i = 0; i < g_NumGrids; ++i) + for (int j = 0; j < 4; ++j) + g_GridInfo[i].confs[j]->value = dsb_gridData[i][j]->value(); + + // Apply key shortcuts +#define act(N) ACTION(N)->setShortcut (key_##N); +#include "actions.h" + + // Ext program settings + for (const LDExtProgInfo& info : g_LDExtProgInfo) + { *info.path = info.input->text(); + +#ifndef _WIN32 + *info.wine = info.wineBox->isChecked(); +#endif // _WIN32 + } + + Config::save(); + reloadAllSubfiles(); + loadLogoedStuds(); + g_win->R()->setBackground(); + g_win->doFullRefresh(); + g_win->updateToolBars(); + g_win->updateDocumentList(); +} + +// ============================================================================= +// A dialog button was clicked +// ----------------------------------------------------------------------------- +void ConfigDialog::buttonClicked (QAbstractButton* button) +{ typedef QDialogButtonBox QDDB; + QDialogButtonBox* dbb = ui->buttonBox; + + if (button == dbb->button (QDDB::Ok)) + { applySettings(); + accept(); + } elif (button == dbb->button (QDDB::Apply)) + { applySettings(); + } elif (button == dbb->button (QDDB::Cancel)) + { reject(); + } +} + +// ============================================================================= +// Update the list of color toolbar items in the quick color tab. +// ----------------------------------------------------------------------------- +void ConfigDialog::updateQuickColorList (LDQuickColor* sel) +{ for (QListWidgetItem * item : quickColorItems) + delete item; + + quickColorItems.clear(); + + // Init table items + for (LDQuickColor& entry : quickColors) + { QListWidgetItem* item = new QListWidgetItem; + + if (entry.isSeparator()) + { item->setText ("--------"); + item->setIcon (getIcon ("empty")); + } + else + { LDColor* col = entry.getColor(); + + if (col == null) + { item->setText ("[[unknown color]]"); + item->setIcon (getIcon ("error")); + } + else + { item->setText (col->name); + item->setIcon (makeColorIcon (col, 16)); + } + } + + ui->quickColorList->addItem (item); + quickColorItems << item; + + if (sel && &entry == sel) + { ui->quickColorList->setCurrentItem (item); + ui->quickColorList->scrollToItem (item); + } + } +} + +// ============================================================================= +// Quick colors: add or edit button was clicked. +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_setColor() +{ LDQuickColor* entry = null; + QListWidgetItem* item = null; + const bool isNew = static_cast<QPushButton*> (sender()) == ui->quickColor_add; + + if (isNew == false) + { item = getSelectedQuickColor(); + + if (!item) + return; + + int i = getItemRow (item, quickColorItems); + entry = &quickColors[i]; + + if (entry->isSeparator() == true) + return; // don't color separators + } + + int defval = entry ? entry->getColor()->index : -1; + int val; + + if (ColorSelector::selectColor (val, defval, this) == false) + return; + + if (entry) + entry->setColor (getColor (val)); + else + { LDQuickColor entry (getColor (val), null); + + item = getSelectedQuickColor(); + int idx = (item) ? getItemRow (item, quickColorItems) + 1 : quickColorItems.size(); + + quickColors.insert (idx, entry); + entry = quickColors[idx]; + } + + updateQuickColorList (entry); +} + +// ============================================================================= +// Remove a quick color +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_delColor() +{ if (ui->quickColorList->selectedItems().isEmpty()) + return; + + QListWidgetItem* item = ui->quickColorList->selectedItems() [0]; + quickColors.removeAt (getItemRow (item, quickColorItems)); + updateQuickColorList(); +} + +// ============================================================================= +// Move a quick color up/down +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_moveColor() +{ const bool up = (static_cast<QPushButton*> (sender()) == ui->quickColor_moveUp); + + if (ui->quickColorList->selectedItems().isEmpty()) + return; + + QListWidgetItem* item = ui->quickColorList->selectedItems() [0]; + int idx = getItemRow (item, quickColorItems); + int dest = up ? (idx - 1) : (idx + 1); + + if (dest < 0 || dest >= quickColorItems.size()) + return; // destination out of bounds + + LDQuickColor tmp = quickColors[dest]; + quickColors[dest] = quickColors[idx]; + quickColors[idx] = tmp; + + updateQuickColorList (&quickColors[dest]); +} + +// ============================================================================= +// Add a separator to quick colors +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_addColorSeparator() +{ quickColors << LDQuickColor::getSeparator(); + updateQuickColorList (&quickColors[quickColors.size() - 1]); +} + +// ============================================================================= +// Clear all quick colors +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_clearColors() +{ quickColors.clear(); + updateQuickColorList(); +} + +// ============================================================================= +// Pick a color and set the appropriate configuration option. +// ----------------------------------------------------------------------------- +void ConfigDialog::pickColor (StringConfig& conf, QPushButton* button) +{ QColor col = QColorDialog::getColor (QColor (conf)); + + if (col.isValid()) + { uchar r = col.red(), + g = col.green(), + b = col.blue(); + conf.value.sprintf ("#%.2X%.2X%.2X", r, g, b); + setButtonBackground (button, conf.value); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_setGLBackground() +{ pickColor (gl_bgcolor, ui->backgroundColorButton); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_setGLForeground() +{ pickColor (gl_maincolor, ui->mainColorButton); +} + +// ============================================================================= +// Sets background color of a given button. +// ----------------------------------------------------------------------------- +void ConfigDialog::setButtonBackground (QPushButton* button, str value) +{ button->setIcon (getIcon ("colorselect")); + button->setAutoFillBackground (true); + button->setStyleSheet (fmt ("background-color: %1", value)); +} + +// ============================================================================= +// Finds the given list widget item in the list of widget items given. +// ----------------------------------------------------------------------------- +int ConfigDialog::getItemRow (QListWidgetItem* item, QList<QListWidgetItem*>& haystack) +{ int i = 0; + + for (QListWidgetItem* it : haystack) + { if (it == item) + return i; + + ++i; + } + + return -1; +} + +// ============================================================================= +// Which quick color is currently selected? +// ----------------------------------------------------------------------------- +QListWidgetItem* ConfigDialog::getSelectedQuickColor() +{ if (ui->quickColorList->selectedItems().isEmpty()) + return null; + + return ui->quickColorList->selectedItems() [0]; +} + +// ============================================================================= +// Get the list of shortcuts selected +// ----------------------------------------------------------------------------- +QList<ShortcutListItem*> ConfigDialog::getShortcutSelection() +{ QList<ShortcutListItem*> out; + + for (QListWidgetItem* entry : ui->shortcutsList->selectedItems()) + out << static_cast<ShortcutListItem*> (entry); + + return out; +} + +// ============================================================================= +// Edit the shortcut of a given action. +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_setShortcut() +{ QList<ShortcutListItem*> sel = getShortcutSelection(); + + if (sel.size() < 1) + return; + + ShortcutListItem* item = sel[0]; + + if (KeySequenceDialog::staticDialog (item->getKeyConfig(), this)) + setShortcutText (item); +} + +// ============================================================================= +// Reset a shortcut to defaults +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_resetShortcut() +{ QList<ShortcutListItem*> sel = getShortcutSelection(); + + for (ShortcutListItem* item : sel) + { item->getKeyConfig()->reset(); + setShortcutText (item); + } +} + +// ============================================================================= +// Remove the shortcut of an action. +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_clearShortcut() +{ QList<ShortcutListItem*> sel = getShortcutSelection(); + + for (ShortcutListItem* item : sel) + { item->getKeyConfig()->value = QKeySequence(); + setShortcutText (item); + } +} + +// ============================================================================= +// Set the path of an external program +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_setExtProgPath() +{ const LDExtProgInfo* info = null; + + for (const LDExtProgInfo& it : g_LDExtProgInfo) + { if (it.setPathButton == sender()) + { info = ⁢ + break; + } + } + + assert (info != null); + str fpath = QFileDialog::getOpenFileName (this, fmt ("Path to %1", info->name), *info->path, g_extProgPathFilter); + + if (fpath.isEmpty()) + return; + + info->input->setText (fpath); +} + +// ============================================================================= +// '...' button pressed for the download path +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_findDownloadFolder() +{ str dpath = QFileDialog::getExistingDirectory(); + ui->downloadPath->setText (dpath); +} + +// ============================================================================= +// Updates the text string for a given shortcut list item +// ----------------------------------------------------------------------------- +void ConfigDialog::setShortcutText (ShortcutListItem* item) +{ QAction* act = item->getAction(); + str label = act->iconText(); + str keybind = item->getKeyConfig()->value.toString(); + item->setText (fmt ("%1 (%2)", label, keybind)); +} + +// ============================================================================= +// Gets the configuration string of the quick color toolbar +// ----------------------------------------------------------------------------- +str ConfigDialog::quickColorString() +{ str val; + + for (const LDQuickColor& entry : quickColors) + { if (val.length() > 0) + val += ':'; + + if (entry.isSeparator()) + val += '|'; + else + val += fmt ("%1", entry.getColor()->index); + } + + return val; +} + +// =============================================================================================== +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// =============================================================================================== +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// =============================================================================================== +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// =============================================================================================== +KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f), seq (seq) +{ lb_output = new QLabel; + IMPLEMENT_DIALOG_BUTTONS + + setWhatsThis (tr ("Into this dialog you can input a key sequence for use as a " + "shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to " + "dismiss.")); + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget (lb_output); + layout->addWidget (bbx_buttons); + setLayout (layout); + + updateOutput(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool KeySequenceDialog::staticDialog (KeySequenceConfig* cfg, QWidget* parent) +{ KeySequenceDialog dlg (cfg->value, parent); + + if (dlg.exec() == false) + return false; + + cfg->value = dlg.seq; + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void KeySequenceDialog::updateOutput() +{ str shortcut = seq.toString(); + + if (seq == QKeySequence()) + shortcut = "<empty>"; + + str text = fmt ("<center><b>%1</b></center>", shortcut); + lb_output->setText (text); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) +{ seq = ev->key() + ev->modifiers(); + updateOutput(); +}
--- a/src/configDialog.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,719 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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/>. - * ===================================================================== - * - * configDialog.cpp: Settings dialog and everything related to it. - * Actual configuration core is in config.cpp. - */ - -#include <QGridLayout> -#include <QFileDialog> -#include <QColorDialog> -#include <QBoxLayout> -#include <QKeyEvent> -#include <QGroupBox> -#include <QDoubleSpinBox> -#include <QLineEdit> -#include <QCheckBox> -#include "main.h" -#include "configDialog.h" -#include "document.h" -#include "config.h" -#include "misc.h" -#include "colors.h" -#include "colorSelectDialog.h" -#include "gldraw.h" -#include "ui_config.h" -#include "moc_configDialog.cpp" - -extern_cfg (String, gl_bgcolor); -extern_cfg (String, gl_maincolor); -extern_cfg (Bool, lv_colorize); -extern_cfg (Bool, gl_colorbfc); -extern_cfg (Float, gl_maincolor_alpha); -extern_cfg (Int, gl_linethickness); -extern_cfg (String, gui_colortoolbar); -extern_cfg (Bool, edit_schemanticinline); -extern_cfg (Bool, gl_blackedges); -extern_cfg (Bool, gl_aa); -extern_cfg (Bool, gui_implicitfiles); -extern_cfg (String, net_downloadpath); -extern_cfg (Bool, net_guesspaths); -extern_cfg (Bool, net_autoclose); -extern_cfg (Bool, gl_logostuds); -extern_cfg (String, ld_defaultname); -extern_cfg (String, ld_defaultuser); -extern_cfg (Int, ld_defaultlicense); -extern_cfg (String, prog_ytruder); -extern_cfg (String, prog_rectifier); -extern_cfg (String, prog_intersector); -extern_cfg (String, prog_coverer); -extern_cfg (String, prog_isecalc); -extern_cfg (String, prog_edger2); -extern_cfg (Bool, prog_ytruder_wine); -extern_cfg (Bool, prog_rectifier_wine); -extern_cfg (Bool, prog_intersector_wine); -extern_cfg (Bool, prog_coverer_wine); -extern_cfg (Bool, prog_isecalc_wine); -extern_cfg (Bool, prog_edger2_wine); - -#define act(N) extern_cfg (KeySequence, key_##N); -#include "actions.h" - -const char* g_extProgPathFilter = -#ifdef _WIN32 - "Applications (*.exe)(*.exe);;All files (*.*)(*.*)"; -#else - ""; -#endif - -// ============================================================================= -// ----------------------------------------------------------------------------- -ConfigDialog::ConfigDialog (ConfigDialog::Tab deftab, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f) -{ - assert (g_win != null); - ui = new Ui_ConfigUI; - ui->setupUi (this); - - // Interface tab - setButtonBackground (ui->backgroundColorButton, gl_bgcolor); - connect (ui->backgroundColorButton, SIGNAL (clicked()), - this, SLOT (slot_setGLBackground())); - - setButtonBackground (ui->mainColorButton, gl_maincolor); - connect (ui->mainColorButton, SIGNAL (clicked()), - this, SLOT (slot_setGLForeground())); - - ui->mainColorAlpha->setValue (gl_maincolor_alpha * 10.0f); - ui->lineThickness->setValue (gl_linethickness); - ui->colorizeObjects->setChecked (lv_colorize); - ui->colorBFC->setChecked (gl_colorbfc); - ui->blackEdges->setChecked (gl_blackedges); - ui->m_aa->setChecked (gl_aa); - ui->implicitFiles->setChecked (gui_implicitfiles); - ui->m_logostuds->setChecked (gl_logostuds); - - int i = 0; -#define act(N) addShortcut (key_##N, ACTION(N), i); -#include "actions.h" - - ui->shortcutsList->setSortingEnabled (true); - ui->shortcutsList->sortItems(); - - connect (ui->shortcut_set, SIGNAL (clicked()), this, SLOT (slot_setShortcut())); - connect (ui->shortcut_reset, SIGNAL (clicked()), this, SLOT (slot_resetShortcut())); - connect (ui->shortcut_clear, SIGNAL (clicked()), this, SLOT (slot_clearShortcut())); - - quickColors = quickColorsFromConfig(); - updateQuickColorList(); - - connect (ui->quickColor_add, SIGNAL (clicked()), this, SLOT (slot_setColor())); - connect (ui->quickColor_remove, SIGNAL (clicked()), this, SLOT (slot_delColor())); - connect (ui->quickColor_edit, SIGNAL (clicked()), this, SLOT (slot_setColor())); - connect (ui->quickColor_addSep, SIGNAL (clicked()), this, SLOT (slot_addColorSeparator())); - connect (ui->quickColor_moveUp, SIGNAL (clicked()), this, SLOT (slot_moveColor())); - connect (ui->quickColor_moveDown, SIGNAL (clicked()), this, SLOT (slot_moveColor())); - connect (ui->quickColor_clear, SIGNAL (clicked()), this, SLOT (slot_clearColors())); - - ui->downloadPath->setText (net_downloadpath); - ui->guessNetPaths->setChecked (net_guesspaths); - ui->autoCloseNetPrompt->setChecked (net_autoclose); - connect (ui->findDownloadPath, SIGNAL (clicked (bool)), this, SLOT (slot_findDownloadFolder())); - - ui->m_profileName->setText (ld_defaultname); - ui->m_profileUsername->setText (ld_defaultuser); - ui->m_profileLicense->setCurrentIndex (ld_defaultlicense); - - initGrids(); - initExtProgs(); - selectPage (deftab); - - connect (ui->buttonBox, SIGNAL (clicked (QAbstractButton*)), - this, SLOT (buttonClicked (QAbstractButton*))); - - connect (ui->m_pages, SIGNAL (currentChanged (int)), - this, SLOT (selectPage (int))); - - connect (ui->m_pagelist, SIGNAL (currentRowChanged (int)), - this, SLOT (selectPage (int))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -ConfigDialog::~ConfigDialog() -{ delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ConfigDialog::selectPage (int row) -{ ui->m_pagelist->setCurrentRow (row); - ui->m_pages->setCurrentIndex (row); -} - -// ============================================================================= -// Adds a shortcut entry to the list of shortcuts. -// ----------------------------------------------------------------------------- -void ConfigDialog::addShortcut (KeySequenceConfig& cfg, QAction* act, int& i) -{ ShortcutListItem* item = new ShortcutListItem; - item->setIcon (act->icon()); - item->setKeyConfig (&cfg); - item->setAction (act); - setShortcutText (item); - - // If the action doesn't have a valid icon, use an empty one - // so that the list is kept aligned. - if (act->icon().isNull()) - item->setIcon (getIcon ("empty")); - - ui->shortcutsList->insertItem (i++, item); -} - -// ============================================================================= -// Initializes the table of grid stuff -// ----------------------------------------------------------------------------- -void ConfigDialog::initGrids() -{ QGridLayout* gridlayout = new QGridLayout; - QLabel* xlabel = new QLabel ("X"), - *ylabel = new QLabel ("Y"), - *zlabel = new QLabel ("Z"), - *anglabel = new QLabel ("Angle"); - int i = 1; - - for (QLabel* label : initlist<QLabel*> ({xlabel, ylabel, zlabel, anglabel})) - { label->setAlignment (Qt::AlignCenter); - gridlayout->addWidget (label, 0, i++); - } - - for (int i = 0; i < g_NumGrids; ++i) - { // Icon - lb_gridIcons[i] = new QLabel; - lb_gridIcons[i]->setPixmap (getIcon (fmt ("grid-%1", str (g_GridInfo[i].name).toLower()))); - - // Text label - lb_gridLabels[i] = new QLabel (fmt ("%1:", g_GridInfo[i].name)); - - QHBoxLayout* labellayout = new QHBoxLayout; - labellayout->addWidget (lb_gridIcons[i]); - labellayout->addWidget (lb_gridLabels[i]); - gridlayout->addLayout (labellayout, i + 1, 0); - - // Add the widgets - for (int j = 0; j < 4; ++j) - { dsb_gridData[i][j] = new QDoubleSpinBox; - dsb_gridData[i][j]->setValue (g_GridInfo[i].confs[j]->value); - gridlayout->addWidget (dsb_gridData[i][j], i + 1, j + 1); - } - } - - ui->grids->setLayout (gridlayout); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static const struct LDExtProgInfo -{ const str name, iconname; - StringConfig* const path; - mutable QLineEdit* input; - mutable QPushButton* setPathButton; -#ifndef _WIN32 - BoolConfig* const wine; - mutable QCheckBox* wineBox; -#endif // _WIN32 -} g_LDExtProgInfo[] = -{ -#ifndef _WIN32 -# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &prog_##LOWNAME, null, null, &prog_##LOWNAME##_wine, null }, -#else -# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &prog_##LOWNAME, null, null }, -#endif - EXTPROG (Ytruder, ytruder) - EXTPROG (Rectifier, rectifier) - EXTPROG (Intersector, intersector) - EXTPROG (Isecalc, isecalc) - EXTPROG (Coverer, coverer) - EXTPROG (Edger2, edger2) -#undef EXTPROG -}; - -// ============================================================================= -// Initializes the stuff in the ext programs tab -// ----------------------------------------------------------------------------- -void ConfigDialog::initExtProgs() -{ QGridLayout* pathsLayout = new QGridLayout; - int row = 0; - - for (const LDExtProgInfo& info : g_LDExtProgInfo) - { QLabel* icon = new QLabel, - *progLabel = new QLabel (info.name); - QLineEdit* input = new QLineEdit; - QPushButton* setPathButton = new QPushButton; - - icon->setPixmap (getIcon (info.iconname)); - input->setText (info.path->value); - setPathButton->setIcon (getIcon ("folder")); - info.input = input; - info.setPathButton = setPathButton; - - connect (setPathButton, SIGNAL (clicked()), this, SLOT (slot_setExtProgPath())); - - pathsLayout->addWidget (icon, row, 0); - pathsLayout->addWidget (progLabel, row, 1); - pathsLayout->addWidget (input, row, 2); - pathsLayout->addWidget (setPathButton, row, 3); - -#ifndef _WIN32 - QCheckBox* wineBox = new QCheckBox ("Wine"); - wineBox->setChecked (*info.wine); - info.wineBox = wineBox; - pathsLayout->addWidget (wineBox, row, 4); -#endif - - ++row; - } - - ui->extProgs->setLayout (pathsLayout); -} - -// ============================================================================= -// Set the settings based on widget data. -// ----------------------------------------------------------------------------- -void ConfigDialog::applySettings() -{ // Apply configuration - lv_colorize = ui->colorizeObjects->isChecked(); - gl_colorbfc = ui->colorBFC->isChecked(); - gl_blackedges = ui->blackEdges->isChecked(); - gl_maincolor_alpha = ( (double) ui->mainColorAlpha->value()) / 10.0f; - gl_linethickness = ui->lineThickness->value(); - gui_implicitfiles = ui->implicitFiles->isChecked(); - net_downloadpath = ui->downloadPath->text(); - net_guesspaths = ui->guessNetPaths->isChecked(); - net_autoclose = ui->autoCloseNetPrompt->isChecked(); - gl_logostuds = ui->m_logostuds->isChecked(); - ld_defaultuser = ui->m_profileUsername->text(); - ld_defaultname = ui->m_profileName->text(); - ld_defaultlicense = ui->m_profileLicense->currentIndex(); - gl_aa = ui->m_aa->isChecked(); - - // Rebuild the quick color toolbar - g_win->setQuickColors (quickColors); - gui_colortoolbar = quickColorString(); - - // Set the grid settings - for (int i = 0; i < g_NumGrids; ++i) - for (int j = 0; j < 4; ++j) - g_GridInfo[i].confs[j]->value = dsb_gridData[i][j]->value(); - - // Apply key shortcuts -#define act(N) ACTION(N)->setShortcut (key_##N); -#include "actions.h" - - // Ext program settings - for (const LDExtProgInfo& info : g_LDExtProgInfo) - { *info.path = info.input->text(); - -#ifndef _WIN32 - *info.wine = info.wineBox->isChecked(); -#endif // _WIN32 - } - - Config::save(); - reloadAllSubfiles(); - loadLogoedStuds(); - g_win->R()->setBackground(); - g_win->doFullRefresh(); - g_win->updateToolBars(); - g_win->updateDocumentList(); -} - -// ============================================================================= -// A dialog button was clicked -// ----------------------------------------------------------------------------- -void ConfigDialog::buttonClicked (QAbstractButton* button) -{ typedef QDialogButtonBox QDDB; - QDialogButtonBox* dbb = ui->buttonBox; - - if (button == dbb->button (QDDB::Ok)) - { applySettings(); - accept(); - } elif (button == dbb->button (QDDB::Apply)) - { applySettings(); - } elif (button == dbb->button (QDDB::Cancel)) - { reject(); - } -} - -// ============================================================================= -// Update the list of color toolbar items in the quick color tab. -// ----------------------------------------------------------------------------- -void ConfigDialog::updateQuickColorList (LDQuickColor* sel) -{ for (QListWidgetItem * item : quickColorItems) - delete item; - - quickColorItems.clear(); - - // Init table items - for (LDQuickColor& entry : quickColors) - { QListWidgetItem* item = new QListWidgetItem; - - if (entry.isSeparator()) - { item->setText ("--------"); - item->setIcon (getIcon ("empty")); - } - else - { LDColor* col = entry.getColor(); - - if (col == null) - { item->setText ("[[unknown color]]"); - item->setIcon (getIcon ("error")); - } - else - { item->setText (col->name); - item->setIcon (makeColorIcon (col, 16)); - } - } - - ui->quickColorList->addItem (item); - quickColorItems << item; - - if (sel && &entry == sel) - { ui->quickColorList->setCurrentItem (item); - ui->quickColorList->scrollToItem (item); - } - } -} - -// ============================================================================= -// Quick colors: add or edit button was clicked. -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_setColor() -{ LDQuickColor* entry = null; - QListWidgetItem* item = null; - const bool isNew = static_cast<QPushButton*> (sender()) == ui->quickColor_add; - - if (isNew == false) - { item = getSelectedQuickColor(); - - if (!item) - return; - - int i = getItemRow (item, quickColorItems); - entry = &quickColors[i]; - - if (entry->isSeparator() == true) - return; // don't color separators - } - - int defval = entry ? entry->getColor()->index : -1; - int val; - - if (ColorSelector::selectColor (val, defval, this) == false) - return; - - if (entry) - entry->setColor (getColor (val)); - else - { LDQuickColor entry (getColor (val), null); - - item = getSelectedQuickColor(); - int idx = (item) ? getItemRow (item, quickColorItems) + 1 : quickColorItems.size(); - - quickColors.insert (idx, entry); - entry = quickColors[idx]; - } - - updateQuickColorList (entry); -} - -// ============================================================================= -// Remove a quick color -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_delColor() -{ if (ui->quickColorList->selectedItems().isEmpty()) - return; - - QListWidgetItem* item = ui->quickColorList->selectedItems() [0]; - quickColors.removeAt (getItemRow (item, quickColorItems)); - updateQuickColorList(); -} - -// ============================================================================= -// Move a quick color up/down -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_moveColor() -{ const bool up = (static_cast<QPushButton*> (sender()) == ui->quickColor_moveUp); - - if (ui->quickColorList->selectedItems().isEmpty()) - return; - - QListWidgetItem* item = ui->quickColorList->selectedItems() [0]; - int idx = getItemRow (item, quickColorItems); - int dest = up ? (idx - 1) : (idx + 1); - - if (dest < 0 || dest >= quickColorItems.size()) - return; // destination out of bounds - - LDQuickColor tmp = quickColors[dest]; - quickColors[dest] = quickColors[idx]; - quickColors[idx] = tmp; - - updateQuickColorList (&quickColors[dest]); -} - -// ============================================================================= -// Add a separator to quick colors -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_addColorSeparator() -{ quickColors << LDQuickColor::getSeparator(); - updateQuickColorList (&quickColors[quickColors.size() - 1]); -} - -// ============================================================================= -// Clear all quick colors -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_clearColors() -{ quickColors.clear(); - updateQuickColorList(); -} - -// ============================================================================= -// Pick a color and set the appropriate configuration option. -// ----------------------------------------------------------------------------- -void ConfigDialog::pickColor (StringConfig& conf, QPushButton* button) -{ QColor col = QColorDialog::getColor (QColor (conf)); - - if (col.isValid()) - { uchar r = col.red(), - g = col.green(), - b = col.blue(); - conf.value.sprintf ("#%.2X%.2X%.2X", r, g, b); - setButtonBackground (button, conf.value); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_setGLBackground() -{ pickColor (gl_bgcolor, ui->backgroundColorButton); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_setGLForeground() -{ pickColor (gl_maincolor, ui->mainColorButton); -} - -// ============================================================================= -// Sets background color of a given button. -// ----------------------------------------------------------------------------- -void ConfigDialog::setButtonBackground (QPushButton* button, str value) -{ button->setIcon (getIcon ("colorselect")); - button->setAutoFillBackground (true); - button->setStyleSheet (fmt ("background-color: %1", value)); -} - -// ============================================================================= -// Finds the given list widget item in the list of widget items given. -// ----------------------------------------------------------------------------- -int ConfigDialog::getItemRow (QListWidgetItem* item, QList<QListWidgetItem*>& haystack) -{ int i = 0; - - for (QListWidgetItem* it : haystack) - { if (it == item) - return i; - - ++i; - } - - return -1; -} - -// ============================================================================= -// Which quick color is currently selected? -// ----------------------------------------------------------------------------- -QListWidgetItem* ConfigDialog::getSelectedQuickColor() -{ if (ui->quickColorList->selectedItems().isEmpty()) - return null; - - return ui->quickColorList->selectedItems() [0]; -} - -// ============================================================================= -// Get the list of shortcuts selected -// ----------------------------------------------------------------------------- -QList<ShortcutListItem*> ConfigDialog::getShortcutSelection() -{ QList<ShortcutListItem*> out; - - for (QListWidgetItem* entry : ui->shortcutsList->selectedItems()) - out << static_cast<ShortcutListItem*> (entry); - - return out; -} - -// ============================================================================= -// Edit the shortcut of a given action. -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_setShortcut() -{ QList<ShortcutListItem*> sel = getShortcutSelection(); - - if (sel.size() < 1) - return; - - ShortcutListItem* item = sel[0]; - - if (KeySequenceDialog::staticDialog (item->getKeyConfig(), this)) - setShortcutText (item); -} - -// ============================================================================= -// Reset a shortcut to defaults -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_resetShortcut() -{ QList<ShortcutListItem*> sel = getShortcutSelection(); - - for (ShortcutListItem* item : sel) - { item->getKeyConfig()->reset(); - setShortcutText (item); - } -} - -// ============================================================================= -// Remove the shortcut of an action. -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_clearShortcut() -{ QList<ShortcutListItem*> sel = getShortcutSelection(); - - for (ShortcutListItem* item : sel) - { item->getKeyConfig()->value = QKeySequence(); - setShortcutText (item); - } -} - -// ============================================================================= -// Set the path of an external program -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_setExtProgPath() -{ const LDExtProgInfo* info = null; - - for (const LDExtProgInfo& it : g_LDExtProgInfo) - { if (it.setPathButton == sender()) - { info = ⁢ - break; - } - } - - assert (info != null); - str fpath = QFileDialog::getOpenFileName (this, fmt ("Path to %1", info->name), *info->path, g_extProgPathFilter); - - if (fpath.isEmpty()) - return; - - info->input->setText (fpath); -} - -// ============================================================================= -// '...' button pressed for the download path -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_findDownloadFolder() -{ str dpath = QFileDialog::getExistingDirectory(); - ui->downloadPath->setText (dpath); -} - -// ============================================================================= -// Updates the text string for a given shortcut list item -// ----------------------------------------------------------------------------- -void ConfigDialog::setShortcutText (ShortcutListItem* item) -{ QAction* act = item->getAction(); - str label = act->iconText(); - str keybind = item->getKeyConfig()->value.toString(); - item->setText (fmt ("%1 (%2)", label, keybind)); -} - -// ============================================================================= -// Gets the configuration string of the quick color toolbar -// ----------------------------------------------------------------------------- -str ConfigDialog::quickColorString() -{ str val; - - for (const LDQuickColor& entry : quickColors) - { if (val.length() > 0) - val += ':'; - - if (entry.isSeparator()) - val += '|'; - else - val += fmt ("%1", entry.getColor()->index); - } - - return val; -} - -// =============================================================================================== -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// =============================================================================================== -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// =============================================================================================== -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// =============================================================================================== -KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f), seq (seq) -{ lb_output = new QLabel; - IMPLEMENT_DIALOG_BUTTONS - - setWhatsThis (tr ("Into this dialog you can input a key sequence for use as a " - "shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to " - "dismiss.")); - - QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (lb_output); - layout->addWidget (bbx_buttons); - setLayout (layout); - - updateOutput(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool KeySequenceDialog::staticDialog (KeySequenceConfig* cfg, QWidget* parent) -{ KeySequenceDialog dlg (cfg->value, parent); - - if (dlg.exec() == false) - return false; - - cfg->value = dlg.seq; - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void KeySequenceDialog::updateOutput() -{ str shortcut = seq.toString(); - - if (seq == QKeySequence()) - shortcut = "<empty>"; - - str text = fmt ("<center><b>%1</b></center>", shortcut); - lb_output->setText (text); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) -{ seq = ev->key() + ev->modifiers(); - updateOutput(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/crashcatcher.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,109 @@ +#ifdef __unix__ + +#include <QString> +#include <QProcess> +#include <QTemporaryFile> +#include <QMessageBox> +#include <unistd.h> +#include <signal.h> +#include <sys/prctl.h> +#include "crashcatcher.h" +#include "types.h" +#include "dialogs.h" + +// Is the crash catcher active now? +static bool g_crashCatcherActive = false; + +// If an assertion failed, what was it? +static str g_assertionFailure; + +// List of signals to catch and crash on +static QList<int> g_signalsToCatch ({ + SIGSEGV, // segmentation fault + SIGABRT, // abort() calls + SIGFPE, // floating point exceptions (e.g. division by zero) + SIGILL, // illegal instructions +}); + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void handleCrash (int sig) +{ printf ("%s: crashed with signal %d, launching gdb\n", __func__, sig); + + if (g_crashCatcherActive) + { printf ("caught signal while crash catcher is active!\n"); + exit (149); + } + + const pid_t pid = getpid(); + QProcess proc; + QTemporaryFile commandsFile; + + g_crashCatcherActive = true; + + if (commandsFile.open()) + { commandsFile.write (fmt ("attach %1\n", pid).toLocal8Bit()); + commandsFile.write (str ("backtrace full\n").toLocal8Bit()); + commandsFile.write (str ("detach\n").toLocal8Bit()); + commandsFile.write (str ("quit").toLocal8Bit()); + commandsFile.flush(); + commandsFile.close(); + } + + QStringList args ({"-x", commandsFile.fileName()}); + + proc.start ("gdb", args); + + // Linux doesn't allow ptrace to be used on anything but direct child processes + // so we need to use prctl to register an exception to this to allow GDB attach to us. + // We need to do this now and no earlier because only now we actually know GDB's PID. + prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0); + + proc.waitForFinished (1000); + str output = QString (proc.readAllStandardOutput()); + str err = QString (proc.readAllStandardError()); + + bombBox (fmt ("<h3>Program crashed with signal %1</h3>\n\n" + "%2" + "<p><b>GDB <tt>stdout</tt>:</b></p><pre>%3</pre>\n" + "<p><b>GDB <tt>stderr</tt>:</b></p><pre>%4</pre>", + sig, (!g_assertionFailure.isEmpty()) ? g_assertionFailure : "", output, err)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void initCrashCatcher() +{ struct sigaction sighandler; + sighandler.sa_handler = &handleCrash; + sighandler.sa_flags = 0; + sigemptyset (&sighandler.sa_mask); + + for (int sig : g_signalsToCatch) + sigaction (sig, &sighandler, null); + + log ("%1: crash catcher hooked to signals: %2\n", __func__, g_signalsToCatch); +} +#endif // #ifdef __unix__ + +// ============================================================================= +// This function must be readily available in both Windows and Linux. We display +// the bomb box straight in Windows while in Linux we let abort() trigger the +// signal handler, which will cause the usual bomb box with GDB diagnostics. +// Said prompt will embed the assertion failure information. +// ----------------------------------------------------------------------------- +void assertionFailure (const char* file, int line, const char* funcname, const char* expr) +{ str errmsg = fmt ( + "<p><b>File</b>: <tt>%1</tt><br />" + "<b>Line</b>: <tt>%2</tt><br />" + "<b>Function:</b> <tt>%3</tt></p>" + "<p>Assertion <b><tt>`%4'</tt></b> failed.</p>", + file, line, funcname, expr); + + g_assertionFailure = errmsg; + +#ifndef __unix__ + bombBox (errmsg); +#endif + + abort(); +} \ No newline at end of file
--- a/src/crashcatcher.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -#ifdef __unix__ - -#include <QString> -#include <QProcess> -#include <QTemporaryFile> -#include <QMessageBox> -#include <unistd.h> -#include <signal.h> -#include <sys/prctl.h> -#include "crashcatcher.h" -#include "types.h" -#include "dialogs.h" - -// Is the crash catcher active now? -static bool g_crashCatcherActive = false; - -// If an assertion failed, what was it? -static str g_assertionFailure; - -// List of signals to catch and crash on -static QList<int> g_signalsToCatch ({ - SIGSEGV, // segmentation fault - SIGABRT, // abort() calls - SIGFPE, // floating point exceptions (e.g. division by zero) - SIGILL, // illegal instructions -}); - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void handleCrash (int sig) -{ printf ("%s: crashed with signal %d, launching gdb\n", __func__, sig); - - if (g_crashCatcherActive) - { printf ("caught signal while crash catcher is active!\n"); - exit (149); - } - - const pid_t pid = getpid(); - QProcess proc; - QTemporaryFile commandsFile; - - g_crashCatcherActive = true; - - if (commandsFile.open()) - { commandsFile.write (fmt ("attach %1\n", pid).toLocal8Bit()); - commandsFile.write (str ("backtrace full\n").toLocal8Bit()); - commandsFile.write (str ("detach\n").toLocal8Bit()); - commandsFile.write (str ("quit").toLocal8Bit()); - commandsFile.flush(); - commandsFile.close(); - } - - QStringList args ({"-x", commandsFile.fileName()}); - - proc.start ("gdb", args); - - // Linux doesn't allow ptrace to be used on anything but direct child processes - // so we need to use prctl to register an exception to this to allow GDB attach to us. - // We need to do this now and no earlier because only now we actually know GDB's PID. - prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0); - - proc.waitForFinished (1000); - str output = QString (proc.readAllStandardOutput()); - str err = QString (proc.readAllStandardError()); - - bombBox (fmt ("<h3>Program crashed with signal %1</h3>\n\n" - "%2" - "<p><b>GDB <tt>stdout</tt>:</b></p><pre>%3</pre>\n" - "<p><b>GDB <tt>stderr</tt>:</b></p><pre>%4</pre>", - sig, (!g_assertionFailure.isEmpty()) ? g_assertionFailure : "", output, err)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void initCrashCatcher() -{ struct sigaction sighandler; - sighandler.sa_handler = &handleCrash; - sighandler.sa_flags = 0; - sigemptyset (&sighandler.sa_mask); - - for (int sig : g_signalsToCatch) - sigaction (sig, &sighandler, null); - - log ("%1: crash catcher hooked to signals: %2\n", __func__, g_signalsToCatch); -} -#endif // #ifdef __unix__ - -// ============================================================================= -// This function must be readily available in both Windows and Linux. We display -// the bomb box straight in Windows while in Linux we let abort() trigger the -// signal handler, which will cause the usual bomb box with GDB diagnostics. -// Said prompt will embed the assertion failure information. -// ----------------------------------------------------------------------------- -void assertionFailure (const char* file, int line, const char* funcname, const char* expr) -{ str errmsg = fmt ( - "<p><b>File</b>: <tt>%1</tt><br />" - "<b>Line</b>: <tt>%2</tt><br />" - "<b>Function:</b> <tt>%3</tt></p>" - "<p>Assertion <b><tt>`%4'</tt></b> failed.</p>", - file, line, funcname, expr); - - g_assertionFailure = errmsg; - -#ifndef __unix__ - bombBox (errmsg); -#endif - - abort(); -} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dialogs.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,340 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QDialog> +#include <QLineEdit> +#include <QSpinBox> +#include <QDialogButtonBox> +#include <QFileDialog> +#include <QLabel> +#include <QPushButton> +#include <QBoxLayout> +#include <QGridLayout> +#include <QProgressBar> +#include <QCheckBox> +#include <QDesktopServices> +#include <QMessageBox> +#include <QUrl> +#include "dialogs.h" +#include "widgets.h" +#include "gui.h" +#include "gldraw.h" +#include "docs.h" +#include "document.h" +#include "dialogs.h" +#include "ui_overlay.h" +#include "ui_ldrawpath.h" +#include "ui_openprogress.h" +#include "ui_extprogpath.h" +#include "ui_about.h" +#include "ui_bombbox.h" +#include "moc_dialogs.cpp" + +extern const char* g_extProgPathFilter; +extern_cfg (String, io_ldpath); + +// ============================================================================= +// ----------------------------------------------------------------------------- +OverlayDialog::OverlayDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) +{ ui = new Ui_OverlayUI; + ui->setupUi (this); + + m_cameraArgs = + { { ui->top, GL::ETopCamera }, + { ui->bottom, GL::EBottomCamera }, + { ui->front, GL::EFrontCamera }, + { ui->back, GL::EBackCamera }, + { ui->left, GL::ELeftCamera }, + { ui->right, GL::ERightCamera } + }; + + GL::EFixedCamera cam = g_win->R()->camera(); + + if (cam == GL::EFreeCamera) + cam = GL::ETopCamera; + + connect (ui->width, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged())); + connect (ui->height, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged())); + connect (ui->buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_help())); + connect (ui->fileSearchButton, SIGNAL (clicked (bool)), this, SLOT (slot_fpath())); + + slot_dimensionsChanged(); + fillDefaults (cam); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +OverlayDialog::~OverlayDialog() +{ delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void OverlayDialog::fillDefaults (int newcam) +{ LDGLOverlay& info = g_win->R()->getOverlay (newcam); + radioDefault<int> (newcam, m_cameraArgs); + + if (info.img != null) + { ui->filename->setText (info.fname); + ui->originX->setValue (info.ox); + ui->originY->setValue (info.oy); + ui->width->setValue (info.lw); + ui->height->setValue (info.lh); + } + else + { ui->filename->setText (""); + ui->originX->setValue (0); + ui->originY->setValue (0); + ui->width->setValue (0.0f); + ui->height->setValue (0.0f); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str OverlayDialog::fpath() const +{ return ui->filename->text(); +} + +int OverlayDialog::ofsx() const +{ return ui->originX->value(); +} + +int OverlayDialog::ofsy() const +{ return ui->originY->value(); +} + +double OverlayDialog::lwidth() const +{ return ui->width->value(); +} + +double OverlayDialog::lheight() const +{ return ui->height->value(); +} + +int OverlayDialog::camera() const +{ return radioSwitch<int> (GL::ETopCamera, m_cameraArgs); +} + +void OverlayDialog::slot_fpath() +{ ui->filename->setText (QFileDialog::getOpenFileName (null, "Overlay image")); +} + +void OverlayDialog::slot_help() +{ showDocumentation (g_docs_overlays); +} + +void OverlayDialog::slot_dimensionsChanged() +{ bool enable = (ui->width->value() != 0) || (ui->height->value() != 0); + ui->buttonBox->button (QDialogButtonBox::Ok)->setEnabled (enable); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDrawPathDialog::LDrawPathDialog (const bool validDefault, QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f), + m_validDefault (validDefault) +{ ui = new Ui_LDPathUI; + ui->setupUi (this); + ui->status->setText ("---"); + + if (validDefault) + ui->heading->hide(); + else + { cancelButton()->setText ("Exit"); + cancelButton()->setIcon (getIcon ("exit")); + } + + okButton()->setEnabled (false); + + connect (ui->path, SIGNAL (textEdited (QString)), this, SLOT (slot_tryConfigure())); + connect (ui->searchButton, SIGNAL (clicked()), this, SLOT (slot_findPath())); + connect (ui->buttonBox, SIGNAL (rejected()), this, validDefault ? SLOT (reject()) : SLOT (slot_exit())); + connect (ui->buttonBox, SIGNAL (accepted()), this, SLOT (slot_accept())); + + setPath (io_ldpath); + + if (validDefault) + slot_tryConfigure(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDrawPathDialog::~LDrawPathDialog() +{ delete ui; +} + +QPushButton* LDrawPathDialog::okButton() +{ return ui->buttonBox->button (QDialogButtonBox::Ok); +} + +QPushButton* LDrawPathDialog::cancelButton() +{ return ui->buttonBox->button (QDialogButtonBox::Cancel); +} + +void LDrawPathDialog::setPath (str path) +{ ui->path->setText (path); +} + +str LDrawPathDialog::filename() const +{ return ui->path->text(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDrawPathDialog::slot_findPath() +{ str newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path"); + + if (newpath.length() > 0 && newpath != filename()) + { setPath (newpath); + slot_tryConfigure(); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDrawPathDialog::slot_exit() +{ exit (0); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDrawPathDialog::slot_tryConfigure() +{ if (LDPaths::tryConfigure (filename()) == false) + { ui->status->setText (fmt ("<span style=\"color:#700; \">%1</span>", LDPaths::getError())); + okButton()->setEnabled (false); + return; + } + + ui->status->setText ("<span style=\"color: #270; \">OK!</span>"); + okButton()->setEnabled (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDrawPathDialog::slot_accept() +{ Config::save(); + accept(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) +{ ui = new Ui_OpenProgressUI; + ui->setupUi (this); + ui->progressText->setText ("Parsing..."); + setNumLines (0); + m_Progress = 0; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +OpenProgressDialog::~OpenProgressDialog() +{ delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void OpenProgressDialog::setNumLines (int const& a) +{ m_NumLines = a; + ui->progressBar->setRange (0, getNumLines()); + updateValues(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void OpenProgressDialog::updateValues() +{ ui->progressText->setText (fmt ("Parsing... %1 / %2", getProgress(), getNumLines())); + ui->progressBar->setValue (getProgress()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void OpenProgressDialog::updateProgress (int progress) +{ setProgress (progress); + updateValues(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +ExtProgPathPrompt::ExtProgPathPrompt (str progName, QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f), + ui (new Ui_ExtProgPath) +{ + ui->setupUi (this); + str labelText = ui->m_label->text(); + labelText.replace ("<PROGRAM>", progName); + ui->m_label->setText (labelText); + connect (ui->m_findPath, SIGNAL (clicked (bool)), this, SLOT (findPath())); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +ExtProgPathPrompt::~ExtProgPathPrompt() +{ delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ExtProgPathPrompt::findPath() +{ str path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter); + + if (!path.isEmpty()) + ui->m_path->setText (path); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str ExtProgPathPrompt::getPath() const +{ return ui->m_path->text(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f) +{ Ui::AboutUI ui; + ui.setupUi (this); + ui.versionInfo->setText (fmt (tr ("LDForge %1"), fullVersionString())); + + QPushButton* mailButton = new QPushButton; + mailButton->setText ("Contact"); + mailButton->setIcon (getIcon ("mail")); + ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole); + connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail())); + + setWindowTitle ("About " APPNAME); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AboutDialog::slot_mail() +{ QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <slatenails64@gmail.com>?subject=LDForge")); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void bombBox (const str& message) +{ QDialog dlg (g_win); + Ui_BombBox ui; + + ui.setupUi (&dlg); + ui.m_text->setText (message); + ui.buttonBox->button (QDialogButtonBox::Close)->setText (QObject::tr ("Damn it")); + dlg.exec(); +}
--- a/src/dialogs.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,340 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QDialog> -#include <QLineEdit> -#include <QSpinBox> -#include <QDialogButtonBox> -#include <QFileDialog> -#include <QLabel> -#include <QPushButton> -#include <QBoxLayout> -#include <QGridLayout> -#include <QProgressBar> -#include <QCheckBox> -#include <QDesktopServices> -#include <QMessageBox> -#include <QUrl> -#include "dialogs.h" -#include "widgets.h" -#include "gui.h" -#include "gldraw.h" -#include "docs.h" -#include "document.h" -#include "dialogs.h" -#include "ui_overlay.h" -#include "ui_ldrawpath.h" -#include "ui_openprogress.h" -#include "ui_extprogpath.h" -#include "ui_about.h" -#include "ui_bombbox.h" -#include "moc_dialogs.cpp" - -extern const char* g_extProgPathFilter; -extern_cfg (String, io_ldpath); - -// ============================================================================= -// ----------------------------------------------------------------------------- -OverlayDialog::OverlayDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) -{ ui = new Ui_OverlayUI; - ui->setupUi (this); - - m_cameraArgs = - { { ui->top, GL::ETopCamera }, - { ui->bottom, GL::EBottomCamera }, - { ui->front, GL::EFrontCamera }, - { ui->back, GL::EBackCamera }, - { ui->left, GL::ELeftCamera }, - { ui->right, GL::ERightCamera } - }; - - GL::EFixedCamera cam = g_win->R()->camera(); - - if (cam == GL::EFreeCamera) - cam = GL::ETopCamera; - - connect (ui->width, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged())); - connect (ui->height, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged())); - connect (ui->buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_help())); - connect (ui->fileSearchButton, SIGNAL (clicked (bool)), this, SLOT (slot_fpath())); - - slot_dimensionsChanged(); - fillDefaults (cam); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -OverlayDialog::~OverlayDialog() -{ delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void OverlayDialog::fillDefaults (int newcam) -{ LDGLOverlay& info = g_win->R()->getOverlay (newcam); - radioDefault<int> (newcam, m_cameraArgs); - - if (info.img != null) - { ui->filename->setText (info.fname); - ui->originX->setValue (info.ox); - ui->originY->setValue (info.oy); - ui->width->setValue (info.lw); - ui->height->setValue (info.lh); - } - else - { ui->filename->setText (""); - ui->originX->setValue (0); - ui->originY->setValue (0); - ui->width->setValue (0.0f); - ui->height->setValue (0.0f); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str OverlayDialog::fpath() const -{ return ui->filename->text(); -} - -int OverlayDialog::ofsx() const -{ return ui->originX->value(); -} - -int OverlayDialog::ofsy() const -{ return ui->originY->value(); -} - -double OverlayDialog::lwidth() const -{ return ui->width->value(); -} - -double OverlayDialog::lheight() const -{ return ui->height->value(); -} - -int OverlayDialog::camera() const -{ return radioSwitch<int> (GL::ETopCamera, m_cameraArgs); -} - -void OverlayDialog::slot_fpath() -{ ui->filename->setText (QFileDialog::getOpenFileName (null, "Overlay image")); -} - -void OverlayDialog::slot_help() -{ showDocumentation (g_docs_overlays); -} - -void OverlayDialog::slot_dimensionsChanged() -{ bool enable = (ui->width->value() != 0) || (ui->height->value() != 0); - ui->buttonBox->button (QDialogButtonBox::Ok)->setEnabled (enable); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDrawPathDialog::LDrawPathDialog (const bool validDefault, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f), - m_validDefault (validDefault) -{ ui = new Ui_LDPathUI; - ui->setupUi (this); - ui->status->setText ("---"); - - if (validDefault) - ui->heading->hide(); - else - { cancelButton()->setText ("Exit"); - cancelButton()->setIcon (getIcon ("exit")); - } - - okButton()->setEnabled (false); - - connect (ui->path, SIGNAL (textEdited (QString)), this, SLOT (slot_tryConfigure())); - connect (ui->searchButton, SIGNAL (clicked()), this, SLOT (slot_findPath())); - connect (ui->buttonBox, SIGNAL (rejected()), this, validDefault ? SLOT (reject()) : SLOT (slot_exit())); - connect (ui->buttonBox, SIGNAL (accepted()), this, SLOT (slot_accept())); - - setPath (io_ldpath); - - if (validDefault) - slot_tryConfigure(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDrawPathDialog::~LDrawPathDialog() -{ delete ui; -} - -QPushButton* LDrawPathDialog::okButton() -{ return ui->buttonBox->button (QDialogButtonBox::Ok); -} - -QPushButton* LDrawPathDialog::cancelButton() -{ return ui->buttonBox->button (QDialogButtonBox::Cancel); -} - -void LDrawPathDialog::setPath (str path) -{ ui->path->setText (path); -} - -str LDrawPathDialog::filename() const -{ return ui->path->text(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDrawPathDialog::slot_findPath() -{ str newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path"); - - if (newpath.length() > 0 && newpath != filename()) - { setPath (newpath); - slot_tryConfigure(); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDrawPathDialog::slot_exit() -{ exit (0); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDrawPathDialog::slot_tryConfigure() -{ if (LDPaths::tryConfigure (filename()) == false) - { ui->status->setText (fmt ("<span style=\"color:#700; \">%1</span>", LDPaths::getError())); - okButton()->setEnabled (false); - return; - } - - ui->status->setText ("<span style=\"color: #270; \">OK!</span>"); - okButton()->setEnabled (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDrawPathDialog::slot_accept() -{ Config::save(); - accept(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) -{ ui = new Ui_OpenProgressUI; - ui->setupUi (this); - ui->progressText->setText ("Parsing..."); - setNumLines (0); - m_Progress = 0; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -OpenProgressDialog::~OpenProgressDialog() -{ delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void OpenProgressDialog::setNumLines (int const& a) -{ m_NumLines = a; - ui->progressBar->setRange (0, getNumLines()); - updateValues(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void OpenProgressDialog::updateValues() -{ ui->progressText->setText (fmt ("Parsing... %1 / %2", getProgress(), getNumLines())); - ui->progressBar->setValue (getProgress()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void OpenProgressDialog::updateProgress (int progress) -{ setProgress (progress); - updateValues(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -ExtProgPathPrompt::ExtProgPathPrompt (str progName, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f), - ui (new Ui_ExtProgPath) -{ - ui->setupUi (this); - str labelText = ui->m_label->text(); - labelText.replace ("<PROGRAM>", progName); - ui->m_label->setText (labelText); - connect (ui->m_findPath, SIGNAL (clicked (bool)), this, SLOT (findPath())); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -ExtProgPathPrompt::~ExtProgPathPrompt() -{ delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ExtProgPathPrompt::findPath() -{ str path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter); - - if (!path.isEmpty()) - ui->m_path->setText (path); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str ExtProgPathPrompt::getPath() const -{ return ui->m_path->text(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f) -{ Ui::AboutUI ui; - ui.setupUi (this); - ui.versionInfo->setText (fmt (tr ("LDForge %1"), fullVersionString())); - - QPushButton* mailButton = new QPushButton; - mailButton->setText ("Contact"); - mailButton->setIcon (getIcon ("mail")); - ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole); - connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail())); - - setWindowTitle ("About " APPNAME); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AboutDialog::slot_mail() -{ QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <slatenails64@gmail.com>?subject=LDForge")); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void bombBox (const str& message) -{ QDialog dlg (g_win); - Ui_BombBox ui; - - ui.setupUi (&dlg); - ui.m_text->setText (message); - ui.buttonBox->button (QDialogButtonBox::Close)->setText (QObject::tr ("Damn it")); - dlg.exec(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/docs.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,72 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QDialog> +#include <QTextEdit> +#include <QDialogButtonBox> +#include <QBoxLayout> +#include "main.h" +#include "types.h" + +// ============================================================================= +// ----------------------------------------------------------------------------- +class DocumentViewer : public QDialog +{ public: + explicit DocumentViewer (QWidget* parent = null, Qt::WindowFlags f = 0) : QDialog (parent, f) + { te_text = new QTextEdit (this); + te_text->setMinimumSize (QSize (400, 300)); + te_text->setReadOnly (true); + + QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Close); + QVBoxLayout* layout = new QVBoxLayout (this); + layout->addWidget (te_text); + layout->addWidget (bbx_buttons); + + connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); + } + + void setText (const char* text) + { te_text->setText (text); + } + + private: + QTextEdit* te_text; +}; + +const char* g_docs_overlays = + "<h1>Overlay images</h1><br />" + "<p>" APPNAME " supports drawing transparent images over the part model. This " + "can be used to have, for instance, a photo of the part overlaid on top of the " + "model and use it for drawing curves somewhat accurately.</p>" + "<p>For this purpose, a specific photo has to be taken of the part; it should " + "represent the part as true as possible to the actual camera used for editing. " + "The image should be taken from straight above the part, at as an orthogonal " + "angle as possible. It is recommended to take a lot of pictures this way and " + "select the best candidate.</p>" + "<p>The image should then be cropped with the knowledge of the image's LDU " + "dimensions in mind. The offset should then be identified in the image in pixels.</p>" + "<p>Finally, use the \"Set Overlay Image\" dialog and fill in the details. The " + "overlay image should then be ready for use."; + +// ============================================================================= +// ----------------------------------------------------------------------------- +void showDocumentation (const char* text) +{ DocumentViewer dlg; + dlg.setText (text); + dlg.exec(); +}
--- a/src/docs.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QDialog> -#include <QTextEdit> -#include <QDialogButtonBox> -#include <QBoxLayout> -#include "main.h" -#include "types.h" - -// ============================================================================= -// ----------------------------------------------------------------------------- -class DocumentViewer : public QDialog -{ public: - explicit DocumentViewer (QWidget* parent = null, Qt::WindowFlags f = 0) : QDialog (parent, f) - { te_text = new QTextEdit (this); - te_text->setMinimumSize (QSize (400, 300)); - te_text->setReadOnly (true); - - QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Close); - QVBoxLayout* layout = new QVBoxLayout (this); - layout->addWidget (te_text); - layout->addWidget (bbx_buttons); - - connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); - } - - void setText (const char* text) - { te_text->setText (text); - } - - private: - QTextEdit* te_text; -}; - -const char* g_docs_overlays = - "<h1>Overlay images</h1><br />" - "<p>" APPNAME " supports drawing transparent images over the part model. This " - "can be used to have, for instance, a photo of the part overlaid on top of the " - "model and use it for drawing curves somewhat accurately.</p>" - "<p>For this purpose, a specific photo has to be taken of the part; it should " - "represent the part as true as possible to the actual camera used for editing. " - "The image should be taken from straight above the part, at as an orthogonal " - "angle as possible. It is recommended to take a lot of pictures this way and " - "select the best candidate.</p>" - "<p>The image should then be cropped with the knowledge of the image's LDU " - "dimensions in mind. The offset should then be identified in the image in pixels.</p>" - "<p>Finally, use the \"Set Overlay Image\" dialog and fill in the details. The " - "overlay image should then be ready for use."; - -// ============================================================================= -// ----------------------------------------------------------------------------- -void showDocumentation (const char* text) -{ DocumentViewer dlg; - dlg.setText (text); - dlg.exec(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/document.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,1180 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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/>. + * ===================================================================== + * + * file.cpp: File I/O and management. + * - File loading, parsing, manipulation, saving, closing. + * - LDraw path verification. + */ + +#include <QMessageBox> +#include <QFileDialog> +#include <QDir> +#include <QApplication> +#include "main.h" +#include "config.h" +#include "document.h" +#include "misc.h" +#include "gui.h" +#include "history.h" +#include "dialogs.h" +#include "gldraw.h" +#include "moc_document.cpp" + +cfg (String, io_ldpath, ""); +cfg (List, io_recentfiles, {}); +extern_cfg (String, net_downloadpath); +extern_cfg (Bool, gl_logostuds); + +static bool g_loadingMainFile = false; +static const int g_MaxRecentFiles = 5; +static bool g_aborted = false; +static LDDocument* g_logoedStud = null; +static LDDocument* g_logoedStud2 = null; + +LDDocument* LDDocument::m_curdoc = null; + +// ============================================================================= +// ----------------------------------------------------------------------------- +namespace LDPaths +{ static str pathError; + + struct + { str LDConfigPath; + str partsPath, primsPath; + } pathInfo; + + void initPaths() + { if (!tryConfigure (io_ldpath)) + { LDrawPathDialog dlg (false); + + if (!dlg.exec()) + exit (0); + + io_ldpath = dlg.filename(); + } + } + + bool tryConfigure (str path) + { QDir dir; + + if (!dir.cd (path)) + { pathError = "Directory does not exist."; + return false; + } + + QStringList mustHave = { "LDConfig.ldr", "parts", "p" }; + QStringList contents = dir.entryList (mustHave); + + if (contents.size() != mustHave.size()) + { pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/."; + return false; + } + + pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path); + pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path); + pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path); + + return true; + } + + // Accessors + str getError() + { return pathError; + } + + str ldconfig() + { return pathInfo.LDConfigPath; + } + + str prims() + { return pathInfo.primsPath; + } + + str parts() + { return pathInfo.partsPath; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocument::LDDocument() +{ setImplicit (true); + setSavePosition (-1); + setListItem (null); + setHistory (new History); + m_History->setFile (this); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocument::~LDDocument() +{ // Clear everything from the model + for (LDObject* obj : getObjects()) + delete obj; + + // Clear the cache as well + for (LDObject* obj : getCache()) + delete obj; + + delete m_History; + + // Remove this file from the list of files + g_loadedFiles.removeOne (this); + + // If we just closed the current file, we need to set the current + // file as something else. + if (this == getCurrentDocument()) + { bool found = false; + + // Try find an explicitly loaded file - if we can't find one, + // we need to create a new file to switch to. + for (LDDocument* file : g_loadedFiles) + { if (!file->isImplicit()) + { LDDocument::setCurrent (file); + found = true; + break; + } + } + + if (!found) + newFile(); + } + + g_win->updateDocumentList(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocument* findDocument (str name) +{ for (LDDocument * file : g_loadedFiles) + if (!file->getName().isEmpty() && file->getShortName() == name) + return file; + + return null; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str dirname (str path) +{ long lastpos = path.lastIndexOf (DIRSLASH); + + if (lastpos > 0) + return path.left (lastpos); + +#ifndef _WIN32 + + if (path[0] == DIRSLASH_CHAR) + return DIRSLASH; + +#endif // _WIN32 + + return ""; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str basename (str path) +{ long lastpos = path.lastIndexOf (DIRSLASH); + + if (lastpos != -1) + return path.mid (lastpos + 1); + + return path; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +File* openLDrawFile (str relpath, bool subdirs) +{ log ("Opening %1...\n", relpath); + File* f = new File; + str fullPath; + + // LDraw models use Windows-style path separators. If we're not on Windows, + // replace the path separator now before opening any files. Qt expects + // forward-slashes as directory separators. +#ifndef WIN32 + relpath.replace ("\\", "/"); +#endif // WIN32 + + if (getCurrentDocument()) + { // First, try find the file in the current model's file path. We want a file + // in the immediate vicinity of the current model to override stock LDraw stuff. + str partpath = fmt ("%1" DIRSLASH "%2", dirname (getCurrentDocument()->getName()), relpath); + + if (f->open (partpath, File::Read)) + return f; + } + + if (f->open (relpath, File::Read)) + return f; + + // Try with just the LDraw path first + fullPath = fmt ("%1" DIRSLASH "%2", io_ldpath, relpath); + + if (f->open (fullPath, File::Read)) + return f; + + if (subdirs) + { // Look in sub-directories: parts and p. Also look in net_downloadpath, since that's + // where we download parts from the PT to. + for (const str& topdir : initlist<str> ({ io_ldpath, net_downloadpath })) + { for (const str& subdir : initlist<str> ({ "parts", "p" })) + { fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); + + if (f->open (fullPath, File::Read)) + return f; + } + } + } + + // Did not find the file. + log ("Could not find %1.\n", relpath); + delete f; + return null; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDFileLoader::start() +{ setDone (false); + setProgress (0); + setAborted (false); + + if (isOnForeground()) + { g_aborted = false; + + // Show a progress dialog if we're loading the main document.here so we can + // show progress updates and keep the WM posted that we're still here. + // Of course we cannot exec() the dialog because then the dialog would + // block. + dlg = new OpenProgressDialog (g_win); + dlg->setNumLines (getLines().size()); + dlg->setModal (true); + dlg->show(); + + // Connect the loader in so we can show updates + connect (this, SIGNAL (workDone()), dlg, SLOT (accept())); + connect (dlg, SIGNAL (rejected()), this, SLOT (abort())); + } + else + dlg = null; + + // Begin working + work (0); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDFileLoader::work (int i) +{ // User wishes to abort, so stop here now. + if (isAborted()) + { for (LDObject* obj : m_Objects) + delete obj; + + m_Objects.clear(); + setDone (true); + return; + } + + // Parse up to 300 lines per iteration + int max = i + 300; + + for (; i < max && i < (int) getLines().size(); ++i) + { str line = getLines()[i]; + + // Trim the trailing newline + QChar c; + + while (!line.isEmpty() && ((c = line[line.length() - 1]) == '\n' || c == '\r')) + line.chop (1); + + LDObject* obj = parseLine (line); + + // Check for parse errors and warn about tthem + if (obj->getType() == LDObject::Error) + { log ("Couldn't parse line #%1: %2", getProgress() + 1, static_cast<LDError*> (obj)->reason); + + if (getWarnings() != null) + (*getWarnings())++; + } + + m_Objects << obj; + setProgress (i); + + // If we have a dialog pointer, update the progress now + if (isOnForeground()) + dlg->updateProgress (i); + } + + // If we're done now, tell the environment we're done and stop. + if (i >= ((int) getLines().size()) - 1) + { emit workDone(); + setDone (true); + return; + } + + // Otherwise, continue, by recursing back. + if (!isDone()) + { // If we have a dialog to show progress output to, we cannot just call + // work() again immediately as the dialog needs some processor cycles as + // well. Thus, take a detour through the event loop by using the + // meta-object system. + // + // This terminates the loop here and control goes back to the function + // which called the file loader. It will keep processing the event loop + // until we're ready (see loadFileContents), thus the event loop will + // eventually catch the invokation we throw here and send us back. Though + // it's not technically recursion anymore, more like a for loop. :P + if (isOnForeground()) + QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i)); + else + work (i + 1); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDFileLoader::abort() +{ setAborted (true); + + if (isOnForeground()) + g_aborted = true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok) +{ QList<str> lines; + QList<LDObject*> objs; + + if (numWarnings) + *numWarnings = 0; + + // Read in the lines + for (str line : *f) + lines << line; + + LDFileLoader* loader = new LDFileLoader; + loader->setWarnings (numWarnings); + loader->setLines (lines); + loader->setOnForeground (g_loadingMainFile); + loader->start(); + + // After start() returns, if the loader isn't done yet, it's delaying + // its next iteration through the event loop. We need to catch this here + // by telling the event loop to tick, which will tick the file loader again. + // We keep doing this until the file loader is ready. + while (loader->isDone() == false) + qApp->processEvents(); + + // If we wanted the success value, supply that now + if (ok) + *ok = !loader->isAborted(); + + objs = loader->getObjects(); + return objs; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocument* openDocument (str path, bool search) +{ // Convert the file name to lowercase since some parts contain uppercase + // file names. I'll assume here that the library will always use lowercase + // file names for the actual parts.. + File* f; + + if (search) + f = openLDrawFile (path.toLower(), true); + else + { f = new File (path, File::Read); + + if (!*f) + { delete f; + return null; + } + } + + if (!f) + return null; + + LDDocument* load = new LDDocument; + load->setName (path); + + int numWarnings; + bool ok; + QList<LDObject*> objs = loadFileContents (f, &numWarnings, &ok); + + if (!ok) + return null; + + for (LDObject* obj : objs) + load->addObject (obj); + + delete f; + g_loadedFiles << load; + + if (g_loadingMainFile) + { LDDocument::setCurrent (load); + g_win->R()->setFile (load); + log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); + } + + return load; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool LDDocument::isSafeToClose() +{ typedef QMessageBox msgbox; + setlocale (LC_ALL, "C"); + + // If we have unsaved changes, warn and give the option of saving. + if (hasUnsavedChanges()) + { str message = fmt ("There are unsaved changes to %1. Should it be saved?", + (getName().length() > 0) ? getName() : "<anonymous>"); + + int button = msgbox::question (g_win, "Unsaved Changes", message, + (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); + + switch (button) + { case msgbox::Yes: + + // If we don't have a file path yet, we have to ask the user for one. + if (getName().length() == 0) + { str newpath = QFileDialog::getSaveFileName (g_win, "Save As", + getCurrentDocument()->getName(), "LDraw files (*.dat *.ldr)"); + + if (newpath.length() == 0) + return false; + + setName (newpath); + } + + if (!save()) + { message = fmt (QObject::tr ("Failed to save %1: %2\nDo you still want to close?"), + getName(), strerror (errno)); + + if (msgbox::critical (g_win, "Save Failure", message, + (msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No) + { return false; + } + } + + break; + + case msgbox::Cancel: + return false; + + default: + break; + } + } + + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void closeAll() +{ // Remove all loaded files and the objects they contain + QList<LDDocument*> files = g_loadedFiles; + +for (LDDocument * file : files) + delete file; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void newFile() +{ // Create a new anonymous file and set it to our current + LDDocument* f = new LDDocument; + f->setName (""); + f->setImplicit (false); + g_loadedFiles << f; + LDDocument::setCurrent (f); + LDDocument::closeInitialFile(); + g_win->R()->setFile (f); + g_win->doFullRefresh(); + g_win->updateTitle(); + g_win->updateActions(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void addRecentFile (str path) +{ alias rfiles = io_recentfiles.value; + int idx = rfiles.indexOf (path); + + // If this file already is in the list, pop it out. + if (idx != -1) + { if (rfiles.size() == 1) + return; // only recent file - abort and do nothing + + // Pop it out. + rfiles.removeAt (idx); + } + + // If there's too many recent files, drop one out. + while (rfiles.size() > (g_MaxRecentFiles - 1)) + rfiles.removeAt (0); + + // Add the file + rfiles << path; + + Config::save(); + g_win->updateRecentFilesMenu(); +} + +// ============================================================================= +// Open an LDraw file and set it as the main model +// ----------------------------------------------------------------------------- +void openMainFile (str path) +{ g_loadingMainFile = true; + LDDocument* file = openDocument (path, false); + + if (!file) + { // Loading failed, thus drop down to a new file since we + // closed everything prior. + newFile(); + + if (!g_aborted) + { // Tell the user loading failed. + setlocale (LC_ALL, "C"); + critical (fmt (QObject::tr ("Failed to open %1: %2"), path, strerror (errno))); + } + + g_loadingMainFile = false; + return; + } + + file->setImplicit (false); + + // If we have an anonymous, unchanged file open as the only open file + // (aside of the one we just opened), close it now. + LDDocument::closeInitialFile(); + + // Rebuild the object tree view now. + LDDocument::setCurrent (file); + g_win->doFullRefresh(); + + // Add it to the recent files list. + addRecentFile (path); + g_loadingMainFile = false; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool LDDocument::save (str savepath) +{ if (!savepath.length()) + savepath = getName(); + + File f (savepath, File::Write); + + if (!f) + return false; + + // If the second object in the list holds the file name, update that now. + // Only do this if the file is explicitly open. If it's saved into a directory + // called "s" or "48", prepend that into the name. + LDComment* fpathComment = null; + LDObject* first = getObject (1); + + if (!isImplicit() && first != null && first->getType() == LDObject::Comment) + { fpathComment = static_cast<LDComment*> (first); + + if (fpathComment->text.left (6) == "Name: ") + { str newname; + str dir = basename (dirname (savepath)); + + if (dir == "s" || dir == "48") + newname = dir + "\\"; + + newname += basename (savepath); + fpathComment->text = fmt ("Name: %1", newname); + g_win->buildObjList(); + } + } + + // File is open, now save the model to it. Note that LDraw requires files to + // have DOS line endings, so we terminate the lines with \r\n. + for (LDObject* obj : getObjects()) + f.write (obj->raw() + "\r\n"); + + // File is saved, now clean up. + f.close(); + + // We have successfully saved, update the save position now. + setSavePosition (getHistory()->getPosition()); + setName (savepath); + + g_win->updateDocumentListItem (this); + g_win->updateTitle(); + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +#define CHECK_TOKEN_COUNT(N) \ + if (tokens.size() != N) \ + return new LDError (line, "Bad amount of tokens"); + +#define CHECK_TOKEN_NUMBERS(MIN, MAX) \ + for (int i = MIN; i <= MAX; ++i) \ + if (!numeric (tokens[i])) \ + return new LDError (line, fmt ("Token #%1 was `%2`, expected a number", (i + 1), tokens[i])); + +// ============================================================================= +// ----------------------------------------------------------------------------- +static vertex parseVertex (QStringList& s, const int n) +{ vertex v; + + for_axes (ax) + v[ax] = s[n + ax].toDouble(); + + return v; +} + +// ============================================================================= +// This is the LDraw code parser function. It takes in a string containing LDraw +// code and returns the object parsed from it. parseLine never returns null, +// the object will be LDError if it could not be parsed properly. +// ----------------------------------------------------------------------------- +LDObject* parseLine (str line) +{ QStringList tokens = line.split (" ", str::SkipEmptyParts); + + if (tokens.size() <= 0) + { // Line was empty, or only consisted of whitespace + return new LDEmpty; + } + + if (tokens[0].length() != 1 || tokens[0][0].isDigit() == false) + return new LDError (line, "Illogical line code"); + + int num = tokens[0][0].digitValue(); + + switch (num) + { case 0: + { // Comment + str comm = line.mid (line.indexOf ("0") + 1).simplified(); + + // Handle BFC statements + if (tokens.size() > 2 && tokens[1] == "BFC") + { for (int i = 0; i < LDBFC::NumStatements; ++i) + if (comm == fmt ("BFC %1", LDBFC::statements [i])) + return new LDBFC ( (LDBFC::Type) i); + + // MLCAD is notorious for stuffing these statements in parts it + // creates. The above block only handles valid statements, so we + // need to handle MLCAD-style invertnext, clip and noclip separately. + struct + { str a; + LDBFC::Type b; + } BFCData[] = + { { "INVERTNEXT", LDBFC::InvertNext }, + { "NOCLIP", LDBFC::NoClip }, + { "CLIP", LDBFC::Clip } + }; + + for (const auto& i : BFCData) + if (comm == "BFC CERTIFY " + i.a) + return new LDBFC (i.b); + } + + if (tokens.size() > 2 && tokens[1] == "!LDFORGE") + { // Handle LDForge-specific types, they're embedded into comments too + if (tokens[2] == "VERTEX") + { // Vertex (0 !LDFORGE VERTEX) + CHECK_TOKEN_COUNT (7) + CHECK_TOKEN_NUMBERS (3, 6) + + LDVertex* obj = new LDVertex; + obj->setColor (tokens[3].toLong()); + + for_axes (ax) + obj->pos[ax] = tokens[4 + ax].toDouble(); // 4 - 6 + + return obj; + } elif (tokens[2] == "OVERLAY") + { CHECK_TOKEN_COUNT (9); + CHECK_TOKEN_NUMBERS (5, 8) + + LDOverlay* obj = new LDOverlay; + obj->setFileName (tokens[3]); + obj->setCamera (tokens[4].toLong()); + obj->setX (tokens[5].toLong()); + obj->setY (tokens[6].toLong()); + obj->setWidth (tokens[7].toLong()); + obj->setHeight (tokens[8].toLong()); + return obj; + } + } + + // Just a regular comment: + LDComment* obj = new LDComment; + obj->text = comm; + return obj; + } + + case 1: + { // Subfile + CHECK_TOKEN_COUNT (15) + CHECK_TOKEN_NUMBERS (1, 13) + + // Try open the file. Disable g_loadingMainFile temporarily since we're + // not loading the main file now, but the subfile in question. + bool tmp = g_loadingMainFile; + g_loadingMainFile = false; + LDDocument* load = getDocument (tokens[14]); + g_loadingMainFile = tmp; + + // If we cannot open the file, mark it an error + if (!load) + { LDError* obj = new LDError (line, fmt ("Could not open %1", tokens[14])); + obj->setFileReferenced (tokens[14]); + return obj; + } + + LDSubfile* obj = new LDSubfile; + obj->setColor (tokens[1].toLong()); + obj->setPosition (parseVertex (tokens, 2)); // 2 - 4 + + matrix transform; + + for (int i = 0; i < 9; ++i) + transform[i] = tokens[i + 5].toDouble(); // 5 - 13 + + obj->setTransform (transform); + obj->setFileInfo (load); + return obj; + } + + case 2: + { CHECK_TOKEN_COUNT (8) + CHECK_TOKEN_NUMBERS (1, 7) + + // Line + LDLine* obj = new LDLine; + obj->setColor (tokens[1].toLong()); + + for (int i = 0; i < 2; ++i) + obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 7 + + return obj; + } + + case 3: + { CHECK_TOKEN_COUNT (11) + CHECK_TOKEN_NUMBERS (1, 10) + + // Triangle + LDTriangle* obj = new LDTriangle; + obj->setColor (tokens[1].toLong()); + + for (int i = 0; i < 3; ++i) + obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 10 + + return obj; + } + + case 4: + case 5: + { CHECK_TOKEN_COUNT (14) + CHECK_TOKEN_NUMBERS (1, 13) + + // Quadrilateral / Conditional line + LDObject* obj; + + if (num == 4) + obj = new LDQuad; + else + obj = new LDCondLine; + + obj->setColor (tokens[1].toLong()); + + for (int i = 0; i < 4; ++i) + obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 13 + + return obj; + } + + default: // Strange line we couldn't parse + return new LDError (line, "Unknown line code number"); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocument* getDocument (str filename) +{ // Try find the file in the list of loaded files + LDDocument* doc = findDocument (filename); + + // If it's not loaded, try open it + if (!doc) + doc = openDocument (filename, true); + + return doc; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void reloadAllSubfiles() +{ if (!getCurrentDocument()) + return; + + g_loadedFiles.clear(); + g_loadedFiles << getCurrentDocument(); + + // Go through all objects in the current file and reload the subfiles + for (LDObject* obj : getCurrentDocument()->getObjects()) + { if (obj->getType() == LDObject::Subfile) + { LDSubfile* ref = static_cast<LDSubfile*> (obj); + LDDocument* fileInfo = getDocument (ref->getFileInfo()->getName()); + + if (fileInfo) + ref->setFileInfo (fileInfo); + else + ref->replace (new LDError (ref->raw(), "Could not open referred file")); + } + + // Reparse gibberish files. It could be that they are invalid because + // of loading errors. Circumstances may be different now. + if (obj->getType() == LDObject::Error) + obj->replace (parseLine (static_cast<LDError*> (obj)->contents)); + } + + // Close all files left unused + LDDocument::closeUnused(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int LDDocument::addObject (LDObject* obj) +{ getHistory()->add (new AddHistory (getObjects().size(), obj)); + m_Objects << obj; + + if (obj->getType() == LDObject::Vertex) + m_Vertices << obj; + + obj->setFile (this); + return getObjectCount() - 1; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::addObjects (const QList<LDObject*> objs) +{ for (LDObject * obj : objs) + if (obj) + addObject (obj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::insertObj (int pos, LDObject* obj) +{ getHistory()->add (new AddHistory (pos, obj)); + m_Objects.insert (pos, obj); + obj->setFile (this); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::forgetObject (LDObject* obj) +{ int idx = obj->getIndex(); + getHistory()->add (new DelHistory (idx, obj)); + m_Objects.removeAt (idx); + obj->setFile (null); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool safeToCloseAll() +{ for (LDDocument* f : g_loadedFiles) + if (!f->isSafeToClose()) + return false; + + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::setObject (int idx, LDObject* obj) +{ assert (idx < m_Objects.size()); + + // Mark this change to history + str oldcode = getObject (idx)->raw(); + str newcode = obj->raw(); + *m_History << new EditHistory (idx, oldcode, newcode); + + obj->setFile (this); + m_Objects[idx] = obj; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static QList<LDDocument*> getFilesUsed (LDDocument* node) +{ QList<LDDocument*> filesUsed; + + for (LDObject* obj : node->getObjects()) + { if (obj->getType() != LDObject::Subfile) + continue; + + LDSubfile* ref = static_cast<LDSubfile*> (obj); + filesUsed << ref->getFileInfo(); + filesUsed << getFilesUsed (ref->getFileInfo()); + } + + return filesUsed; +} + +// ============================================================================= +// Find out which files are unused and close them. +// ----------------------------------------------------------------------------- +void LDDocument::closeUnused() +{ QList<LDDocument*> filesUsed = getFilesUsed (getCurrentDocument()); + + // Anything that's explicitly opened must not be closed + for (LDDocument* file : g_loadedFiles) + if (!file->isImplicit()) + filesUsed << file; + + // Remove duplicated entries + removeDuplicates (filesUsed); + + // Close all open files that aren't in filesUsed + for (LDDocument* file : g_loadedFiles) + { bool isused = false; + + for (LDDocument* usedFile : filesUsed) + { if (file == usedFile) + { isused = true; + break; + } + } + + if (!isused) + delete file; + } + + g_loadedFiles.clear(); + g_loadedFiles << filesUsed; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject* LDDocument::getObject (int pos) const +{ if (m_Objects.size() <= pos) + return null; + + return m_Objects[pos]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int LDDocument::getObjectCount() const +{ return getObjects().size(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool LDDocument::hasUnsavedChanges() const +{ return !isImplicit() && getHistory()->getPosition() != getSavePosition(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDDocument::getShortName() +{ if (!getName().isEmpty()) + return basename (getName()); + + if (!getDefaultName().isEmpty()) + return getDefaultName(); + + return tr ("<anonymous>"); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QList<LDObject*> LDDocument::inlineContents (LDSubfile::InlineFlags flags) +{ // Possibly substitute with logoed studs: + // stud.dat -> stud-logo.dat + // stud2.dat -> stud-logo2.dat + if (gl_logostuds && (flags & LDSubfile::RendererInline)) + { if (getName() == "stud.dat" && g_logoedStud) + return g_logoedStud->inlineContents (flags); + + elif (getName() == "stud2.dat" && g_logoedStud2) + return g_logoedStud2->inlineContents (flags); + } + + QList<LDObject*> objs, objcache; + + bool deep = flags & LDSubfile::DeepInline, + doCache = flags & LDSubfile::CacheInline; + + // If we have this cached, just clone that + if (deep && getCache().size()) + { for (LDObject* obj : getCache()) + objs << obj->clone(); + } + else + { if (!deep) + doCache = false; + + for (LDObject* obj : getObjects()) + { // Skip those without scemantic meaning + if (!obj->isScemantic()) + continue; + + // Got another sub-file reference, inline it if we're deep-inlining. If not, + // just add it into the objects normally. Also, we only cache immediate + // subfiles and this is not one. Yay, recursion! + if (deep && obj->getType() == LDObject::Subfile) + { LDSubfile* ref = static_cast<LDSubfile*> (obj); + + // We only want to cache immediate subfiles, so shed the caching + // flag when recursing deeper in hierarchy. + QList<LDObject*> otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline)); + + for (LDObject * otherobj : otherobjs) + { // Cache this object, if desired + if (doCache) + objcache << otherobj->clone(); + + objs << otherobj; + } + } + else + { if (doCache) + objcache << obj->clone(); + + objs << obj->clone(); + } + } + + if (doCache) + setCache (objcache); + } + + return objs; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocument* LDDocument::current() +{ return m_curdoc; +} + +// ============================================================================= +// Sets the given file as the current one on display. At some point in time this +// was an operation completely unheard of. ;) +// +// FIXME: f can be temporarily null. This probably should not be the case. +// ----------------------------------------------------------------------------- +void LDDocument::setCurrent (LDDocument* f) +{ // Implicit files were loaded for caching purposes and must never be set + // current. + if (f && f->isImplicit()) + return; + + m_curdoc = f; + + if (g_win && f) + { // A ton of stuff needs to be updated + g_win->updateDocumentListItem (f); + g_win->buildObjList(); + g_win->updateTitle(); + g_win->R()->setFile (f); + g_win->R()->resetAllAngles(); + g_win->R()->repaint(); + + log ("Changed file to %1", f->getShortName()); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int LDDocument::countExplicitFiles() +{ int count = 0; + + for (LDDocument* f : g_loadedFiles) + if (f->isImplicit() == false) + count++; + + return count; +} + +// ============================================================================= +// This little beauty closes the initial file that was open at first when opening +// a new file over it. +// ----------------------------------------------------------------------------- +void LDDocument::closeInitialFile() +{ if ( + countExplicitFiles() == 2 && + g_loadedFiles[0]->getName() == "" && + !g_loadedFiles[0]->hasUnsavedChanges() + ) + delete g_loadedFiles[0]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void loadLogoedStuds() +{ log ("Loading logoed studs...\n"); + + delete g_logoedStud; + delete g_logoedStud2; + + g_logoedStud = openDocument ("stud-logo.dat", true); + g_logoedStud2 = openDocument ("stud2-logo.dat", true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::addToSelection (LDObject* obj) // [protected] +{ if (obj->isSelected()) + return; + + assert (obj->getFile() == this); + m_sel << obj; + obj->setSelected (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::removeFromSelection (LDObject* obj) // [protected] +{ if (!obj->isSelected()) + return; + + assert (obj->getFile() == this); + m_sel.removeOne (obj); + obj->setSelected (false); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::clearSelection() +{ for (LDObject* obj : m_sel) + removeFromSelection (obj); + + assert (m_sel.isEmpty()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +const QList<LDObject*>& LDDocument::getSelection() const +{ return m_sel; +} \ No newline at end of file
--- a/src/document.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1186 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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/>. - * ===================================================================== - * - * file.cpp: File I/O and management. - * - File loading, parsing, manipulation, saving, closing. - * - LDraw path verification. - */ - -#include <QMessageBox> -#include <QFileDialog> -#include <QDir> -#include <QApplication> -#include "main.h" -#include "config.h" -#include "document.h" -#include "misc.h" -#include "gui.h" -#include "history.h" -#include "dialogs.h" -#include "gldraw.h" -#include "moc_document.cpp" - -cfg (String, io_ldpath, ""); -cfg (List, io_recentfiles, {}); -extern_cfg (String, net_downloadpath); -extern_cfg (Bool, gl_logostuds); - -static bool g_loadingMainFile = false; -static const int g_MaxRecentFiles = 5; -static bool g_aborted = false; -static LDDocument* g_logoedStud = null; -static LDDocument* g_logoedStud2 = null; - -LDDocument* LDDocument::m_curdoc = null; - -// ============================================================================= -// ----------------------------------------------------------------------------- -namespace LDPaths -{ static str pathError; - - struct - { str LDConfigPath; - str partsPath, primsPath; - } pathInfo; - - void initPaths() - { if (!tryConfigure (io_ldpath)) - { LDrawPathDialog dlg (false); - - if (!dlg.exec()) - exit (0); - - io_ldpath = dlg.filename(); - } - } - - bool tryConfigure (str path) - { QDir dir; - - if (!dir.cd (path)) - { pathError = "Directory does not exist."; - return false; - } - - QStringList mustHave = { "LDConfig.ldr", "parts", "p" }; - QStringList contents = dir.entryList (mustHave); - - if (contents.size() != mustHave.size()) - { pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/."; - return false; - } - - pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path); - pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path); - pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path); - - return true; - } - - // Accessors - str getError() - { return pathError; - } - - str ldconfig() - { return pathInfo.LDConfigPath; - } - - str prims() - { return pathInfo.primsPath; - } - - str parts() - { return pathInfo.partsPath; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument::LDDocument() -{ setImplicit (true); - setSavePosition (-1); - setListItem (null); - setHistory (new History); - m_History->setFile (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument::~LDDocument() -{ // Clear everything from the model - for (LDObject* obj : getObjects()) - delete obj; - - // Clear the cache as well - for (LDObject* obj : getCache()) - delete obj; - - delete m_History; - - // Remove this file from the list of files - g_loadedFiles.removeOne (this); - - // If we just closed the current file, we need to set the current - // file as something else. - if (this == getCurrentDocument()) - { bool found = false; - - // Try find an explicitly loaded file - if we can't find one, - // we need to create a new file to switch to. - for (LDDocument* file : g_loadedFiles) - { if (!file->isImplicit()) - { LDDocument::setCurrent (file); - found = true; - break; - } - } - - if (!found) - newFile(); - } - - g_win->updateDocumentList(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument* findDocument (str name) -{ for (LDDocument * file : g_loadedFiles) - if (!file->getName().isEmpty() && file->getShortName() == name) - return file; - - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str dirname (str path) -{ long lastpos = path.lastIndexOf (DIRSLASH); - - if (lastpos > 0) - return path.left (lastpos); - -#ifndef _WIN32 - - if (path[0] == DIRSLASH_CHAR) - return DIRSLASH; - -#endif // _WIN32 - - return ""; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str basename (str path) -{ long lastpos = path.lastIndexOf (DIRSLASH); - - if (lastpos != -1) - return path.mid (lastpos + 1); - - return path; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File* openLDrawFile (str relpath, bool subdirs) -{ log ("Opening %1...\n", relpath); - File* f = new File; - str fullPath; - - // LDraw models use Windows-style path separators. If we're not on Windows, - // replace the path separator now before opening any files. -#ifndef WIN32 - relpath.replace ("\\", "/"); -#endif // WIN32 - - if (getCurrentDocument()) - { // First, try find the file in the current model's file path. We want a file - // in the immediate vicinity of the current model to override stock LDraw stuff. - str partpath = fmt ("%1" DIRSLASH "%2", dirname (getCurrentDocument()->getName()), relpath); - - if (f->open (partpath, File::Read)) - return f; - } - - if (f->open (relpath, File::Read)) - return f; - - // Try with just the LDraw path first - fullPath = fmt ("%1" DIRSLASH "%2", io_ldpath, relpath); - - if (f->open (fullPath, File::Read)) - return f; - - if (subdirs) - { // Look in sub-directories: parts and p. Also look in net_downloadpath, since that's - // where we download parts from the PT to. - for (const str& topdir : initlist<str> ({ io_ldpath, net_downloadpath })) - { for (const str& subdir : initlist<str> ({ "parts", "p" })) - { fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); - - if (f->open (fullPath, File::Read)) - return f; - } - } - } - - // Did not find the file. - log ("Could not find %1.\n", relpath); - delete f; - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFileLoader::start() -{ setDone (false); - setProgress (0); - setAborted (false); - - if (isOnForeground()) - { g_aborted = false; - - // Show a progress dialog if we're loading the main document.here so we can - // show progress updates and keep the WM posted that we're still here. - // Of course we cannot exec() the dialog because then the dialog would - // block. - dlg = new OpenProgressDialog (g_win); - dlg->setNumLines (getLines().size()); - dlg->setModal (true); - dlg->show(); - - // Connect the loader in so we can show updates - connect (this, SIGNAL (workDone()), dlg, SLOT (accept())); - connect (dlg, SIGNAL (rejected()), this, SLOT (abort())); - } - else - dlg = null; - - // Begin working - work (0); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFileLoader::work (int i) -{ // User wishes to abort, so stop here now. - if (isAborted()) - { for (LDObject* obj : m_Objects) - delete obj; - - m_Objects.clear(); - setDone (true); - return; - } - - // Parse up to 300 lines per iteration - int max = i + 300; - - for (; i < max && i < (int) getLines().size(); ++i) - { str line = getLines()[i]; - - // Trim the trailing newline - QChar c; - - while (!line.isEmpty() && ((c = line[line.length() - 1]) == '\n' || c == '\r')) - line.chop (1); - - LDObject* obj = parseLine (line); - - // Check for parse errors and warn about tthem - if (obj->getType() == LDObject::Error) - { log ("Couldn't parse line #%1: %2", getProgress() + 1, static_cast<LDError*> (obj)->reason); - - if (getWarnings() != null) - (*getWarnings())++; - } - - m_Objects << obj; - setProgress (i); - - // If we have a dialog pointer, update the progress now - if (isOnForeground()) - dlg->updateProgress (i); - } - - // If we're done now, tell the environment we're done and stop. - if (i >= ((int) getLines().size()) - 1) - { emit workDone(); - setDone (true); - return; - } - - // Otherwise, continue, by recursing back. - if (!isDone()) - { // If we have a dialog to show progress output to, we cannot just call - // work() again immediately as the dialog needs some processor cycles as - // well. Thus, take a detour through the event loop by using the - // meta-object system. - // - // This terminates the loop here and control goes back to the function - // which called the file loader. It will keep processing the event loop - // until we're ready (see loadFileContents), thus the event loop will - // eventually catch the invokation we throw here and send us back. Though - // it's not technically recursion anymore, more like a for loop. :P - if (isOnForeground()) - QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i)); - else - work (i + 1); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFileLoader::abort() -{ setAborted (true); - - if (isOnForeground()) - g_aborted = true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok) -{ QList<str> lines; - QList<LDObject*> objs; - - if (numWarnings) - *numWarnings = 0; - - // Calculate the amount of lines - for (str line : *f) - lines << line; - - f->rewind(); - - LDFileLoader* loader = new LDFileLoader; - loader->setWarnings (numWarnings); - loader->setLines (lines); - loader->setOnForeground (g_loadingMainFile); - loader->start(); - - // After start() returns, if the loader isn't done yet, it's delaying - // its next iteration through the event loop. We need to catch this here - // by telling the event loop to tick, which will tick the file loader again. - // We keep doing this until the file loader is ready. - while (loader->isDone() == false) - qApp->processEvents(); - - // If we wanted the success value, supply that now - if (ok) - *ok = !loader->isAborted(); - - objs = loader->getObjects(); - return objs; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument* openDocument (str path, bool search) -{ // Convert the file name to lowercase since some parts contain uppercase - // file names. I'll assume here that the library will always use lowercase - // file names for the actual parts.. - File* f; - - if (search) - f = openLDrawFile (path.toLower(), true); - else - { f = new File (path, File::Read); - - if (!*f) - { delete f; - return null; - } - } - - if (!f) - return null; - - LDDocument* load = new LDDocument; - load->setName (path); - - int numWarnings; - bool ok; - QList<LDObject*> objs = loadFileContents (f, &numWarnings, &ok); - - if (!ok) - return null; - - for (LDObject* obj : objs) - load->addObject (obj); - - delete f; - g_loadedFiles << load; - - if (g_loadingMainFile) - { LDDocument::setCurrent (load); - g_win->R()->setFile (load); - log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); - } - - return load; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDDocument::isSafeToClose() -{ typedef QMessageBox msgbox; - setlocale (LC_ALL, "C"); - - // If we have unsaved changes, warn and give the option of saving. - if (hasUnsavedChanges()) - { str message = fmt ("There are unsaved changes to %1. Should it be saved?", - (getName().length() > 0) ? getName() : "<anonymous>"); - - int button = msgbox::question (g_win, "Unsaved Changes", message, - (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); - - switch (button) - { case msgbox::Yes: - - // If we don't have a file path yet, we have to ask the user for one. - if (getName().length() == 0) - { str newpath = QFileDialog::getSaveFileName (g_win, "Save As", - getCurrentDocument()->getName(), "LDraw files (*.dat *.ldr)"); - - if (newpath.length() == 0) - return false; - - setName (newpath); - } - - if (!save()) - { message = fmt (QObject::tr ("Failed to save %1: %2\nDo you still want to close?"), - getName(), strerror (errno)); - - if (msgbox::critical (g_win, "Save Failure", message, - (msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No) - { return false; - } - } - - break; - - case msgbox::Cancel: - return false; - - default: - break; - } - } - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void closeAll() -{ // Remove all loaded files and the objects they contain - QList<LDDocument*> files = g_loadedFiles; - -for (LDDocument * file : files) - delete file; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void newFile() -{ // Create a new anonymous file and set it to our current - LDDocument* f = new LDDocument; - f->setName (""); - f->setImplicit (false); - g_loadedFiles << f; - LDDocument::setCurrent (f); - LDDocument::closeInitialFile(); - g_win->R()->setFile (f); - g_win->doFullRefresh(); - g_win->updateTitle(); - g_win->updateActions(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void addRecentFile (str path) -{ alias rfiles = io_recentfiles.value; - int idx = rfiles.indexOf (path); - - // If this file already is in the list, pop it out. - if (idx != -1) - { if (rfiles.size() == 1) - return; // only recent file - abort and do nothing - - // Pop it out. - rfiles.removeAt (idx); - } - - // If there's too many recent files, drop one out. - while (rfiles.size() > (g_MaxRecentFiles - 1)) - rfiles.removeAt (0); - - // Add the file - rfiles << path; - - Config::save(); - g_win->updateRecentFilesMenu(); -} - -// ============================================================================= -// Open an LDraw file and set it as the main model -// ----------------------------------------------------------------------------- -void openMainFile (str path) -{ g_loadingMainFile = true; - LDDocument* file = openDocument (path, false); - - if (!file) - { // Loading failed, thus drop down to a new file since we - // closed everything prior. - newFile(); - - if (!g_aborted) - { // Tell the user loading failed. - setlocale (LC_ALL, "C"); - critical (fmt (QObject::tr ("Failed to open %1: %2"), path, strerror (errno))); - } - - g_loadingMainFile = false; - return; - } - - file->setImplicit (false); - - // If we have an anonymous, unchanged file open as the only open file - // (aside of the one we just opened), close it now. - LDDocument::closeInitialFile(); - - // Rebuild the object tree view now. - LDDocument::setCurrent (file); - g_win->doFullRefresh(); - - // Add it to the recent files list. - addRecentFile (path); - g_loadingMainFile = false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDDocument::save (str savepath) -{ if (!savepath.length()) - savepath = getName(); - - File f (savepath, File::Write); - - if (!f) - return false; - - // If the second object in the list holds the file name, update that now. - // Only do this if the file is explicitly open. If it's saved into a directory - // called "s" or "48", prepend that into the name. - LDComment* fpathComment = null; - LDObject* first = getObject (1); - - if (!isImplicit() && first != null && first->getType() == LDObject::Comment) - { fpathComment = static_cast<LDComment*> (first); - - if (fpathComment->text.left (6) == "Name: ") - { str newname; - str dir = basename (dirname (savepath)); - - if (dir == "s" || dir == "48") - newname = dir + "\\"; - - newname += basename (savepath); - fpathComment->text = fmt ("Name: %1", newname); - g_win->buildObjList(); - } - } - - // File is open, now save the model to it. Note that LDraw requires files to - // have DOS line endings, so we terminate the lines with \r\n. - for (LDObject* obj : getObjects()) - f.write (obj->raw() + "\r\n"); - - // File is saved, now clean up. - f.close(); - - // We have successfully saved, update the save position now. - setSavePosition (getHistory()->getPosition()); - setName (savepath); - - g_win->updateDocumentListItem (this); - g_win->updateTitle(); - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -#define CHECK_TOKEN_COUNT(N) \ - if (tokens.size() != N) \ - return new LDError (line, "Bad amount of tokens"); - -#define CHECK_TOKEN_NUMBERS(MIN, MAX) \ - for (int i = MIN; i <= MAX; ++i) \ - if (!numeric (tokens[i])) \ - return new LDError (line, fmt ("Token #%1 was `%2`, expected a number", (i + 1), tokens[i])); - -// ============================================================================= -// ----------------------------------------------------------------------------- -static vertex parseVertex (QStringList& s, const int n) -{ vertex v; - - for (const Axis ax : g_Axes) - v[ax] = s[n + ax].toDouble(); - - return v; -} - -// ============================================================================= -// This is the LDraw code parser function. It takes in a string containing LDraw -// code and returns the object parsed from it. parseLine never returns null, -// the object will be LDError if it could not be parsed properly. -// ----------------------------------------------------------------------------- -LDObject* parseLine (str line) -{ QStringList tokens = line.split (" ", str::SkipEmptyParts); - - if (tokens.size() <= 0) - { // Line was empty, or only consisted of whitespace - return new LDEmpty; - } - - if (tokens[0].length() != 1 || tokens[0][0].isDigit() == false) - return new LDError (line, "Illogical line code"); - - int num = tokens[0][0].digitValue(); - - switch (num) - { case 0: - { // Comment - str comm = line.mid (line.indexOf ("0") + 1); - - // Remove any leading whitespace - while (comm[0] == ' ') - comm.remove (0, 1); - - // Handle BFC statements - if (tokens.size() > 2 && tokens[1] == "BFC") - { for (int i = 0; i < LDBFC::NumStatements; ++i) - if (comm == fmt ("BFC %1", LDBFC::statements [i])) - return new LDBFC ( (LDBFC::Type) i); - - // MLCAD is notorious for stuffing these statements in parts it - // creates. The above block only handles valid statements, so we - // need to handle MLCAD-style invertnext, clip and noclip separately. - struct - { const char* a; - LDBFC::Type b; - } BFCData[] = - { { "INVERTNEXT", LDBFC::InvertNext }, - { "NOCLIP", LDBFC::NoClip }, - { "CLIP", LDBFC::Clip } - }; - - for (const auto & i : BFCData) - if (comm == fmt ("BFC CERTIFY %1", i.a)) - return new LDBFC (i.b); - } - - if (tokens.size() > 2 && tokens[1] == "!LDFORGE") - { // Handle LDForge-specific types, they're embedded into comments too - if (tokens[2] == "VERTEX") - { // Vertex (0 !LDFORGE VERTEX) - CHECK_TOKEN_COUNT (7) - CHECK_TOKEN_NUMBERS (3, 6) - - LDVertex* obj = new LDVertex; - obj->setColor (tokens[3].toLong()); - - for (const Axis ax : g_Axes) - obj->pos[ax] = tokens[4 + ax].toDouble(); // 4 - 6 - - return obj; - } elif (tokens[2] == "OVERLAY") - - { CHECK_TOKEN_COUNT (9); - CHECK_TOKEN_NUMBERS (5, 8) - - LDOverlay* obj = new LDOverlay; - obj->setFileName (tokens[3]); - obj->setCamera (tokens[4].toLong()); - obj->setX (tokens[5].toLong()); - obj->setY (tokens[6].toLong()); - obj->setWidth (tokens[7].toLong()); - obj->setHeight (tokens[8].toLong()); - return obj; - } - } - - // Just a regular comment: - LDComment* obj = new LDComment; - obj->text = comm; - return obj; - } - - case 1: - { // Subfile - CHECK_TOKEN_COUNT (15) - CHECK_TOKEN_NUMBERS (1, 13) - - // Try open the file. Disable g_loadingMainFile temporarily since we're - // not loading the main file now, but the subfile in question. - bool tmp = g_loadingMainFile; - g_loadingMainFile = false; - LDDocument* load = getDocument (tokens[14]); - g_loadingMainFile = tmp; - - // If we cannot open the file, mark it an error - if (!load) - { LDError* obj = new LDError (line, fmt ("Could not open %1", tokens[14])); - obj->setFileReferenced (tokens[14]); - return obj; - } - - LDSubfile* obj = new LDSubfile; - obj->setColor (tokens[1].toLong()); - obj->setPosition (parseVertex (tokens, 2)); // 2 - 4 - - matrix transform; - - for (int i = 0; i < 9; ++i) - transform[i] = tokens[i + 5].toDouble(); // 5 - 13 - - obj->setTransform (transform); - obj->setFileInfo (load); - return obj; - } - - case 2: - { CHECK_TOKEN_COUNT (8) - CHECK_TOKEN_NUMBERS (1, 7) - - // Line - LDLine* obj = new LDLine; - obj->setColor (tokens[1].toLong()); - - for (int i = 0; i < 2; ++i) - obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 7 - - return obj; - } - - case 3: - { CHECK_TOKEN_COUNT (11) - CHECK_TOKEN_NUMBERS (1, 10) - - // Triangle - LDTriangle* obj = new LDTriangle; - obj->setColor (tokens[1].toLong()); - - for (int i = 0; i < 3; ++i) - obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 10 - - return obj; - } - - case 4: - case 5: - { CHECK_TOKEN_COUNT (14) - CHECK_TOKEN_NUMBERS (1, 13) - - // Quadrilateral / Conditional line - LDObject* obj; - - if (num == 4) - obj = new LDQuad; - else - obj = new LDCondLine; - - obj->setColor (tokens[1].toLong()); - - for (int i = 0; i < 4; ++i) - obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 13 - - return obj; - } - - default: // Strange line we couldn't parse - return new LDError (line, "Unknown line code number"); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument* getDocument (str filename) -{ // Try find the file in the list of loaded files - LDDocument* doc = findDocument (filename); - - // If it's not loaded, try open it - if (!doc) - doc = openDocument (filename, true); - - return doc; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void reloadAllSubfiles() -{ if (!getCurrentDocument()) - return; - - g_loadedFiles.clear(); - g_loadedFiles << getCurrentDocument(); - - // Go through all objects in the current file and reload the subfiles - for (LDObject* obj : getCurrentDocument()->getObjects()) - { if (obj->getType() == LDObject::Subfile) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - LDDocument* fileInfo = getDocument (ref->getFileInfo()->getName()); - - if (fileInfo) - ref->setFileInfo (fileInfo); - else - ref->replace (new LDError (ref->raw(), "Could not open referred file")); - } - - // Reparse gibberish files. It could be that they are invalid because - // of loading errors. Circumstances may be different now. - if (obj->getType() == LDObject::Error) - obj->replace (parseLine (static_cast<LDError*> (obj)->contents)); - } - - // Close all files left unused - LDDocument::closeUnused(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int LDDocument::addObject (LDObject* obj) -{ getHistory()->add (new AddHistory (getObjects().size(), obj)); - m_Objects << obj; - - if (obj->getType() == LDObject::Vertex) - m_Vertices << obj; - - obj->setFile (this); - return getObjectCount() - 1; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::addObjects (const QList<LDObject*> objs) -{ for (LDObject * obj : objs) - if (obj) - addObject (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::insertObj (int pos, LDObject* obj) -{ getHistory()->add (new AddHistory (pos, obj)); - m_Objects.insert (pos, obj); - obj->setFile (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::forgetObject (LDObject* obj) -{ int idx = obj->getIndex(); - getHistory()->add (new DelHistory (idx, obj)); - m_Objects.removeAt (idx); - obj->setFile (null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool safeToCloseAll() -{ for (LDDocument* f : g_loadedFiles) - if (!f->isSafeToClose()) - return false; - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::setObject (int idx, LDObject* obj) -{ assert (idx < m_Objects.size()); - - // Mark this change to history - str oldcode = getObject (idx)->raw(); - str newcode = obj->raw(); - *m_History << new EditHistory (idx, oldcode, newcode); - - obj->setFile (this); - m_Objects[idx] = obj; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static QList<LDDocument*> getFilesUsed (LDDocument* node) -{ QList<LDDocument*> filesUsed; - - for (LDObject* obj : node->getObjects()) - { if (obj->getType() != LDObject::Subfile) - continue; - - LDSubfile* ref = static_cast<LDSubfile*> (obj); - filesUsed << ref->getFileInfo(); - filesUsed << getFilesUsed (ref->getFileInfo()); - } - - return filesUsed; -} - -// ============================================================================= -// Find out which files are unused and close them. -// ----------------------------------------------------------------------------- -void LDDocument::closeUnused() -{ QList<LDDocument*> filesUsed = getFilesUsed (getCurrentDocument()); - - // Anything that's explicitly opened must not be closed - for (LDDocument* file : g_loadedFiles) - if (!file->isImplicit()) - filesUsed << file; - - // Remove duplicated entries - removeDuplicates (filesUsed); - - // Close all open files that aren't in filesUsed - for (LDDocument* file : g_loadedFiles) - { bool isused = false; - - for (LDDocument* usedFile : filesUsed) - { if (file == usedFile) - { isused = true; - break; - } - } - - if (!isused) - delete file; - } - - g_loadedFiles.clear(); - g_loadedFiles << filesUsed; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDDocument::getObject (int pos) const -{ if (m_Objects.size() <= pos) - return null; - - return m_Objects[pos]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int LDDocument::getObjectCount() const -{ return getObjects().size(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDDocument::hasUnsavedChanges() const -{ return !isImplicit() && getHistory()->getPosition() != getSavePosition(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDDocument::getShortName() -{ if (!getName().isEmpty()) - return basename (getName()); - - if (!getDefaultName().isEmpty()) - return getDefaultName(); - - return tr ("<anonymous>"); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDObject*> LDDocument::inlineContents (LDSubfile::InlineFlags flags) -{ // Possibly substitute with logoed studs: - // stud.dat -> stud-logo.dat - // stud2.dat -> stud-logo2.dat - if (gl_logostuds && (flags & LDSubfile::RendererInline)) - { if (getName() == "stud.dat" && g_logoedStud) - return g_logoedStud->inlineContents (flags); - - elif (getName() == "stud2.dat" && g_logoedStud2) - return g_logoedStud2->inlineContents (flags); - } - - QList<LDObject*> objs, objcache; - - bool deep = flags & LDSubfile::DeepInline, - doCache = flags & LDSubfile::CacheInline; - - // If we have this cached, just clone that - if (deep && getCache().size()) - { for (LDObject* obj : getCache()) - objs << obj->clone(); - } - else - { if (!deep) - doCache = false; - - for (LDObject* obj : getObjects()) - { // Skip those without scemantic meaning - if (!obj->isScemantic()) - continue; - - // Got another sub-file reference, inline it if we're deep-inlining. If not, - // just add it into the objects normally. Also, we only cache immediate - // subfiles and this is not one. Yay, recursion! - if (deep && obj->getType() == LDObject::Subfile) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - - // We only want to cache immediate subfiles, so shed the caching - // flag when recursing deeper in hierarchy. - QList<LDObject*> otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline)); - - for (LDObject * otherobj : otherobjs) - { // Cache this object, if desired - if (doCache) - objcache << otherobj->clone(); - - objs << otherobj; - } - } - else - { if (doCache) - objcache << obj->clone(); - - objs << obj->clone(); - } - } - - if (doCache) - setCache (objcache); - } - - return objs; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument* LDDocument::current() -{ return m_curdoc; -} - -// ============================================================================= -// Sets the given file as the current one on display. At some point in time this -// was an operation completely unheard of. ;) -// -// FIXME: f can be temporarily null. This probably should not be the case. -// ----------------------------------------------------------------------------- -void LDDocument::setCurrent (LDDocument* f) -{ // Implicit files were loaded for caching purposes and must never be set - // current. - if (f && f->isImplicit()) - return; - - m_curdoc = f; - - if (g_win && f) - { // A ton of stuff needs to be updated - g_win->updateDocumentListItem (f); - g_win->buildObjList(); - g_win->updateTitle(); - g_win->R()->setFile (f); - g_win->R()->resetAllAngles(); - g_win->R()->repaint(); - - log ("Changed file to %1", f->getShortName()); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int LDDocument::countExplicitFiles() -{ int count = 0; - - for (LDDocument* f : g_loadedFiles) - if (f->isImplicit() == false) - count++; - - return count; -} - -// ============================================================================= -// This little beauty closes the initial file that was open at first when opening -// a new file over it. -// ----------------------------------------------------------------------------- -void LDDocument::closeInitialFile() -{ if ( - countExplicitFiles() == 2 && - g_loadedFiles[0]->getName() == "" && - !g_loadedFiles[0]->hasUnsavedChanges() - ) - delete g_loadedFiles[0]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void loadLogoedStuds() -{ log ("Loading logoed studs...\n"); - - delete g_logoedStud; - delete g_logoedStud2; - - g_logoedStud = openDocument ("stud-logo.dat", true); - g_logoedStud2 = openDocument ("stud2-logo.dat", true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::addToSelection (LDObject* obj) // [protected] -{ if (obj->isSelected()) - return; - - assert (obj->getFile() == this); - m_sel << obj; - obj->setSelected (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::removeFromSelection (LDObject* obj) // [protected] -{ if (!obj->isSelected()) - return; - - assert (obj->getFile() == this); - m_sel.removeOne (obj); - obj->setSelected (false); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::clearSelection() -{ for (LDObject* obj : m_sel) - removeFromSelection (obj); - - assert (m_sel.isEmpty()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const QList<LDObject*>& LDDocument::getSelection() const -{ return m_sel; -} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/download.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,496 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QNetworkAccessManager> +#include <QNetworkRequest> +#include <QNetworkReply> +#include <QDir> +#include <QProgressBar> +#include <QPushButton> +#include "download.h" +#include "ui_downloadfrom.h" +#include "types.h" +#include "gui.h" +#include "document.h" +#include "gldraw.h" +#include "configDialog.h" +#include "moc_download.cpp" + +cfg (String, net_downloadpath, ""); +cfg (Bool, net_guesspaths, true); +cfg (Bool, net_autoclose, true); + +const str g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/"); + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloader::staticBegin() +{ str path = getDownloadPath(); + + if (path == "" || QDir (path).exists() == false) + { critical (PartDownloader::tr ("You need to specify a valid path for " + "downloaded files in the configuration to download paths.")); + + (new ConfigDialog (ConfigDialog::DownloadTab, null))->exec(); + return; + } + + PartDownloader* dlg = new PartDownloader; + dlg->exec(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str PartDownloader::getDownloadPath() +{ str path = net_downloadpath; + +#if DIRSLASH_CHAR != '/' + path.replace (DIRSLASH, "/"); +#endif + + return path; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PartDownloader::PartDownloader (QWidget* parent) : QDialog (parent) +{ setInterface (new Ui_DownloadFrom); + getInterface()->setupUi (this); + getInterface()->fname->setFocus(); + getInterface()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); + + setDownloadButton (new QPushButton (tr ("Download"))); + getInterface()->buttonBox->addButton (getDownloadButton(), QDialogButtonBox::ActionRole); + getButton (Abort)->setEnabled (false); + + connect (getInterface()->source, SIGNAL (currentIndexChanged (int)), + this, SLOT (sourceChanged (int))); + connect (getInterface()->buttonBox, SIGNAL (clicked (QAbstractButton*)), + this, SLOT (buttonClicked (QAbstractButton*))); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PartDownloader::~PartDownloader() +{ delete getInterface(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str PartDownloader::getURL() const +{ const Source src = getSource(); + str dest; + + switch (src) + { case PartsTracker: + dest = getInterface()->fname->text(); + modifyDestination (dest); + return g_unofficialLibraryURL + dest; + + case CustomURL: + return getInterface()->fname->text(); + } + + // Shouldn't happen + return ""; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloader::modifyDestination (str& dest) const +{ dest = dest.simplified(); + + // If the user doesn't want us to guess, stop right here. + if (net_guesspaths == false) + return; + + // Ensure .dat extension + if (dest.right (4) != ".dat") + { // Remove the existing extension, if any. It may be we're here over a + // typo in the .dat extension. + const int dotpos = dest.lastIndexOf ("."); + + if (dotpos != -1 && dotpos >= dest.length() - 4) + dest.chop (dest.length() - dotpos); + + dest += ".dat"; + } + + // If the part starts with s\ or s/, then use parts/s/. Same goes with + // 48\ and p/48/. + if (dest.left (2) == "s\\" || dest.left (2) == "s/") + { dest.remove (0, 2); + dest.prepend ("parts/s/"); + } elif (dest.left (3) == "48\\" || dest.left (3) == "48/") + { dest.remove (0, 3); + dest.prepend ("p/48/"); + } + + /* Try determine where to put this part. We have four directories: + parts/, parts/s/, p/, and p/48/. If we haven't already specified + either parts/ or p/, we need to add it automatically. Part files + are numbers wit a possible u prefix for parts with unknown number + which can be followed by any of: + - c** (composites) + - d** (formed stickers) + - p** (patterns) + - a lowercase alphabetic letter for variants + + Subfiles (usually) have an s** prefix, in which case we use parts/s/. + Note that the regex starts with a '^' so it won't catch already fully + given part file names. */ + str partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*"; + str subpartRegex = partRegex + "s[0-9][0-9]+"; + + partRegex += "\\.dat$"; + subpartRegex += "\\.dat$"; + + if (QRegExp (subpartRegex).exactMatch (dest)) + dest.prepend ("parts/s/"); + elif (QRegExp (partRegex).exactMatch (dest)) + dest.prepend ("parts/"); + elif (dest.left (6) != "parts/" && dest.left (2) != "p/") + dest.prepend ("p/"); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PartDownloader::Source PartDownloader::getSource() const +{ return (Source) getInterface()->source->currentIndex(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloader::sourceChanged (int i) +{ if (i == CustomURL) + getInterface()->fileNameLabel->setText (tr ("URL:")); + else + getInterface()->fileNameLabel->setText (tr ("File name:")); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloader::buttonClicked (QAbstractButton* btn) +{ if (btn == getButton (Close)) + { reject(); + } + elif (btn == getButton (Abort)) + { setAborted (true); + + for (PartDownloadRequest* req : getRequests()) + req->abort(); + } + elif (btn == getButton (Download)) + { str dest = getInterface()->fname->text(); + setPrimaryFile (null); + setAborted (false); + + if (getSource() == CustomURL) + dest = basename (getURL()); + + modifyDestination (dest); + + if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest)) + { const str overwritemsg = fmt (tr ("%1 already exists in download directory. Overwrite?"), dest); + if (!confirm (tr ("Overwrite?"), overwritemsg)) + return; + } + + getDownloadButton()->setEnabled (false); + getInterface()->progress->setEnabled (true); + getInterface()->fname->setEnabled (false); + getInterface()->source->setEnabled (false); + downloadFile (dest, getURL(), true); + getButton (Close)->setEnabled (false); + getButton (Abort)->setEnabled (true); + getButton (Download)->setEnabled (false); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloader::downloadFile (str dest, str url, bool primary) +{ const int row = getInterface()->progress->rowCount(); + + // Don't download files repeadetly. + if (getFilesToDownload().indexOf (dest) != -1) + return; + + modifyDestination (dest); + log ("DOWNLOAD: %1 -> %2\n", url, PartDownloader::getDownloadPath() + DIRSLASH + dest); + PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); + + pushToFilesToDownload (dest); + pushToRequests (req); + getInterface()->progress->insertRow (row); + req->setTableRow (row); + req->updateToTable(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloader::checkIfFinished() +{ bool failed = isAborted(); + + // If there is some download still working, we're not finished. + for (PartDownloadRequest* req : getRequests()) + { if (!req->isFinished()) + return; + + if (req->getState() == PartDownloadRequest::EFailed) + failed = true; + } + + for (PartDownloadRequest* req : getRequests()) + delete req; + + clearRequests(); + + // Update everything now + if (getPrimaryFile()) + { LDDocument::setCurrent (getPrimaryFile()); + reloadAllSubfiles(); + g_win->doFullRefresh(); + g_win->R()->resetAngles(); + } + + if (net_autoclose && !failed) + { // Close automatically if desired. + accept(); + } + else + { // Allow the prompt be closed now. + getButton (Abort)->setEnabled (false); + getButton (Close)->setEnabled (true); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QPushButton* PartDownloader::getButton (PartDownloader::Button i) +{ switch (i) + { case Download: + return getDownloadButton(); + + case Abort: + return qobject_cast<QPushButton*> (getInterface()->buttonBox->button (QDialogButtonBox::Abort)); + + case Close: + return qobject_cast<QPushButton*> (getInterface()->buttonBox->button (QDialogButtonBox::Close)); + } + + return null; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PartDownloadRequest::PartDownloadRequest (str url, str dest, bool primary, PartDownloader* parent) : + QObject (parent), + m_State (ERequesting), + m_Prompt (parent), + m_URL (url), + m_Destinaton (dest), + m_FilePath (PartDownloader::getDownloadPath() + DIRSLASH + dest), + m_NAM (new QNetworkAccessManager), + m_FirstUpdate (true), + m_Primary (primary), + m_FilePointer (null) +{ + // Make sure that we have a valid destination. + str dirpath = dirname (getFilePath()); + + QDir dir (dirpath); + + if (!dir.exists()) + { log ("Creating %1...\n", dirpath); + + if (!dir.mkpath (dirpath)) + critical (fmt (tr ("Couldn't create the directory %1!"), dirpath)); + } + + setReply (getNAM()->get (QNetworkRequest (QUrl (url)))); + connect (getReply(), SIGNAL (finished()), this, SLOT (downloadFinished())); + connect (getReply(), SIGNAL (readyRead()), this, SLOT (readyRead())); + connect (getReply(), SIGNAL (downloadProgress (qint64, qint64)), + this, SLOT (downloadProgress (qint64, qint64))); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PartDownloadRequest::~PartDownloadRequest() {} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloadRequest::updateToTable() +{ const int labelcol = PartDownloader::PartLabelColumn, + progcol = PartDownloader::ProgressColumn; + QTableWidget* table = getPrompt()->getInterface()->progress; + QProgressBar* prog; + + switch (getState()) + { case ERequesting: + case EDownloading: + { prog = qobject_cast<QProgressBar*> (table->cellWidget (getTableRow(), progcol)); + + if (!prog) + { prog = new QProgressBar; + table->setCellWidget (getTableRow(), progcol, prog); + } + + prog->setRange (0, getBytesTotal()); + prog->setValue (getBytesRead()); + } break; + + case EFinished: + case EFailed: + { const str text = (getState() == EFinished) + ? "<b><span style=\"color: #080\">FINISHED</span></b>" + : "<b><span style=\"color: #800\">FAILED</span></b>"; + + QLabel* lb = new QLabel (text); + lb->setAlignment (Qt::AlignCenter); + table->setCellWidget (getTableRow(), progcol, lb); + } break; + } + + QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (getTableRow(), labelcol)); + + if (isFirstUpdate()) + { lb = new QLabel (fmt ("<b>%1</b>", getDestinaton()), table); + table->setCellWidget (getTableRow(), labelcol, lb); + } + + // Make sure that the cell is big enough to contain the label + if (table->columnWidth (labelcol) < lb->width()) + table->setColumnWidth (labelcol, lb->width()); + + setFirstUpdate (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloadRequest::downloadFinished() +{ if (getReply()->error() != QNetworkReply::NoError) + { if (isPrimary() && !getPrompt()->isAborted()) + critical (getReply()->errorString()); + + setState (EFailed); + } + elif (getState() != EFailed) + setState (EFinished); + + setBytesRead (getBytesTotal()); + updateToTable(); + + if (getFilePointer()) + { getFilePointer()->close(); + delete getFilePointer(); + setFilePointer (null); + + if (getState() == EFailed) + QFile::remove (getFilePath()); + } + + if (getState() != EFinished) + { getPrompt()->checkIfFinished(); + return; + } + + // Try to load this file now. + LDDocument* f = openDocument (getFilePath(), false); + + if (!f) + return; + + f->setImplicit (!isPrimary()); + + // Iterate through this file and check for errors. If there's any that stems + // from unknown file references, try resolve that by downloading the reference. + // This is why downloading a part may end up downloading multiple files, as + // it resolves dependencies. + for (LDObject* obj : f->getObjects()) + { LDError* err = dynamic_cast<LDError*> (obj); + + if (!err || err->getFileReferenced().isEmpty()) + continue; + + str dest = err->getFileReferenced(); + getPrompt()->modifyDestination (dest); + getPrompt()->downloadFile (dest, g_unofficialLibraryURL + dest, false); + } + + if (isPrimary()) + { addRecentFile (getFilePath()); + getPrompt()->setPrimaryFile (f); + } + + getPrompt()->checkIfFinished(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloadRequest::downloadProgress (int64 recv, int64 total) +{ setBytesRead (recv); + setBytesTotal (total); + setState (EDownloading); + updateToTable(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloadRequest::readyRead() +{ if (getState() == EFailed) + return; + + if (getFilePointer() == null) + { replaceInFilePath ("\\", "/"); + + // We have already asked the user whether we can overwrite so we're good + // to go here. + setFilePointer (new QFile (getFilePath().toLocal8Bit())); + + if (!getFilePointer()->open (QIODevice::WriteOnly)) + { critical (fmt (tr ("Couldn't open %1 for writing: %2"), getFilePath(), strerror (errno))); + setState (EFailed); + getReply()->abort(); + updateToTable(); + getPrompt()->checkIfFinished(); + return; + } + } + + getFilePointer()->write (getReply()->readAll()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool PartDownloadRequest::isFinished() const +{ return getState() == EFinished || getState() == EFailed; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloadRequest::abort() +{ getReply()->abort(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (DownloadFrom, 0) +{ PartDownloader::staticBegin(); +}
--- a/src/download.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,496 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QNetworkAccessManager> -#include <QNetworkRequest> -#include <QNetworkReply> -#include <QDir> -#include <QProgressBar> -#include <QPushButton> -#include "download.h" -#include "ui_downloadfrom.h" -#include "types.h" -#include "gui.h" -#include "document.h" -#include "gldraw.h" -#include "configDialog.h" -#include "moc_download.cpp" - -cfg (String, net_downloadpath, ""); -cfg (Bool, net_guesspaths, true); -cfg (Bool, net_autoclose, true); - -const str g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/"); - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::staticBegin() -{ str path = getDownloadPath(); - - if (path == "" || QDir (path).exists() == false) - { critical (PartDownloader::tr ("You need to specify a valid path for " - "downloaded files in the configuration to download paths.")); - - (new ConfigDialog (ConfigDialog::DownloadTab, null))->exec(); - return; - } - - PartDownloader* dlg = new PartDownloader; - dlg->exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str PartDownloader::getDownloadPath() -{ str path = net_downloadpath; - -#if DIRSLASH_CHAR != '/' - path.replace (DIRSLASH, "/"); -#endif - - return path; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloader::PartDownloader (QWidget* parent) : QDialog (parent) -{ setInterface (new Ui_DownloadFrom); - getInterface()->setupUi (this); - getInterface()->fname->setFocus(); - getInterface()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); - - setDownloadButton (new QPushButton (tr ("Download"))); - getInterface()->buttonBox->addButton (getDownloadButton(), QDialogButtonBox::ActionRole); - getButton (Abort)->setEnabled (false); - - connect (getInterface()->source, SIGNAL (currentIndexChanged (int)), - this, SLOT (sourceChanged (int))); - connect (getInterface()->buttonBox, SIGNAL (clicked (QAbstractButton*)), - this, SLOT (buttonClicked (QAbstractButton*))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloader::~PartDownloader() -{ delete getInterface(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str PartDownloader::getURL() const -{ const Source src = getSource(); - str dest; - - switch (src) - { case PartsTracker: - dest = getInterface()->fname->text(); - modifyDestination (dest); - return g_unofficialLibraryURL + dest; - - case CustomURL: - return getInterface()->fname->text(); - } - - // Shouldn't happen - return ""; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::modifyDestination (str& dest) const -{ dest = dest.simplified(); - - // If the user doesn't want us to guess, stop right here. - if (net_guesspaths == false) - return; - - // Ensure .dat extension - if (dest.right (4) != ".dat") - { // Remove the existing extension, if any. It may be we're here over a - // typo in the .dat extension. - const int dotpos = dest.lastIndexOf ("."); - - if (dotpos != -1 && dotpos >= dest.length() - 4) - dest.chop (dest.length() - dotpos); - - dest += ".dat"; - } - - // If the part starts with s\ or s/, then use parts/s/. Same goes with - // 48\ and p/48/. - if (dest.left (2) == "s\\" || dest.left (2) == "s/") - { dest.remove (0, 2); - dest.prepend ("parts/s/"); - } elif (dest.left (3) == "48\\" || dest.left (3) == "48/") - { dest.remove (0, 3); - dest.prepend ("p/48/"); - } - - /* Try determine where to put this part. We have four directories: - parts/, parts/s/, p/, and p/48/. If we haven't already specified - either parts/ or p/, we need to add it automatically. Part files - are numbers wit a possible u prefix for parts with unknown number - which can be followed by any of: - - c** (composites) - - d** (formed stickers) - - p** (patterns) - - a lowercase alphabetic letter for variants - - Subfiles (usually) have an s** prefix, in which case we use parts/s/. - Note that the regex starts with a '^' so it won't catch already fully - given part file names. */ - str partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*"; - str subpartRegex = partRegex + "s[0-9][0-9]+"; - - partRegex += "\\.dat$"; - subpartRegex += "\\.dat$"; - - if (QRegExp (subpartRegex).exactMatch (dest)) - dest.prepend ("parts/s/"); - elif (QRegExp (partRegex).exactMatch (dest)) - dest.prepend ("parts/"); - elif (dest.left (6) != "parts/" && dest.left (2) != "p/") - dest.prepend ("p/"); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloader::Source PartDownloader::getSource() const -{ return (Source) getInterface()->source->currentIndex(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::sourceChanged (int i) -{ if (i == CustomURL) - getInterface()->fileNameLabel->setText (tr ("URL:")); - else - getInterface()->fileNameLabel->setText (tr ("File name:")); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::buttonClicked (QAbstractButton* btn) -{ if (btn == getButton (Close)) - { reject(); - } - elif (btn == getButton (Abort)) - { setAborted (true); - - for (PartDownloadRequest* req : getRequests()) - req->abort(); - } - elif (btn == getButton (Download)) - { str dest = getInterface()->fname->text(); - setPrimaryFile (null); - setAborted (false); - - if (getSource() == CustomURL) - dest = basename (getURL()); - - modifyDestination (dest); - - if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest)) - { const str overwritemsg = fmt (tr ("%1 already exists in download directory. Overwrite?"), dest); - if (!confirm (tr ("Overwrite?"), overwritemsg)) - return; - } - - getDownloadButton()->setEnabled (false); - getInterface()->progress->setEnabled (true); - getInterface()->fname->setEnabled (false); - getInterface()->source->setEnabled (false); - downloadFile (dest, getURL(), true); - getButton (Close)->setEnabled (false); - getButton (Abort)->setEnabled (true); - getButton (Download)->setEnabled (false); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::downloadFile (str dest, str url, bool primary) -{ const int row = getInterface()->progress->rowCount(); - - // Don't download files repeadetly. - if (getFilesToDownload().indexOf (dest) != -1) - return; - - modifyDestination (dest); - log ("DOWNLOAD: %1 -> %2\n", url, PartDownloader::getDownloadPath() + DIRSLASH + dest); - PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); - - pushToFilesToDownload (dest); - pushToRequests (req); - getInterface()->progress->insertRow (row); - req->setTableRow (row); - req->updateToTable(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::checkIfFinished() -{ bool failed = isAborted(); - - // If there is some download still working, we're not finished. - for (PartDownloadRequest* req : getRequests()) - { if (!req->isFinished()) - return; - - if (req->getState() == PartDownloadRequest::EFailed) - failed = true; - } - - for (PartDownloadRequest* req : getRequests()) - delete req; - - clearRequests(); - - // Update everything now - if (getPrimaryFile()) - { LDDocument::setCurrent (getPrimaryFile()); - reloadAllSubfiles(); - g_win->doFullRefresh(); - g_win->R()->resetAngles(); - } - - if (net_autoclose && !failed) - { // Close automatically if desired. - accept(); - } - else - { // Allow the prompt be closed now. - getButton (Abort)->setEnabled (false); - getButton (Close)->setEnabled (true); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QPushButton* PartDownloader::getButton (PartDownloader::Button i) -{ switch (i) - { case Download: - return getDownloadButton(); - - case Abort: - return qobject_cast<QPushButton*> (getInterface()->buttonBox->button (QDialogButtonBox::Abort)); - - case Close: - return qobject_cast<QPushButton*> (getInterface()->buttonBox->button (QDialogButtonBox::Close)); - } - - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloadRequest::PartDownloadRequest (str url, str dest, bool primary, PartDownloader* parent) : - QObject (parent), - m_State (ERequesting), - m_Prompt (parent), - m_URL (url), - m_Destinaton (dest), - m_FilePath (PartDownloader::getDownloadPath() + DIRSLASH + dest), - m_NAM (new QNetworkAccessManager), - m_FirstUpdate (true), - m_Primary (primary), - m_FilePointer (null) -{ - // Make sure that we have a valid destination. - str dirpath = dirname (getFilePath()); - - QDir dir (dirpath); - - if (!dir.exists()) - { log ("Creating %1...\n", dirpath); - - if (!dir.mkpath (dirpath)) - critical (fmt (tr ("Couldn't create the directory %1!"), dirpath)); - } - - setReply (getNAM()->get (QNetworkRequest (QUrl (url)))); - connect (getReply(), SIGNAL (finished()), this, SLOT (downloadFinished())); - connect (getReply(), SIGNAL (readyRead()), this, SLOT (readyRead())); - connect (getReply(), SIGNAL (downloadProgress (qint64, qint64)), - this, SLOT (downloadProgress (qint64, qint64))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloadRequest::~PartDownloadRequest() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::updateToTable() -{ const int labelcol = PartDownloader::PartLabelColumn, - progcol = PartDownloader::ProgressColumn; - QTableWidget* table = getPrompt()->getInterface()->progress; - QProgressBar* prog; - - switch (getState()) - { case ERequesting: - case EDownloading: - { prog = qobject_cast<QProgressBar*> (table->cellWidget (getTableRow(), progcol)); - - if (!prog) - { prog = new QProgressBar; - table->setCellWidget (getTableRow(), progcol, prog); - } - - prog->setRange (0, getBytesTotal()); - prog->setValue (getBytesRead()); - } break; - - case EFinished: - case EFailed: - { const str text = (getState() == EFinished) - ? "<b><span style=\"color: #080\">FINISHED</span></b>" - : "<b><span style=\"color: #800\">FAILED</span></b>"; - - QLabel* lb = new QLabel (text); - lb->setAlignment (Qt::AlignCenter); - table->setCellWidget (getTableRow(), progcol, lb); - } break; - } - - QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (getTableRow(), labelcol)); - - if (isFirstUpdate()) - { lb = new QLabel (fmt ("<b>%1</b>", getDestinaton()), table); - table->setCellWidget (getTableRow(), labelcol, lb); - } - - // Make sure that the cell is big enough to contain the label - if (table->columnWidth (labelcol) < lb->width()) - table->setColumnWidth (labelcol, lb->width()); - - setFirstUpdate (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::downloadFinished() -{ if (getReply()->error() != QNetworkReply::NoError) - { if (isPrimary() && !getPrompt()->isAborted()) - critical (getReply()->errorString()); - - setState (EFailed); - } - elif (getState() != EFailed) - setState (EFinished); - - setBytesRead (getBytesTotal()); - updateToTable(); - - if (getFilePointer()) - { getFilePointer()->close(); - delete getFilePointer(); - setFilePointer (null); - - if (getState() == EFailed) - QFile::remove (getFilePath()); - } - - if (getState() != EFinished) - { getPrompt()->checkIfFinished(); - return; - } - - // Try to load this file now. - LDDocument* f = openDocument (getFilePath(), false); - - if (!f) - return; - - f->setImplicit (!isPrimary()); - - // Iterate through this file and check for errors. If there's any that stems - // from unknown file references, try resolve that by downloading the reference. - // This is why downloading a part may end up downloading multiple files, as - // it resolves dependencies. - for (LDObject* obj : f->getObjects()) - { LDError* err = dynamic_cast<LDError*> (obj); - - if (!err || err->getFileReferenced().isEmpty()) - continue; - - str dest = err->getFileReferenced(); - getPrompt()->modifyDestination (dest); - getPrompt()->downloadFile (dest, g_unofficialLibraryURL + dest, false); - } - - if (isPrimary()) - { addRecentFile (getFilePath()); - getPrompt()->setPrimaryFile (f); - } - - getPrompt()->checkIfFinished(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::downloadProgress (int64 recv, int64 total) -{ setBytesRead (recv); - setBytesTotal (total); - setState (EDownloading); - updateToTable(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::readyRead() -{ if (getState() == EFailed) - return; - - if (getFilePointer() == null) - { replaceInFilePath ("\\", "/"); - - // We have already asked the user whether we can overwrite so we're good - // to go here. - setFilePointer (new QFile (getFilePath().toLocal8Bit())); - - if (!getFilePointer()->open (QIODevice::WriteOnly)) - { critical (fmt (tr ("Couldn't open %1 for writing: %2"), getFilePath(), strerror (errno))); - setState (EFailed); - getReply()->abort(); - updateToTable(); - getPrompt()->checkIfFinished(); - return; - } - } - - getFilePointer()->write (getReply()->readAll()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool PartDownloadRequest::isFinished() const -{ return getState() == EFinished || getState() == EFailed; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::abort() -{ getReply()->abort(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (DownloadFrom, 0) -{ PartDownloader::staticBegin(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/extprogs.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,638 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QProcess> +#include <QTemporaryFile> +#include <QDialog> +#include <QDialogButtonBox> +#include <QSpinBox> +#include <QCheckBox> +#include <QComboBox> +#include <QGridLayout> +#include "main.h" +#include "config.h" +#include "misc.h" +#include "gui.h" +#include "document.h" +#include "widgets.h" +#include "history.h" +#include "ui_ytruder.h" +#include "ui_intersector.h" +#include "ui_rectifier.h" +#include "ui_coverer.h" +#include "ui_isecalc.h" +#include "ui_edger2.h" +#include "dialogs.h" + +enum extprog +{ Isecalc, + Intersector, + Coverer, + Ytruder, + Rectifier, + Edger2, +}; + +// ============================================================================= +// ----------------------------------------------------------------------------- +cfg (String, prog_isecalc, ""); +cfg (String, prog_intersector, ""); +cfg (String, prog_coverer, ""); +cfg (String, prog_ytruder, ""); +cfg (String, prog_rectifier, ""); +cfg (String, prog_edger2, ""); + +StringConfig* const g_extProgPaths[] = +{ &prog_isecalc, + &prog_intersector, + &prog_coverer, + &prog_ytruder, + &prog_rectifier, + &prog_edger2, +}; + +#ifndef _WIN32 +cfg (Bool, prog_isecalc_wine, false); +cfg (Bool, prog_intersector_wine, false); +cfg (Bool, prog_coverer_wine, false); +cfg (Bool, prog_ytruder_wine, false); +cfg (Bool, prog_rectifier_wine, false); +cfg (Bool, prog_edger2_wine, false); + +BoolConfig* const g_extProgWine[] = +{ &prog_isecalc_wine, + &prog_intersector_wine, + &prog_coverer_wine, + &prog_ytruder_wine, + &prog_rectifier_wine, + &prog_edger2_wine, +}; +#endif // _WIN32 + +const char* g_extProgNames[] = +{ "Isecalc", + "Intersector", + "Coverer", + "Ytruder", + "Rectifier", + "Edger2" +}; + +// ============================================================================= +// ----------------------------------------------------------------------------- +static bool checkProgPath (const extprog prog) +{ alias path = g_extProgPaths[prog]->value; + + if (path.length() > 0) + return true; + + ExtProgPathPrompt* dlg = new ExtProgPathPrompt (g_extProgNames[prog]); + + if (dlg->exec() && !dlg->getPath().isEmpty()) + { path = dlg->getPath(); + return true; + } + + return false; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static str processErrorString (QProcess& proc) +{ switch (proc.error()) + { case QProcess::FailedToStart: + return "Failed to start (check your permissions)"; + + case QProcess::Crashed: + return "Crashed."; + + case QProcess::WriteError: + case QProcess::ReadError: + return "I/O error."; + + case QProcess::UnknownError: + return "Unknown error"; + + case QProcess::Timedout: + return fmt ("Timed out (30 seconds)"); + } + + return ""; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static bool mkTempFile (QTemporaryFile& tmp, str& fname) +{ if (!tmp.open()) + return false; + + fname = tmp.fileName(); + tmp.close(); + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void writeObjects (const QList<LDObject*>& objects, File& f) +{ for (LDObject* obj : objects) + { if (obj->getType() == LDObject::Subfile) + { LDSubfile* ref = static_cast<LDSubfile*> (obj); + QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepInline); + + writeObjects (objs, f); + + for (LDObject* obj : objs) + delete obj; + } + else + f.write (obj->raw() + "\r\n"); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void writeObjects (const QList<LDObject*>& objects, str fname) +{ // Write the input file + File f (fname, File::Write); + + if (!f) + { critical (fmt ("Couldn't open temporary file %1 for writing.\n", fname)); + return; + } + + writeObjects (objects, f); + f.close(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void writeSelection (str fname) +{ writeObjects (selection(), fname); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void writeColorGroup (const int colnum, str fname) +{ QList<LDObject*> objects; + + for (LDObject* obj : getCurrentDocument()->getObjects()) + { if (obj->isColored() == false || obj->getColor() != colnum) + continue; + + objects << obj; + } + + writeObjects (objects, fname); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool runUtilityProcess (extprog prog, str path, str argvstr) +{ QTemporaryFile input, output; + str inputname, outputname; + QStringList argv = argvstr.split (" ", QString::SkipEmptyParts); + +#ifndef _WIN32 + if (*g_extProgWine[prog]) + { argv.insert (0, path); + path = "wine"; + } +#endif // _WIN32 + + log ("cmdline: %1 %2\n", path, argv.join (" ")); + + // Temporary files for stdin and stdout + if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname)) + return false; + + QProcess proc; + + // Init stdin + File stdinfp (inputname, File::Write); + + // Begin! + proc.setStandardInputFile (inputname); + proc.start (path, argv); + + // Write an enter, the utility tools all expect one + stdinfp.write ("\n"); + + // Wait while it runs + proc.waitForFinished(); + + str err = ""; + + if (proc.exitStatus() != QProcess::NormalExit) + err = processErrorString (proc); + + // Check the return code + if (proc.exitCode() != 0) + err = fmt ("Program exited abnormally (return code %1).", proc.exitCode()); + + if (err.length() > 0) + { critical (fmt ("%1 failed: %2\n", g_extProgNames[prog], err)); + return false; + } + + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void insertOutput (str fname, bool replace, QList<int> colorsToReplace) +{ +#ifndef RELEASE + QFile::copy (fname, "./debug_lastOutput"); +#endif // RELEASE + + // Read the output file + File f (fname, File::Read); + + if (!f) + { critical (fmt ("Couldn't open temporary file %1 for reading.\n", fname)); + return; + } + + QList<LDObject*> objs = loadFileContents (&f, null); + + // If we replace the objects, delete the selection now. + if (replace) + g_win->deleteSelection(); + + for (const int colnum : colorsToReplace) + g_win->deleteByColor (colnum); + + // Insert the new objects + getCurrentDocument()->clearSelection(); + + for (LDObject * obj : objs) + { if (!obj->isScemantic()) + { delete obj; + continue; + } + + getCurrentDocument()->addObject (obj); + obj->select(); + } + + g_win->doFullRefresh(); +} + +// ============================================================================= +// Interface for Ytruder +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Ytruder, 0) +{ setlocale (LC_ALL, "C"); + + if (!checkProgPath (Ytruder)) + return; + + QDialog* dlg = new QDialog; + Ui::YtruderUI ui; + ui.setupUi (dlg); + + if (!dlg->exec()) + return; + + // Read the user's choices + const enum { Distance, Symmetry, Projection, Radial } mode = + ui.mode_distance->isChecked() ? Distance : + ui.mode_symmetry->isChecked() ? Symmetry : + ui.mode_projection->isChecked() ? Projection : Radial; + + const Axis axis = + ui.axis_x->isChecked() ? X : + ui.axis_y->isChecked() ? Y : Z; + + const double depth = ui.planeDepth->value(), + condAngle = ui.condAngle->value(); + + QTemporaryFile indat, outdat; + str inDATName, outDATName; + + // Make temp files for the input and output files + if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) + return; + + // Compose the command-line arguments + str argv = join ( + { (axis == X) ? "-x" : (axis == Y) ? "-y" : "-z", + (mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r", + depth, + "-a", + condAngle, + inDATName, + outDATName + }); + + writeSelection (inDATName); + + if (!runUtilityProcess (Ytruder, prog_ytruder, argv)) + return; + + insertOutput (outDATName, false, {}); +} + +// ============================================================================= +// Rectifier interface +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Rectifier, 0) +{ setlocale (LC_ALL, "C"); + + if (!checkProgPath (Rectifier)) + return; + + QDialog* dlg = new QDialog; + Ui::RectifierUI ui; + ui.setupUi (dlg); + + if (!dlg->exec()) + return; + + QTemporaryFile indat, outdat; + str inDATName, outDATName; + + // Make temp files for the input and output files + if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) + return; + + // Compose arguments + str argv = join ( + { (!ui.cb_condense->isChecked()) ? "-q" : "", + (!ui.cb_subst->isChecked()) ? "-r" : "", + (ui.cb_condlineCheck->isChecked()) ? "-a" : "", + (ui.cb_colorize->isChecked()) ? "-c" : "", + "-t", + ui.dsb_coplthres->value(), + inDATName, + outDATName + }); + + writeSelection (inDATName); + + if (!runUtilityProcess (Rectifier, prog_rectifier, argv)) + return; + + insertOutput (outDATName, true, {}); +} + +// ============================================================================= +// Intersector interface +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Intersector, 0) +{ setlocale (LC_ALL, "C"); + + if (!checkProgPath (Intersector)) + return; + + QDialog* dlg = new QDialog; + Ui::IntersectorUI ui; + ui.setupUi (dlg); + + makeColorComboBox (ui.cmb_incol); + makeColorComboBox (ui.cmb_cutcol); + ui.cb_repeat->setWhatsThis ("If this is set, " APPNAME " runs Intersector a second time with inverse files to cut the " + " cutter group with the input group. Both groups are cut by the intersection."); + ui.cb_edges->setWhatsThis ("Makes " APPNAME " try run Isecalc to create edgelines for the intersection."); + + int inCol, cutCol; + const bool repeatInverse = ui.cb_repeat->isChecked(); + + forever + { if (!dlg->exec()) + return; + + inCol = ui.cmb_incol->itemData (ui.cmb_incol->currentIndex()).toInt(); + cutCol = ui.cmb_cutcol->itemData (ui.cmb_cutcol->currentIndex()).toInt(); + + if (inCol == cutCol) + { critical ("Cannot use the same color group for both input and cutter!"); + continue; + } + + break; + } + + // Five temporary files! + // indat = input group file + // cutdat = cutter group file + // outdat = primary output + // outdat2 = inverse output + // edgesdat = edges output (isecalc) + QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat; + str inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName; + + if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) || + !mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) || + !mkTempFile (edgesdat, edgesDATName)) + { return; + } + + str parms = join ( + { (ui.cb_colorize->isChecked()) ? "-c" : "", + (ui.cb_nocondense->isChecked()) ? "-t" : "", + "-s", + ui.dsb_prescale->value() + }); + + str argv_normal = join ( + { parms, + inDATName, + cutDATName, + outDATName + }); + + str argv_inverse = join ( + { parms, + cutDATName, + inDATName, + outDAT2Name + }); + + writeColorGroup (inCol, inDATName); + writeColorGroup (cutCol, cutDATName); + + if (!runUtilityProcess (Intersector, prog_intersector, argv_normal)) + return; + + insertOutput (outDATName, false, {inCol}); + + if (repeatInverse && runUtilityProcess (Intersector, prog_intersector, argv_inverse)) + insertOutput (outDAT2Name, false, {cutCol}); + + if ( + ui.cb_edges->isChecked() && + checkProgPath (Isecalc) && + runUtilityProcess (Isecalc, prog_isecalc, join ( {inDATName, cutDATName, edgesDATName})) + ) + insertOutput (edgesDATName, false, {}); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Coverer, 0) +{ setlocale (LC_ALL, "C"); + + if (!checkProgPath (Coverer)) + return; + + QDialog* dlg = new QDialog; + Ui::CovererUI ui; + ui.setupUi (dlg); + makeColorComboBox (ui.cmb_col1); + makeColorComboBox (ui.cmb_col2); + + int in1Col, in2Col; + + forever + { if (!dlg->exec()) + return; + + in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt(); + in2Col = ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt(); + + if (in1Col == in2Col) + { critical ("Cannot use the same color group for both input and cutter!"); + continue; + } + + break; + } + + QTemporaryFile in1dat, in2dat, outdat; + str in1DATName, in2DATName, outDATName; + + if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName)) + return; + + str argv = join ( + { (ui.cb_oldsweep->isChecked() ? "-s" : ""), + (ui.cb_reverse->isChecked() ? "-r" : ""), + (ui.dsb_segsplit->value() != 0 ? fmt ("-l %1", ui.dsb_segsplit->value()) : ""), + (ui.sb_bias->value() != 0 ? fmt ("-s %1", ui.sb_bias->value()) : ""), + in1DATName, + in2DATName, + outDATName + }); + + writeColorGroup (in1Col, in1DATName); + writeColorGroup (in2Col, in2DATName); + + if (!runUtilityProcess (Coverer, prog_coverer, argv)) + return; + + insertOutput (outDATName, false, {}); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Isecalc, 0) +{ setlocale (LC_ALL, "C"); + + if (!checkProgPath (Isecalc)) + return; + + Ui::IsecalcUI ui; + QDialog* dlg = new QDialog; + ui.setupUi (dlg); + + makeColorComboBox (ui.cmb_col1); + makeColorComboBox (ui.cmb_col2); + + int in1Col, in2Col; + + // Run the dialog and validate input + forever + { if (!dlg->exec()) + return; + + in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt(), + in2Col = ui.cmb_col1->itemData (ui.cmb_col2->currentIndex()).toInt(); + + if (in1Col == in2Col) + { critical ("Cannot use the same color group for both input and cutter!"); + continue; + } + + break; + } + + QTemporaryFile in1dat, in2dat, outdat; + str in1DATName, in2DATName, outDATName; + + if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName)) + return; + + str argv = join ( + { in1DATName, + in2DATName, + outDATName + }); + + writeColorGroup (in1Col, in1DATName); + writeColorGroup (in2Col, in2DATName); + runUtilityProcess (Isecalc, prog_isecalc, argv); + insertOutput (outDATName, false, {}); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Edger2, 0) +{ setlocale (LC_ALL, "C"); + + if (!checkProgPath (Edger2)) + return; + + QDialog* dlg = new QDialog; + Ui::Edger2Dialog ui; + ui.setupUi (dlg); + + if (!dlg->exec()) + return; + + QTemporaryFile in, out; + str inName, outName; + + if (!mkTempFile (in, inName) || !mkTempFile (out, outName)) + return; + + int unmatched = ui.unmatched->currentIndex(); + + str argv = join ( + { fmt ("-p %1", ui.precision->value()), + fmt ("-af %1", ui.flatAngle->value()), + fmt ("-ac %1", ui.condAngle->value()), + fmt ("-ae %1", ui.edgeAngle->value()), + ui.delLines->isChecked() ? "-de" : "", + ui.delCondLines->isChecked() ? "-dc" : "", + ui.colored->isChecked() ? "-c" : "", + ui.bfc->isChecked() ? "-b" : "", + ui.convex->isChecked() ? "-cx" : "", + ui.concave->isChecked() ? "-cv" : "", + unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""), + inName, + outName, + }); + + writeSelection (inName); + + if (!runUtilityProcess (Edger2, prog_edger2, argv)) + return; + + insertOutput (outName, true, {}); +}
--- a/src/extprogs.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,638 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QProcess> -#include <QTemporaryFile> -#include <QDialog> -#include <QDialogButtonBox> -#include <QSpinBox> -#include <QCheckBox> -#include <QComboBox> -#include <QGridLayout> -#include "main.h" -#include "config.h" -#include "misc.h" -#include "gui.h" -#include "document.h" -#include "widgets.h" -#include "history.h" -#include "ui_ytruder.h" -#include "ui_intersector.h" -#include "ui_rectifier.h" -#include "ui_coverer.h" -#include "ui_isecalc.h" -#include "ui_edger2.h" -#include "dialogs.h" - -enum extprog -{ Isecalc, - Intersector, - Coverer, - Ytruder, - Rectifier, - Edger2, -}; - -// ============================================================================= -// ----------------------------------------------------------------------------- -cfg (String, prog_isecalc, ""); -cfg (String, prog_intersector, ""); -cfg (String, prog_coverer, ""); -cfg (String, prog_ytruder, ""); -cfg (String, prog_rectifier, ""); -cfg (String, prog_edger2, ""); - -StringConfig* const g_extProgPaths[] = -{ &prog_isecalc, - &prog_intersector, - &prog_coverer, - &prog_ytruder, - &prog_rectifier, - &prog_edger2, -}; - -#ifndef _WIN32 -cfg (Bool, prog_isecalc_wine, false); -cfg (Bool, prog_intersector_wine, false); -cfg (Bool, prog_coverer_wine, false); -cfg (Bool, prog_ytruder_wine, false); -cfg (Bool, prog_rectifier_wine, false); -cfg (Bool, prog_edger2_wine, false); - -BoolConfig* const g_extProgWine[] = -{ &prog_isecalc_wine, - &prog_intersector_wine, - &prog_coverer_wine, - &prog_ytruder_wine, - &prog_rectifier_wine, - &prog_edger2_wine, -}; -#endif // _WIN32 - -const char* g_extProgNames[] = -{ "Isecalc", - "Intersector", - "Coverer", - "Ytruder", - "Rectifier", - "Edger2" -}; - -// ============================================================================= -// ----------------------------------------------------------------------------- -static bool checkProgPath (const extprog prog) -{ alias path = g_extProgPaths[prog]->value; - - if (path.length() > 0) - return true; - - ExtProgPathPrompt* dlg = new ExtProgPathPrompt (g_extProgNames[prog]); - - if (dlg->exec() && !dlg->getPath().isEmpty()) - { path = dlg->getPath(); - return true; - } - - return false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static str processErrorString (QProcess& proc) -{ switch (proc.error()) - { case QProcess::FailedToStart: - return "Failed to start (check your permissions)"; - - case QProcess::Crashed: - return "Crashed."; - - case QProcess::WriteError: - case QProcess::ReadError: - return "I/O error."; - - case QProcess::UnknownError: - return "Unknown error"; - - case QProcess::Timedout: - return fmt ("Timed out (30 seconds)"); - } - - return ""; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static bool mkTempFile (QTemporaryFile& tmp, str& fname) -{ if (!tmp.open()) - return false; - - fname = tmp.fileName(); - tmp.close(); - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void writeObjects (const QList<LDObject*>& objects, File& f) -{ for (LDObject* obj : objects) - { if (obj->getType() == LDObject::Subfile) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepInline); - - writeObjects (objs, f); - - for (LDObject* obj : objs) - delete obj; - } - else - f.write (obj->raw() + "\r\n"); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void writeObjects (const QList<LDObject*>& objects, str fname) -{ // Write the input file - File f (fname, File::Write); - - if (!f) - { critical (fmt ("Couldn't open temporary file %1 for writing.\n", fname)); - return; - } - - writeObjects (objects, f); - f.close(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void writeSelection (str fname) -{ writeObjects (selection(), fname); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void writeColorGroup (const int colnum, str fname) -{ QList<LDObject*> objects; - - for (LDObject* obj : getCurrentDocument()->getObjects()) - { if (obj->isColored() == false || obj->getColor() != colnum) - continue; - - objects << obj; - } - - writeObjects (objects, fname); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool runUtilityProcess (extprog prog, str path, str argvstr) -{ QTemporaryFile input, output; - str inputname, outputname; - QStringList argv = argvstr.split (" ", QString::SkipEmptyParts); - -#ifndef _WIN32 - if (*g_extProgWine[prog]) - { argv.insert (0, path); - path = "wine"; - } -#endif // _WIN32 - - log ("cmdline: %1 %2\n", path, argv.join (" ")); - - // Temporary files for stdin and stdout - if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname)) - return false; - - QProcess proc; - - // Init stdin - File stdinfp (inputname, File::Write); - - // Begin! - proc.setStandardInputFile (inputname); - proc.start (path, argv); - - // Write an enter, the utility tools all expect one - stdinfp.write ("\n"); - - // Wait while it runs - proc.waitForFinished(); - - str err = ""; - - if (proc.exitStatus() != QProcess::NormalExit) - err = processErrorString (proc); - - // Check the return code - if (proc.exitCode() != 0) - err = fmt ("Program exited abnormally (return code %1).", proc.exitCode()); - - if (err.length() > 0) - { critical (fmt ("%1 failed: %2\n", g_extProgNames[prog], err)); - return false; - } - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void insertOutput (str fname, bool replace, QList<int> colorsToReplace) -{ -#ifndef RELEASE - QFile::copy (fname, "./debug_lastOutput"); -#endif // RELEASE - - // Read the output file - File f (fname, File::Read); - - if (!f) - { critical (fmt ("Couldn't open temporary file %1 for reading.\n", fname)); - return; - } - - QList<LDObject*> objs = loadFileContents (&f, null); - - // If we replace the objects, delete the selection now. - if (replace) - g_win->deleteSelection(); - - for (const int colnum : colorsToReplace) - g_win->deleteByColor (colnum); - - // Insert the new objects - getCurrentDocument()->clearSelection(); - - for (LDObject * obj : objs) - { if (!obj->isScemantic()) - { delete obj; - continue; - } - - getCurrentDocument()->addObject (obj); - obj->select(); - } - - g_win->doFullRefresh(); -} - -// ============================================================================= -// Interface for Ytruder -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Ytruder, 0) -{ setlocale (LC_ALL, "C"); - - if (!checkProgPath (Ytruder)) - return; - - QDialog* dlg = new QDialog; - Ui::YtruderUI ui; - ui.setupUi (dlg); - - if (!dlg->exec()) - return; - - // Read the user's choices - const enum { Distance, Symmetry, Projection, Radial } mode = - ui.mode_distance->isChecked() ? Distance : - ui.mode_symmetry->isChecked() ? Symmetry : - ui.mode_projection->isChecked() ? Projection : Radial; - - const Axis axis = - ui.axis_x->isChecked() ? X : - ui.axis_y->isChecked() ? Y : Z; - - const double depth = ui.planeDepth->value(), - condAngle = ui.condAngle->value(); - - QTemporaryFile indat, outdat; - str inDATName, outDATName; - - // Make temp files for the input and output files - if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) - return; - - // Compose the command-line arguments - str argv = join ( - { (axis == X) ? "-x" : (axis == Y) ? "-y" : "-z", - (mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r", - depth, - "-a", - condAngle, - inDATName, - outDATName - }); - - writeSelection (inDATName); - - if (!runUtilityProcess (Ytruder, prog_ytruder, argv)) - return; - - insertOutput (outDATName, false, {}); -} - -// ============================================================================= -// Rectifier interface -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Rectifier, 0) -{ setlocale (LC_ALL, "C"); - - if (!checkProgPath (Rectifier)) - return; - - QDialog* dlg = new QDialog; - Ui::RectifierUI ui; - ui.setupUi (dlg); - - if (!dlg->exec()) - return; - - QTemporaryFile indat, outdat; - str inDATName, outDATName; - - // Make temp files for the input and output files - if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) - return; - - // Compose arguments - str argv = join ( - { (!ui.cb_condense->isChecked()) ? "-q" : "", - (!ui.cb_subst->isChecked()) ? "-r" : "", - (ui.cb_condlineCheck->isChecked()) ? "-a" : "", - (ui.cb_colorize->isChecked()) ? "-c" : "", - "-t", - ui.dsb_coplthres->value(), - inDATName, - outDATName - }); - - writeSelection (inDATName); - - if (!runUtilityProcess (Rectifier, prog_rectifier, argv)) - return; - - insertOutput (outDATName, true, {}); -} - -// ============================================================================= -// Intersector interface -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Intersector, 0) -{ setlocale (LC_ALL, "C"); - - if (!checkProgPath (Intersector)) - return; - - QDialog* dlg = new QDialog; - Ui::IntersectorUI ui; - ui.setupUi (dlg); - - makeColorComboBox (ui.cmb_incol); - makeColorComboBox (ui.cmb_cutcol); - ui.cb_repeat->setWhatsThis ("If this is set, " APPNAME " runs Intersector a second time with inverse files to cut the " - " cutter group with the input group. Both groups are cut by the intersection."); - ui.cb_edges->setWhatsThis ("Makes " APPNAME " try run Isecalc to create edgelines for the intersection."); - - int inCol, cutCol; - const bool repeatInverse = ui.cb_repeat->isChecked(); - - forever - { if (!dlg->exec()) - return; - - inCol = ui.cmb_incol->itemData (ui.cmb_incol->currentIndex()).toInt(); - cutCol = ui.cmb_cutcol->itemData (ui.cmb_cutcol->currentIndex()).toInt(); - - if (inCol == cutCol) - { critical ("Cannot use the same color group for both input and cutter!"); - continue; - } - - break; - } - - // Five temporary files! - // indat = input group file - // cutdat = cutter group file - // outdat = primary output - // outdat2 = inverse output - // edgesdat = edges output (isecalc) - QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat; - str inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName; - - if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) || - !mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) || - !mkTempFile (edgesdat, edgesDATName)) - { return; - } - - str parms = join ( - { (ui.cb_colorize->isChecked()) ? "-c" : "", - (ui.cb_nocondense->isChecked()) ? "-t" : "", - "-s", - ui.dsb_prescale->value() - }); - - str argv_normal = join ( - { parms, - inDATName, - cutDATName, - outDATName - }); - - str argv_inverse = join ( - { parms, - cutDATName, - inDATName, - outDAT2Name - }); - - writeColorGroup (inCol, inDATName); - writeColorGroup (cutCol, cutDATName); - - if (!runUtilityProcess (Intersector, prog_intersector, argv_normal)) - return; - - insertOutput (outDATName, false, {inCol}); - - if (repeatInverse && runUtilityProcess (Intersector, prog_intersector, argv_inverse)) - insertOutput (outDAT2Name, false, {cutCol}); - - if ( - ui.cb_edges->isChecked() && - checkProgPath (Isecalc) && - runUtilityProcess (Isecalc, prog_isecalc, join ( {inDATName, cutDATName, edgesDATName})) - ) - insertOutput (edgesDATName, false, {}); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Coverer, 0) -{ setlocale (LC_ALL, "C"); - - if (!checkProgPath (Coverer)) - return; - - QDialog* dlg = new QDialog; - Ui::CovererUI ui; - ui.setupUi (dlg); - makeColorComboBox (ui.cmb_col1); - makeColorComboBox (ui.cmb_col2); - - int in1Col, in2Col; - - forever - { if (!dlg->exec()) - return; - - in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt(); - in2Col = ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt(); - - if (in1Col == in2Col) - { critical ("Cannot use the same color group for both input and cutter!"); - continue; - } - - break; - } - - QTemporaryFile in1dat, in2dat, outdat; - str in1DATName, in2DATName, outDATName; - - if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName)) - return; - - str argv = join ( - { (ui.cb_oldsweep->isChecked() ? "-s" : ""), - (ui.cb_reverse->isChecked() ? "-r" : ""), - (ui.dsb_segsplit->value() != 0 ? fmt ("-l %1", ui.dsb_segsplit->value()) : ""), - (ui.sb_bias->value() != 0 ? fmt ("-s %1", ui.sb_bias->value()) : ""), - in1DATName, - in2DATName, - outDATName - }); - - writeColorGroup (in1Col, in1DATName); - writeColorGroup (in2Col, in2DATName); - - if (!runUtilityProcess (Coverer, prog_coverer, argv)) - return; - - insertOutput (outDATName, false, {}); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Isecalc, 0) -{ setlocale (LC_ALL, "C"); - - if (!checkProgPath (Isecalc)) - return; - - Ui::IsecalcUI ui; - QDialog* dlg = new QDialog; - ui.setupUi (dlg); - - makeColorComboBox (ui.cmb_col1); - makeColorComboBox (ui.cmb_col2); - - int in1Col, in2Col; - - // Run the dialog and validate input - forever - { if (!dlg->exec()) - return; - - in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt(), - in2Col = ui.cmb_col1->itemData (ui.cmb_col2->currentIndex()).toInt(); - - if (in1Col == in2Col) - { critical ("Cannot use the same color group for both input and cutter!"); - continue; - } - - break; - } - - QTemporaryFile in1dat, in2dat, outdat; - str in1DATName, in2DATName, outDATName; - - if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName)) - return; - - str argv = join ( - { in1DATName, - in2DATName, - outDATName - }); - - writeColorGroup (in1Col, in1DATName); - writeColorGroup (in2Col, in2DATName); - runUtilityProcess (Isecalc, prog_isecalc, argv); - insertOutput (outDATName, false, {}); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Edger2, 0) -{ setlocale (LC_ALL, "C"); - - if (!checkProgPath (Edger2)) - return; - - QDialog* dlg = new QDialog; - Ui::Edger2Dialog ui; - ui.setupUi (dlg); - - if (!dlg->exec()) - return; - - QTemporaryFile in, out; - str inName, outName; - - if (!mkTempFile (in, inName) || !mkTempFile (out, outName)) - return; - - int unmatched = ui.unmatched->currentIndex(); - - str argv = join ( - { fmt ("-p %1", ui.precision->value()), - fmt ("-af %1", ui.flatAngle->value()), - fmt ("-ac %1", ui.condAngle->value()), - fmt ("-ae %1", ui.edgeAngle->value()), - ui.delLines->isChecked() ? "-de" : "", - ui.delCondLines->isChecked() ? "-dc" : "", - ui.colored->isChecked() ? "-c" : "", - ui.bfc->isChecked() ? "-b" : "", - ui.convex->isChecked() ? "-cx" : "", - ui.concave->isChecked() ? "-cv" : "", - unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""), - inName, - outName, - }); - - writeSelection (inName); - - if (!runUtilityProcess (Edger2, prog_edger2, argv)) - return; - - insertOutput (outName, true, {}); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gldraw.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,1995 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QGLWidget> +#include <QWheelEvent> +#include <QMouseEvent> +#include <QContextMenuEvent> +#include <QInputDialog> +#include <QToolTip> +#include <QTimer> +#include <GL/glu.h> + +#include "main.h" +#include "config.h" +#include "document.h" +#include "gldraw.h" +#include "colors.h" +#include "gui.h" +#include "misc.h" +#include "history.h" +#include "dialogs.h" +#include "addObjectDialog.h" +#include "messagelog.h" +#include "primitives.h" +#include "moc_gldraw.cpp" + +static const LDFixedCameraInfo g_FixedCameras[6] = +{ {{ 1, 0, 0 }, X, Z, false, false }, + {{ 0, 0, 0 }, X, Y, false, true }, + {{ 0, 1, 0 }, Z, Y, true, true }, + {{ -1, 0, 0 }, X, Z, false, true }, + {{ 0, 0, 0 }, X, Y, true, true }, + {{ 0, -1, 0 }, Z, Y, false, true }, +}; + +static const matrix g_circleDrawTransforms[3] = +{ { 2, 0, 0, 0, 1, 0, 0, 0, 2 }, + { 2, 0, 0, 0, 0, 2, 0, 1, 0 }, + { 0, 1, 0, 2, 0, 0, 0, 0, 2 }, +}; + +cfg (String, gl_bgcolor, "#CCCCD9"); +cfg (String, gl_maincolor, "#707078"); +cfg (Float, gl_maincolor_alpha, 1.0); +cfg (Int, gl_linethickness, 2); +cfg (Bool, gl_colorbfc, false); +cfg (Int, gl_camera, GLRenderer::EFreeCamera); +cfg (Bool, gl_blackedges, false); +cfg (Bool, gl_axes, false); +cfg (Bool, gl_wireframe, false); +cfg (Bool, gl_logostuds, false); +cfg (Bool, gl_aa, true); + +// argh +const char* g_CameraNames[7] = +{ QT_TRANSLATE_NOOP ("GLRenderer", "Top"), + QT_TRANSLATE_NOOP ("GLRenderer", "Front"), + QT_TRANSLATE_NOOP ("GLRenderer", "Left"), + QT_TRANSLATE_NOOP ("GLRenderer", "Bottom"), + QT_TRANSLATE_NOOP ("GLRenderer", "Back"), + QT_TRANSLATE_NOOP ("GLRenderer", "Right"), + QT_TRANSLATE_NOOP ("GLRenderer", "Free") +}; + +const GL::EFixedCamera g_Cameras[7] = +{ GL::ETopCamera, + GL::EFrontCamera, + GL::ELeftCamera, + GL::EBottomCamera, + GL::EBackCamera, + GL::ERightCamera, + GL::EFreeCamera +}; + +const struct LDGLAxis +{ const QColor col; + const vertex vert; +} g_GLAxes[3] = +{ { QColor (255, 0, 0), vertex (10000, 0, 0) }, + { QColor (80, 192, 0), vertex (0, 10000, 0) }, + { QColor (0, 160, 192), vertex (0, 0, 10000) }, +}; + +static bool g_glInvert = false; +static QList<int> g_warnedColors; + +// ============================================================================= +// ----------------------------------------------------------------------------- +GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) +{ m_Picking = m_rangepick = false; + m_camera = (GL::EFixedCamera) gl_camera.value; + m_drawToolTip = false; + m_EditMode = ESelectMode; + m_rectdraw = false; + m_panning = false; + setFile (null); + setDrawOnly (false); + setMessageLog (null); + m_width = m_height = -1; + m_hoverpos = g_origin; + + m_toolTipTimer = new QTimer (this); + m_toolTipTimer->setSingleShot (true); + connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer())); + + m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); + m_thinBorderPen = m_thickBorderPen; + m_thinBorderPen.setWidth (1); + + // Init camera icons + for (const GL::EFixedCamera cam : g_Cameras) + { str iconname = fmt ("camera-%1", tr (g_CameraNames[cam]).toLower()); + + CameraIcon* info = &m_cameraIcons[cam]; + info->img = new QPixmap (getIcon (iconname)); + info->cam = cam; + } + + for (int i = 0; i < 6; ++i) + { m_overlays[i].img = null; + m_depthValues[i] = 0.0f; + } + + calcCameraIcons(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +GLRenderer::~GLRenderer() +{ for (int i = 0; i < 6; ++i) + delete m_overlays[i].img; + + for (CameraIcon& info : m_cameraIcons) + delete info.img; +} + +// ============================================================================= +// Calculates the "hitboxes" of the camera icons so that we can tell when the +// cursor is pointing at the camera icon. +// ----------------------------------------------------------------------------- +void GLRenderer::calcCameraIcons() +{ int i = 0; + + for (CameraIcon& info : m_cameraIcons) + { // MATH + const long x1 = (m_width - (info.cam != EFreeCamera ? 48 : 16)) + ((i % 3) * 16) - 1, + y1 = ((i / 3) * 16) + 1; + + info.srcRect = QRect (0, 0, 16, 16); + info.destRect = QRect (x1, y1, 16, 16); + info.selRect = QRect ( + info.destRect.x(), + info.destRect.y(), + info.destRect.width() + 1, + info.destRect.height() + 1 + ); + + ++i; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::initGLData() +{ glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable (GL_POLYGON_OFFSET_FILL); + glPolygonOffset (1.0f, 1.0f); + + glEnable (GL_DEPTH_TEST); + glShadeModel (GL_SMOOTH); + glEnable (GL_MULTISAMPLE); + + if (gl_aa) + { glEnable (GL_LINE_SMOOTH); + glEnable (GL_POLYGON_SMOOTH); + glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); + glHint (GL_POLYGON_SMOOTH_HINT, GL_NICEST); + } else + { glDisable (GL_LINE_SMOOTH); + glDisable (GL_POLYGON_SMOOTH); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::resetAngles() +{ rot (X) = 30.0f; + rot (Y) = 325.f; + pan (X) = pan (Y) = rot (Z) = 0.0f; + zoomToFit(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::resetAllAngles() +{ EFixedCamera oldcam = camera(); + + for (int i = 0; i < 7; ++i) + { setCamera ((EFixedCamera) i); + resetAngles(); + } + + setCamera (oldcam); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::initializeGL() +{ setBackground(); + + glLineWidth (gl_linethickness); + + setAutoFillBackground (false); + setMouseTracking (true); + setFocusPolicy (Qt::WheelFocus); + compileAllObjects(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QColor GLRenderer::getMainColor() +{ QColor col (gl_maincolor); + + if (!col.isValid()) + return QColor (0, 0, 0); + + col.setAlpha (gl_maincolor_alpha * 255.f); + return col; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::setBackground() +{ QColor col (gl_bgcolor); + + if (!col.isValid()) + return; + + col.setAlpha (255); + + m_darkbg = luma (col) < 80; + m_bgcolor = col; + qglClearColor (col); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::setObjectColor (LDObject* obj, const ListType list) +{ QColor qcol; + + if (!obj->isColored()) + return; + + if (list == GL::PickList) + { // Make the color by the object's ID if we're picking, so we can make the + // ID again from the color we get from the picking results. Be sure to use + // the top level parent's index since we want a subfile's children point + // to the subfile itself. + long i = obj->topLevelParent()->getID(); + + // Calculate a color based from this index. This method caters for + // 16777216 objects. I don't think that'll be exceeded anytime soon. :) + // ATM biggest is 53588.dat with 12600 lines. + double r = (i / (256 * 256)) % 256, + g = (i / 256) % 256, + b = i % 256; + + qglColor (QColor (r, g, b)); + return; + } + + if ((list == BFCFrontList || list == BFCBackList) && + obj->getType() != LDObject::Line && + obj->getType() != LDObject::CondLine) + { if (list == GL::BFCFrontList) + qcol = QColor (40, 192, 0); + else + qcol = QColor (224, 0, 0); + } + else + { if (obj->getColor() == maincolor) + qcol = getMainColor(); + else + { LDColor* col = getColor (obj->getColor()); + + if (col) + qcol = col->faceColor; + } + + if (obj->getColor() == edgecolor) + { qcol = luma (m_bgcolor) < 40 ? QColor (64, 64, 64) : Qt::black; + LDColor* col; + + if (!gl_blackedges && obj->getParent() && (col = getColor (obj->getParent()->getColor()))) + qcol = col->edgeColor; + } + + if (qcol.isValid() == false) + { // The color was unknown. Use main color to make the object at least + // not appear pitch-black. + if (obj->getColor() != edgecolor) + qcol = getMainColor(); + + // Warn about the unknown colors, but only once. + for (int i : g_warnedColors) + if (obj->getColor() == i) + return; + + log ("%1: Unknown color %2!\n", __func__, obj->getColor()); + g_warnedColors << obj->getColor(); + return; + } + } + + long r = qcol.red(), + g = qcol.green(), + b = qcol.blue(), + a = qcol.alpha(); + + if (obj->topLevelParent()->isSelected()) + { // Brighten it up for the select list. + const uchar add = 51; + + r = min (r + add, 255l); + g = min (g + add, 255l); + b = min (b + add, 255l); + } + + glColor4f ( + ((double) r) / 255.0f, + ((double) g) / 255.0f, + ((double) b) / 255.0f, + ((double) a) / 255.0f); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::refresh() +{ update(); + swapBuffers(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::hardRefresh() +{ compileAllObjects(); + refresh(); + + glLineWidth (gl_linethickness); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::resizeGL (int w, int h) +{ m_width = w; + m_height = h; + + calcCameraIcons(); + + glViewport (0, 0, w, h); + glMatrixMode (GL_PROJECTION); + glLoadIdentity(); + gluPerspective (45.0f, (double) w / (double) h, 1.0f, 10000.0f); + glMatrixMode (GL_MODELVIEW); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::drawGLScene() +{ if (getFile() == null) + return; + + if (gl_wireframe && !isPicking()) + glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + + glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable (GL_DEPTH_TEST); + + if (m_camera != EFreeCamera) + { glMatrixMode (GL_PROJECTION); + glPushMatrix(); + + glLoadIdentity(); + glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f); + glTranslatef (pan (X), pan (Y), 0.0f); + + if (m_camera != EFrontCamera && m_camera != EBackCamera) + { glRotatef (90.0f, g_FixedCameras[camera()].glrotate[0], + g_FixedCameras[camera()].glrotate[1], + g_FixedCameras[camera()].glrotate[2]); + } + + // Back camera needs to be handled differently + if (m_camera == GLRenderer::EBackCamera) + { glRotatef (180.0f, 1.0f, 0.0f, 0.0f); + glRotatef (180.0f, 0.0f, 0.0f, 1.0f); + } + } + else + { glMatrixMode (GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glTranslatef (0.0f, 0.0f, -2.0f); + glTranslatef (pan (X), pan (Y), -zoom()); + glRotatef (rot (X), 1.0f, 0.0f, 0.0f); + glRotatef (rot (Y), 0.0f, 1.0f, 0.0f); + glRotatef (rot (Z), 0.0f, 0.0f, 1.0f); + } + + const GL::ListType list = (!isDrawOnly() && isPicking()) ? PickList : NormalList; + + if (gl_colorbfc && !isPicking() && !isDrawOnly()) + { glEnable (GL_CULL_FACE); + + for (LDObject* obj : getFile()->getObjects()) + { if (obj->isHidden()) + continue; + + glCullFace (GL_BACK); + glCallList (obj->glLists[BFCFrontList]); + + glCullFace (GL_FRONT); + glCallList (obj->glLists[BFCBackList]); + } + + glDisable (GL_CULL_FACE); + } + else + { for (LDObject* obj : getFile()->getObjects()) + { if (obj->isHidden()) + continue; + + glCallList (obj->glLists[list]); + } + } + + if (gl_axes && !isPicking() && !isDrawOnly()) + glCallList (m_axeslist); + + glPopMatrix(); + glMatrixMode (GL_MODELVIEW); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +// This converts a 2D point on the screen to a 3D point in the model. If 'snap' +// is true, the 3D point will snap to the current grid. +// ----------------------------------------------------------------------------- +vertex GLRenderer::coordconv2_3 (const QPoint& pos2d, bool snap) const +{ assert (camera() != EFreeCamera); + + vertex pos3d; + const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; + const Axis axisX = cam->axisX; + const Axis axisY = cam->axisY; + const int negXFac = cam->negX ? -1 : 1, + negYFac = cam->negY ? -1 : 1; + + // Calculate cx and cy - these are the LDraw unit coords the cursor is at. + double cx = (-m_virtWidth + ((2 * pos2d.x() * m_virtWidth) / m_width) - pan (X)); + double cy = (m_virtHeight - ((2 * pos2d.y() * m_virtHeight) / m_height) - pan (Y)); + + if (snap) + { cx = Grid::snap (cx, (Grid::Config) axisX); + cy = Grid::snap (cy, (Grid::Config) axisY); + } + + cx *= negXFac; + cy *= negYFac; + + str tmp; + // Create the vertex from the coordinates + pos3d[axisX] = tmp.sprintf ("%.3f", cx).toDouble(); + pos3d[axisY] = tmp.sprintf ("%.3f", cy).toDouble(); + pos3d[3 - axisX - axisY] = getDepthValue(); + return pos3d; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +// Inverse operation for the above - convert a 3D position to a 2D screen +// position +// ----------------------------------------------------------------------------- +QPoint GLRenderer::coordconv3_2 (const vertex& pos3d) const +{ GLfloat m[16]; + const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; + const Axis axisX = cam->axisX; + const Axis axisY = cam->axisY; + const int negXFac = cam->negX ? -1 : 1, + negYFac = cam->negY ? -1 : 1; + + glGetFloatv (GL_MODELVIEW_MATRIX, m); + + const double x = pos3d.x(); + const double y = pos3d.y(); + const double z = pos3d.z(); + + vertex transformed; + transformed[X] = (m[0] * x) + (m[1] * y) + (m[2] * z) + m[3]; + transformed[Y] = (m[4] * x) + (m[5] * y) + (m[6] * z) + m[7]; + transformed[Z] = (m[8] * x) + (m[9] * y) + (m[10] * z) + m[11]; + + double rx = (((transformed[axisX] * negXFac) + m_virtWidth + pan (X)) * m_width) / (2 * m_virtWidth); + double ry = (((transformed[axisY] * negYFac) - m_virtHeight + pan (Y)) * m_height) / (2 * m_virtHeight); + + return QPoint (rx, -ry); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::paintEvent (QPaintEvent* ev) +{ Q_UNUSED (ev) + + makeCurrent(); + m_virtWidth = zoom(); + m_virtHeight = (m_height * m_virtWidth) / m_width; + + initGLData(); + drawGLScene(); + + const QPen textpen = getTextPen(); + const QBrush polybrush (QColor (64, 192, 0, 128)); + QPainter paint (this); + QFontMetrics metrics = QFontMetrics (QFont()); + paint.setRenderHint (QPainter::HighQualityAntialiasing); + + // If we wish to only draw the brick, stop here + if (isDrawOnly()) + return; + + if (m_camera != EFreeCamera && !isPicking()) + { // Paint the overlay image if we have one + const LDGLOverlay& overlay = m_overlays[m_camera]; + + if (overlay.img != null) + { QPoint v0 = coordconv3_2 (m_overlays[m_camera].v0), + v1 = coordconv3_2 (m_overlays[m_camera].v1); + + QRect targRect (v0.x(), v0.y(), abs (v1.x() - v0.x()), abs (v1.y() - v0.y())), + srcRect (0, 0, overlay.img->width(), overlay.img->height()); + paint.drawImage (targRect, *overlay.img, srcRect); + } + + // Paint the coordinates onto the screen. + str text = fmt (tr ("X: %1, Y: %2, Z: %3"), m_hoverpos[X], m_hoverpos[Y], m_hoverpos[Z]); + + QFontMetrics metrics = QFontMetrics (font()); + QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); + + paint.setPen (getTextPen()); + paint.drawText (m_width - textSize.width(), m_height - 16, textSize.width(), + textSize.height(), Qt::AlignCenter, text); + + QPen linepen = m_thinBorderPen; + linepen.setWidth (2); + linepen.setColor (luma (m_bgcolor) < 40 ? Qt::white : Qt::black); + + // If we're drawing, draw the vertices onto the screen. + if (getEditMode() == EDrawMode) + { int numverts = 4; + + if (!m_rectdraw) + numverts = m_drawedVerts.size() + 1; + + if (numverts > 0) + { QPoint poly[4]; + vertex polyverts[4]; + + if (!m_rectdraw) + { uchar i = 0; + + for (vertex& vert : m_drawedVerts) + { poly[i] = coordconv3_2 (vert); + polyverts[i] = vert; + ++i; + } + + // Draw the cursor vertex as the last one in the list. + if (numverts <= 4) + { poly[i] = coordconv3_2 (m_hoverpos); + polyverts[i] = m_hoverpos; + } + else + { numverts = 4; + } + } + else + { if (m_drawedVerts.size() > 0) + { // Get vertex information from m_rectverts + for (int i = 0; i < numverts; ++i) + { polyverts[i] = m_rectverts[i]; + poly[i] = coordconv3_2 (polyverts[i]); + } + } + else + { poly[0] = coordconv3_2 (m_hoverpos); + polyverts[0] = m_hoverpos; + } + } + + // Draw the polygon-to-be + paint.setPen (linepen); + paint.setBrush (polybrush); + paint.drawPolygon (poly, numverts); + + // Draw vertex blips + for (int i = 0; i < numverts; ++i) + { QPoint& blip = poly[i]; + drawBlip (paint, blip); + + // Draw their coordinates + paint.drawText (blip.x(), blip.y() - 8, polyverts[i].stringRep (true)); + } + } + } + elif (getEditMode() == ECircleMode) + { // If we have not specified the center point of the circle yet, preview it on the screen. + if (m_drawedVerts.isEmpty()) + drawBlip (paint, coordconv3_2 (m_hoverpos)); + else + { QVector<vertex> verts, verts2; + const double dist0 = getCircleDrawDist (0), + dist1 = (m_drawedVerts.size() >= 2) ? getCircleDrawDist (1) : -1; + const int segs = lores; + const double angleUnit = (2 * pi) / segs; + Axis relX, relY; + QVector<QPoint> ringpoints, circlepoints, circle2points; + + getRelativeAxes (relX, relY); + + // Calculate the preview positions of vertices + for (int i = 0; i < segs; ++i) + { vertex v = g_origin; + v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist0); + v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist0); + verts << v; + + if (dist1 != -1) + { v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist1); + v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist1); + verts2 << v; + } + } + + int i = 0; + for (const vertex& v : verts + verts2) + { // Calculate the 2D point of the vertex + QPoint point = coordconv3_2 (v); + + // Draw a green blip at where it is + drawBlip (paint, point); + + // Add it to the list of points for the green ring fill. + ringpoints << point; + + // Also add the circle points to separate lists + if (i < verts.size()) + circlepoints << point; + else + circle2points << point; + + ++i; + } + + // Insert the first point as the seventeenth one so that + // the ring polygon is closed properly. + if (ringpoints.size() >= 16) + ringpoints.insert (16, ringpoints[0]); + + // Same for the outer ring. Note that the indices are offset by 1 + // because of the insertion done above bumps the values. + if (ringpoints.size() >= 33) + ringpoints.insert (33, ringpoints[17]); + + // Draw the ring + paint.setBrush ((m_drawedVerts.size() >= 2) ? polybrush : Qt::NoBrush); + paint.setPen (Qt::NoPen); + paint.drawPolygon (QPolygon (ringpoints)); + + // Draw the circles + paint.setBrush (Qt::NoBrush); + paint.setPen (linepen); + paint.drawPolygon (QPolygon (circlepoints)); + paint.drawPolygon (QPolygon (circle2points)); + + { // Draw the current radius in the middle of the circle. + QPoint origin = coordconv3_2 (m_drawedVerts[0]); + str label = str::number (dist0); + paint.setPen (textpen); + paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label); + + if (m_drawedVerts.size() >= 2) + { label = str::number (dist1); + paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y() + metrics.height(), label); + } + } + } + } + } + + // Camera icons + if (!isPicking()) + { // Draw a background for the selected camera + paint.setPen (m_thinBorderPen); + paint.setBrush (QBrush (QColor (0, 128, 160, 128))); + paint.drawRect (m_cameraIcons[camera()].selRect); + + // Draw the actual icons + for (CameraIcon& info : m_cameraIcons) + { // Don't draw the free camera icon when in draw mode + if (&info == &m_cameraIcons[GL::EFreeCamera] && getEditMode() != ESelectMode) + continue; + + paint.drawPixmap (info.destRect, *info.img, info.srcRect); + } + + str fmtstr = tr ("%1 Camera"); + + // Draw a label for the current camera in the bottom left corner + { const int margin = 4; + + str label; + label = fmt (fmtstr, tr (g_CameraNames[camera()])); + paint.setPen (textpen); + paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label); + } + + // Tool tips + if (m_drawToolTip) + { if (m_cameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false) + m_drawToolTip = false; + else + { str label = fmt (fmtstr, tr (g_CameraNames[m_toolTipCamera])); + QToolTip::showText (m_globalpos, label); + } + } + } + + // Message log + if (getMessageLog()) + { int y = 0; + const int margin = 2; + QColor penColor = getTextPen(); + + for (const MessageManager::Line& line : getMessageLog()->getLines()) + { penColor.setAlphaF (line.alpha); + paint.setPen (penColor); + paint.drawText (QPoint (margin, y + margin + metrics.ascent()), line.text); + y += metrics.height(); + } + } + + // If we're range-picking, draw a rectangle encompassing the selection area. + if (m_rangepick && !isPicking() && m_totalmove >= 10) + { int x0 = m_rangeStart.x(), + y0 = m_rangeStart.y(), + x1 = m_pos.x(), + y1 = m_pos.y(); + + QRect rect (x0, y0, x1 - x0, y1 - y0); + QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF"); + fillColor.setAlphaF (0.2f); + + paint.setPen (m_thickBorderPen); + paint.setBrush (QBrush (fillColor)); + paint.drawRect (rect); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::drawBlip (QPainter& paint, QPoint pos) const +{ QPen pen = m_thinBorderPen; + const int blipsize = 8; + pen.setWidth (1); + paint.setPen (pen); + paint.setBrush (QColor (64, 192, 0)); + paint.drawEllipse (pos.x() - blipsize / 2, pos.y() - blipsize / 2, blipsize, blipsize); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QColor GLRenderer::getTextPen () const +{ return m_darkbg ? Qt::white : Qt::black; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::compileAllObjects() +{ if (!getFile()) + return; + + // Compiling all is a big job, use a busy cursor + setCursor (Qt::BusyCursor); + + m_knownVerts.clear(); + + for (LDObject* obj : getFile()->getObjects()) + compileObject (obj); + + // Compile axes + glDeleteLists (m_axeslist, 1); + m_axeslist = glGenLists (1); + glNewList (m_axeslist, GL_COMPILE); + glBegin (GL_LINES); + + for (const LDGLAxis& ax : g_GLAxes) + { qglColor (ax.col); + compileVertex (ax.vert); + compileVertex (-ax.vert); + } + + glEnd(); + glEndList(); + + setCursor (Qt::ArrowCursor); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype) +{ glBegin (gltype); + + const int numverts = (obj->getType() != LDObject::CondLine) ? obj->vertices() : 2; + + if (g_glInvert == false) + for (int i = 0; i < numverts; ++i) + compileVertex (obj->getVertex (i)); + else + for (int i = numverts - 1; i >= 0; --i) + compileVertex (obj->getVertex (i)); + + glEnd(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::compileList (LDObject* obj, const GLRenderer::ListType list) +{ setObjectColor (obj, list); + + switch (obj->getType()) + { case LDObject::Line: + compileSubObject (obj, GL_LINES); + break; + + case LDObject::CondLine: + + // Draw conditional lines with a dash pattern - however, use a full + // line when drawing a pick list to make selecting them easier. + if (list != GL::PickList) + { glLineStipple (1, 0x6666); + glEnable (GL_LINE_STIPPLE); + } + + compileSubObject (obj, GL_LINES); + + glDisable (GL_LINE_STIPPLE); + break; + + case LDObject::Triangle: + compileSubObject (obj, GL_TRIANGLES); + break; + + case LDObject::Quad: + compileSubObject (obj, GL_QUADS); + break; + + case LDObject::Subfile: + { LDSubfile* ref = static_cast<LDSubfile*> (obj); + QList<LDObject*> objs; + + objs = ref->inlineContents ( + LDSubfile::DeepInline | + LDSubfile::CacheInline | + LDSubfile::RendererInline); + bool oldinvert = g_glInvert; + + if (ref->getTransform().getDeterminant() < 0) + g_glInvert = !g_glInvert; + + LDObject* prev = ref->prev(); + + if (prev && prev->getType() == LDObject::BFC && static_cast<LDBFC*> (prev)->type == LDBFC::InvertNext) + g_glInvert = !g_glInvert; + + for (LDObject * obj : objs) + { compileList (obj, list); + delete obj; + } + + g_glInvert = oldinvert; + } + break; + + default: + break; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::compileVertex (const vertex& vrt) +{ glVertex3d (vrt[X], -vrt[Y], -vrt[Z]); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::clampAngle (double& angle) const +{ while (angle < 0) + angle += 360.0; + + while (angle > 360.0) + angle -= 360.0; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::addDrawnVertex (vertex pos) +{ // If we picked an already-existing vertex, stop drawing + if (getEditMode() != ECircleMode) + { for (vertex& vert : m_drawedVerts) + { if (vert == pos) + { endDraw (true); + return; + } + } + } + + m_drawedVerts << pos; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::mouseReleaseEvent (QMouseEvent* ev) +{ const bool wasLeft = (m_lastButtons & Qt::LeftButton) && ! (ev->buttons() & Qt::LeftButton), + wasRight = (m_lastButtons & Qt::RightButton) && ! (ev->buttons() & Qt::RightButton), + wasMid = (m_lastButtons & Qt::MidButton) && ! (ev->buttons() & Qt::MidButton); + + if (m_panning) + m_panning = false; + + if (wasLeft) + { // Check if we selected a camera icon + if (!m_rangepick) + { for (CameraIcon & info : m_cameraIcons) + { if (info.destRect.contains (ev->pos())) + { setCamera (info.cam); + goto end; + } + } + } + + switch (getEditMode()) + { case EDrawMode: + { if (m_rectdraw) + { if (m_drawedVerts.size() == 2) + { endDraw (true); + return; + } + } else + { // If we have 4 verts, stop drawing. + if (m_drawedVerts.size() >= 4) + { endDraw (true); + return; + } + + if (m_drawedVerts.isEmpty() && ev->modifiers() & Qt::ShiftModifier) + { m_rectdraw = true; + updateRectVerts(); + } + } + + addDrawnVertex (m_hoverpos); + } break; + + case ECircleMode: + { if (m_drawedVerts.size() == 3) + { endDraw (true); + return; + } + + addDrawnVertex (m_hoverpos); + } break; + + case ESelectMode: + { if (!isDrawOnly()) + { if (m_totalmove < 10) + m_rangepick = false; + + if (!m_rangepick) + m_addpick = (m_keymods & Qt::ControlModifier); + + if (m_totalmove < 10 || m_rangepick) + pick (ev->x(), ev->y()); + } + } break; + } + + m_rangepick = false; + } + + if (wasMid && getEditMode() != ESelectMode && m_drawedVerts.size() < 4 && m_totalmove < 10) + { // Find the closest vertex to our cursor + double mindist = 1024.0f; + vertex closest; + bool valid = false; + + QPoint curspos = coordconv3_2 (m_hoverpos); + + for (const vertex& pos3d: m_knownVerts) + { QPoint pos2d = coordconv3_2 (pos3d); + + // Measure squared distance + const double dx = abs (pos2d.x() - curspos.x()), + dy = abs (pos2d.y() - curspos.y()), + distsq = (dx * dx) + (dy * dy); + + if (distsq >= 1024.0f) // 32.0f ** 2 + continue; // too far away + + if (distsq < mindist) + { mindist = distsq; + closest = pos3d; + valid = true; + + // If it's only 4 pixels away, I think we found our vertex now. + if (distsq <= 16.0f) // 4.0f ** 2 + break; + } + } + + if (valid) + addDrawnVertex (closest); + } + + if (wasRight && !m_drawedVerts.isEmpty()) + { // Remove the last vertex + m_drawedVerts.removeLast(); + + if (m_drawedVerts.isEmpty()) + m_rectdraw = false; + } + +end: + update(); + m_totalmove = 0; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::mousePressEvent (QMouseEvent* ev) +{ m_totalmove = 0; + + if (ev->modifiers() & Qt::ControlModifier) + { m_rangepick = true; + m_rangeStart.setX (ev->x()); + m_rangeStart.setY (ev->y()); + m_addpick = (m_keymods & Qt::AltModifier); + ev->accept(); + } + + m_lastButtons = ev->buttons(); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::mouseMoveEvent (QMouseEvent* ev) +{ int dx = ev->x() - m_pos.x(); + int dy = ev->y() - m_pos.y(); + m_totalmove += abs (dx) + abs (dy); + + const bool left = ev->buttons() & Qt::LeftButton, + mid = ev->buttons() & Qt::MidButton, + shift = ev->modifiers() & Qt::ShiftModifier; + + if (mid || (left && shift)) + { pan (X) += 0.03f * dx * (zoom() / 7.5f); + pan (Y) -= 0.03f * dy * (zoom() / 7.5f); + m_panning = true; + } elif (left && !m_rangepick && camera() == EFreeCamera) + { rot (X) = rot (X) + dy; + rot (Y) = rot (Y) + dx; + + clampAngle (rot (X)); + clampAngle (rot (Y)); + } + + // Start the tool tip timer + if (!m_drawToolTip) + m_toolTipTimer->start (500); + + // Update 2d position + m_pos = ev->pos(); + m_globalpos = ev->globalPos(); + + // Calculate 3d position of the cursor + m_hoverpos = (camera() != EFreeCamera) ? coordconv2_3 (m_pos, true) : g_origin; + + // Update rect vertices since m_hoverpos may have changed + updateRectVerts(); + + update(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::keyPressEvent (QKeyEvent* ev) +{ m_keymods = ev->modifiers(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::keyReleaseEvent (QKeyEvent* ev) +{ m_keymods = ev->modifiers(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::wheelEvent (QWheelEvent* ev) +{ makeCurrent(); + + zoomNotch (ev->delta() > 0); + zoom() = clamp (zoom(), 0.01, 10000.0); + + update(); + ev->accept(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::leaveEvent (QEvent* ev) +{ (void) ev; + m_drawToolTip = false; + m_toolTipTimer->stop(); + update(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::contextMenuEvent (QContextMenuEvent* ev) +{ g_win->spawnContextMenu (ev->globalPos()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::setCamera (const GLRenderer::EFixedCamera cam) +{ m_camera = cam; + gl_camera = (int) cam; + g_win->updateEditModeActions(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::pick (int mouseX, int mouseY) +{ GLint viewport[4]; + makeCurrent(); + + // Use particularly thick lines while picking ease up selecting lines. + glLineWidth (max<double> (gl_linethickness, 6.5f)); + + // Clear the selection if we do not wish to add to it. + if (!m_addpick) + { QList<LDObject*> oldsel = selection(); + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : oldsel) + compileObject (obj); + } + + setPicking (true); + + // Paint the picking scene + glDisable (GL_DITHER); + glClearColor (1.0f, 1.0f, 1.0f, 1.0f); + + drawGLScene(); + + glGetIntegerv (GL_VIEWPORT, viewport); + + int x0 = mouseX, + y0 = mouseY; + int x1, y1; + + // Determine how big an area to read - with range picking, we pick by + // the area given, with single pixel picking, we use an 1 x 1 area. + if (m_rangepick) + { x1 = m_rangeStart.x(); + y1 = m_rangeStart.y(); + } + else + { x1 = x0 + 1; + y1 = y0 + 1; + } + + // x0 and y0 must be less than x1 and y1, respectively. + if (x0 > x1) + qSwap (x0, x1); + + if (y0 > y1) + qSwap (y0, y1); + + // Clamp the values to ensure they're within bounds + x0 = max (0, x0); + y0 = max (0, y0); + x1 = min (x1, m_width); + y1 = min (y1, m_height); + + const int areawidth = (x1 - x0); + const int areaheight = (y1 - y0); + const qint32 numpixels = areawidth * areaheight; + + // Allocate space for the pixel data. + uchar* const pixeldata = new uchar[4 * numpixels]; + uchar* pixelptr = &pixeldata[0]; + + assert (viewport[3] == m_height); + + // Read pixels from the color buffer. + glReadPixels (x0, viewport[3] - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata); + + LDObject* removedObj = null; + + // Go through each pixel read and add them to the selection. + for (qint32 i = 0; i < numpixels; ++i) + { qint32 idx = + (*(pixelptr + 0) * 0x10000) + + (*(pixelptr + 1) * 0x00100) + + (*(pixelptr + 2) * 0x00001); + pixelptr += 4; + + if (idx == 0xFFFFFF) + continue; // White is background; skip + + LDObject* obj = LDObject::fromID (idx); + + // If this is an additive single pick and the object is currently selected, + // we remove it from selection instead. + if (!m_rangepick && m_addpick) + { if (obj->isSelected()) + { obj->unselect(); + removedObj = obj; + break; + } + } + + obj->select(); + } + + delete[] pixeldata; + + // Update everything now. + g_win->updateSelection(); + + // Recompile the objects now to update their color + for (LDObject* obj : selection()) + compileObject (obj); + + if (removedObj) + compileObject (removedObj); + + // Restore line thickness + glLineWidth (gl_linethickness); + + setPicking (false); + m_rangepick = false; + glEnable (GL_DITHER); + + setBackground(); + repaint(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::setEditMode (EditMode const& a) +{ m_EditMode = a; + + switch (a) + { case ESelectMode: + { unsetCursor(); + setContextMenuPolicy (Qt::DefaultContextMenu); + } break; + + case EDrawMode: + case ECircleMode: + { // Cannot draw into the free camera - use top instead. + if (m_camera == EFreeCamera) + setCamera (ETopCamera); + + // Disable the context menu - we need the right mouse button + // for removing vertices. + setContextMenuPolicy (Qt::NoContextMenu); + + // Use the crosshair cursor when drawing. + setCursor (Qt::CrossCursor); + + // Clear the selection when beginning to draw. + QList<LDObject*> priorsel = selection(); + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : priorsel) + compileObject (obj); + + g_win->updateSelection(); + m_drawedVerts.clear(); + } break; + } + + g_win->updateEditModeActions(); + update(); +} + +void GLRenderer::setFile (LDDocument* const& a) +{ m_File = a; + + if (a != null) + initOverlaysFromObjects(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +matrix GLRenderer::getCircleDrawMatrix (double scale) +{ matrix transform = g_circleDrawTransforms[camera() % 3]; + + for (int i = 0; i < 9; ++i) + { if (transform[i] == 2) + transform[i] = scale; + elif (transform[i] == 1 && camera() >= 3) + transform[i] = -1; + } + + return transform; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::endDraw (bool accept) +{ (void) accept; + + // Clean the selection and create the object + QList<vertex>& verts = m_drawedVerts; + QList<LDObject*> objs; + + switch (getEditMode()) + { case EDrawMode: + { if (m_rectdraw) + { LDQuad* quad = new LDQuad; + + // Copy the vertices from m_rectverts + updateRectVerts(); + + for (int i = 0; i < quad->vertices(); ++i) + quad->setVertex (i, m_rectverts[i]); + + quad->setColor (maincolor); + objs << quad; + } + else + { switch (verts.size()) + { case 1: + { // 1 vertex - add a vertex object + LDVertex* obj = new LDVertex; + obj->pos = verts[0]; + obj->setColor (maincolor); + objs << obj; + } break; + + case 2: + { // 2 verts - make a line + LDLine* obj = new LDLine (verts[0], verts[1]); + obj->setColor (edgecolor); + objs << obj; + } break; + + case 3: + case 4: + { LDObject* obj = (verts.size() == 3) ? + static_cast<LDObject*> (new LDTriangle) : + static_cast<LDObject*> (new LDQuad); + + obj->setColor (maincolor); + + for (int i = 0; i < obj->vertices(); ++i) + obj->setVertex (i, verts[i]); + + objs << obj; + } break; + } + } + } break; + + case ECircleMode: + { const int segs = lores, divs = lores; // TODO: make customizable + double dist0 = getCircleDrawDist (0), + dist1 = getCircleDrawDist (1); + LDDocument* refFile = null; + matrix transform; + bool circleOrDisc = false; + + if (dist1 < dist0) + std::swap<double> (dist0, dist1); + + if (dist0 == dist1) + { // If the radii are the same, there's no ring space to fill. Use a circle. + refFile = ::getDocument ("4-4edge.dat"); + transform = getCircleDrawMatrix (dist0); + circleOrDisc = true; + } + elif (dist0 == 0 || dist1 == 0) + { // If either radii is 0, use a disc. + refFile = ::getDocument ("4-4disc.dat"); + transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); + circleOrDisc = true; + } + elif (g_RingFinder (dist0, dist1)) + { // The ring finder found a solution, use that. Add the component rings to the file. + for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents()) + { // Get a ref file for this primitive. If we cannot find it in the + // LDraw library, generate it. + if ((refFile = ::getDocument (radialFileName (::Ring, lores, lores, cmp.num))) == null) + { refFile = generatePrimitive (::Ring, lores, lores, cmp.num); + refFile->setImplicit (false); + } + + LDSubfile* ref = new LDSubfile; + ref->setFileInfo (refFile); + ref->setTransform (getCircleDrawMatrix (cmp.scale)); + ref->setPosition (m_drawedVerts[0]); + ref->setColor (maincolor); + objs << ref; + } + } + else + { // Ring finder failed, last resort: draw the ring with quads + QList<QLineF> c0, c1; + Axis relX, relY, relZ; + getRelativeAxes (relX, relY); + relZ = (Axis) (3 - relX - relY); + double x0 = m_drawedVerts[0][relX], + y0 = m_drawedVerts[0][relY]; + + vertex templ; + templ[relX] = x0; + templ[relY] = y0; + templ[relZ] = getDepthValue(); + + // Calculate circle coords + makeCircle (segs, divs, dist0, c0); + makeCircle (segs, divs, dist1, c1); + + for (int i = 0; i < segs; ++i) + { vertex v0, v1, v2, v3; + v0 = v1 = v2 = v3 = templ; + v0[relX] += c0[i].x1(); + v0[relY] += c0[i].y1(); + v1[relX] += c0[i].x2(); + v1[relY] += c0[i].y2(); + v2[relX] += c1[i].x2(); + v2[relY] += c1[i].y2(); + v3[relX] += c1[i].x1(); + v3[relY] += c1[i].y1(); + + LDQuad* q = new LDQuad (v0, v1, v2, v3); + q->setColor (maincolor); + + // Ensure the quads always are BFC-front towards the camera + if (camera() % 3 <= 0) + q->invert(); + + objs << q; + } + } + + if (circleOrDisc) + { LDSubfile* ref = new LDSubfile; + ref->setFileInfo (refFile); + ref->setTransform (transform); + ref->setPosition (m_drawedVerts[0]); + ref->setColor (maincolor); + objs << ref; + } + } break; + + case ESelectMode: + { // this shouldn't happen + assert (false); + return; + } break; + } + + if (objs.size() > 0) + { g_win->beginAction (null); + + for (LDObject* obj : objs) + { getFile()->addObject (obj); + compileObject (obj); + } + + g_win->refresh(); + g_win->endAction(); + } + + m_drawedVerts.clear(); + m_rectdraw = false; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +double GLRenderer::getCircleDrawDist (int pos) const +{ assert (m_drawedVerts.size() >= pos + 1); + const vertex& v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : m_hoverpos; + Axis relX, relY; + getRelativeAxes (relX, relY); + + const double dx = m_drawedVerts[0][relX] - v1[relX]; + const double dy = m_drawedVerts[0][relY] - v1[relY]; + return sqrt ((dx * dx) + (dy * dy)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const +{ const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; + relX = cam->axisX; + relY = cam->axisY; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static QList<vertex> getVertices (LDObject* obj) +{ QList<vertex> verts; + + if (obj->vertices() >= 2) + { for (int i = 0; i < obj->vertices(); ++i) + verts << obj->getVertex (i); + } elif (obj->getType() == LDObject::Subfile) + { LDSubfile* ref = static_cast<LDSubfile*> (obj); + QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepCacheInline); + + for (LDObject* obj : objs) + { verts << getVertices (obj); + delete obj; + } + } + + return verts; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::compileObject (LDObject* obj) +{ deleteLists (obj); + + for (const GL::ListType listType : g_glListTypes) + { if (isDrawOnly() && listType != GL::NormalList) + continue; + + GLuint list = glGenLists (1); + glNewList (list, GL_COMPILE); + + obj->glLists[listType] = list; + compileList (obj, listType); + + glEndList(); + } + + // Mark in known vertices of this object + QList<vertex> verts = getVertices (obj); + m_knownVerts << verts; + removeDuplicates (m_knownVerts); + + obj->m_glinit = true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +uchar* GLRenderer::getScreencap (int& w, int& h) +{ w = m_width; + h = m_height; + uchar* cap = new uchar[4 * w * h]; + + m_screencap = true; + update(); + m_screencap = false; + + // Capture the pixels + glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap); + + return cap; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::slot_toolTipTimer() +{ // We come here if the cursor has stayed in one place for longer than a + // a second. Check if we're holding it over a camera icon - if so, draw + // a tooltip. +for (CameraIcon & icon : m_cameraIcons) + { if (icon.destRect.contains (m_pos)) + { m_toolTipCamera = icon.cam; + m_drawToolTip = true; + update(); + break; + } + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::deleteLists (LDObject* obj) +{ // Delete the lists but only if they have been initialized + if (!obj->m_glinit) + return; + +for (const GL::ListType listType : g_glListTypes) + glDeleteLists (obj->glLists[listType], 1); + + obj->m_glinit = false; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +Axis GLRenderer::getCameraAxis (bool y, GLRenderer::EFixedCamera camid) +{ if (camid == (GL::EFixedCamera) - 1) + camid = m_camera; + + const LDFixedCameraInfo* cam = &g_FixedCameras[camid]; + return (y) ? cam->axisY : cam->axisX; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool GLRenderer::setupOverlay (EFixedCamera cam, str file, int x, int y, int w, int h) +{ QImage* img = new QImage (file); + LDGLOverlay& info = getOverlay (cam); + + if (img->isNull()) + { critical (tr ("Failed to load overlay image!")); + delete img; + return false; + } + + delete info.img; // delete the old image + + info.fname = file; + info.lw = w; + info.lh = h; + info.ox = x; + info.oy = y; + info.img = img; + + if (info.lw == 0) + info.lw = (info.lh * img->width()) / img->height(); + elif (info.lh == 0) + info.lh = (info.lw * img->height()) / img->width(); + + const Axis x2d = getCameraAxis (false, cam), + y2d = getCameraAxis (true, cam); + const double negXFac = g_FixedCameras[cam].negX ? -1 : 1, + negYFac = g_FixedCameras[cam].negY ? -1 : 1; + + info.v0 = info.v1 = g_origin; + info.v0[x2d] = - (info.ox * info.lw * negXFac) / img->width(); + info.v0[y2d] = (info.oy * info.lh * negYFac) / img->height(); + info.v1[x2d] = info.v0[x2d] + info.lw; + info.v1[y2d] = info.v0[y2d] + info.lh; + + // Set alpha of all pixels to 0.5 + for (long i = 0; i < img->width(); ++i) + for (long j = 0; j < img->height(); ++j) + { uint32 pixel = img->pixel (i, j); + img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF)); + } + + updateOverlayObjects(); + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::clearOverlay() +{ if (camera() == EFreeCamera) + return; + + LDGLOverlay& info = m_overlays[camera()]; + delete info.img; + info.img = null; + + updateOverlayObjects(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::setDepthValue (double depth) +{ assert (camera() < EFreeCamera); + m_depthValues[camera()] = depth; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +double GLRenderer::getDepthValue() const +{ assert (camera() < EFreeCamera); + return m_depthValues[camera()]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +const char* GLRenderer::getCameraName() const +{ return g_CameraNames[camera()]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDGLOverlay& GLRenderer::getOverlay (int newcam) +{ return m_overlays[newcam]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::zoomNotch (bool inward) +{ if (zoom() > 15) + zoom() *= inward ? 0.833f : 1.2f; + else + zoom() += inward ? -1.2f : 1.2f; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::zoomToFit() +{ if (getFile() == null || m_width == -1 || m_height == -1) + { zoom() = 30.0f; + return; + } + + bool lastfilled = false; + bool firstrun = true; + const uint32 white = 0xFFFFFFFF; + bool inward = true; + const int w = m_width, h = m_height; + + glClearColor (1.0, 1.0, 1.0, 1.0); + glDisable (GL_DITHER); + + // Use the pick list while drawing the scene, this way we can tell whether borders + // are background or not. + setPicking (true); + + for (;;) + { if (zoom() > 10000.0 || zoom() < 0.0) + { // Obviously, there's nothing to draw if we get here. + // Default to 30.0f and break out. + zoom() = 30.0; + break; + } + + zoomNotch (inward); + + uchar* cap = new uchar[4 * w * h]; + drawGLScene(); + glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap); + uint32* imgdata = reinterpret_cast<uint32*> (cap); + bool filled = false; + + // Check the top and bottom rows + for (int i = 0; i < w && !filled; ++i) + if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white) + filled = true; + + // Left and right edges + for (int i = 0; i < h && !filled; ++i) + if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white) + filled = true; + + delete[] cap; + + if (firstrun) + { // If this is the first run, we don't know enough to determine + // whether the zoom was to fit, so we mark in our knowledge so + // far and start over. + inward = !filled; + firstrun = false; + } + else + { // If this run filled the screen and the last one did not, the + // last run had ideal zoom - zoom a bit back and we should reach it. + if (filled && !lastfilled) + { zoomNotch (false); + break; + } + + // If this run did not fill the screen and the last one did, we've + // now reached ideal zoom so we're done here. + if (!filled && lastfilled) + break; + + inward = !filled; + } + + lastfilled = filled; + } + + setBackground(); + setPicking (false); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::zoomAllToFit() +{ EFixedCamera oldcam = camera(); + + for (int i = 0; i < 7; ++i) + { setCamera ((EFixedCamera) i); + zoomToFit(); + } + + setCamera (oldcam); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::updateRectVerts() +{ if (!m_rectdraw) + return; + + if (m_drawedVerts.isEmpty()) + { for (int i = 0; i < 4; ++i) + m_rectverts[i] = m_hoverpos; + + return; + } + + vertex v0 = m_drawedVerts[0], + v1 = (m_drawedVerts.size() >= 2) ? m_drawedVerts[1] : m_hoverpos; + + const Axis ax = getCameraAxis (false), + ay = getCameraAxis (true), + az = (Axis) (3 - ax - ay); + + for (int i = 0; i < 4; ++i) + m_rectverts[i][az] = getDepthValue(); + + m_rectverts[0][ax] = v0[ax]; + m_rectverts[0][ay] = v0[ay]; + m_rectverts[1][ax] = v1[ax]; + m_rectverts[1][ay] = v0[ay]; + m_rectverts[2][ax] = v1[ax]; + m_rectverts[2][ay] = v1[ay]; + m_rectverts[3][ax] = v0[ax]; + m_rectverts[3][ay] = v1[ay]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev) +{ if (!(ev->buttons() & Qt::LeftButton) || getEditMode() != ESelectMode) + return; + + pick (ev->x(), ev->y()); + + if (selection().isEmpty()) + return; + + g_win->beginAction (null); + LDObject* obj = selection().first(); + AddObjectDialog::staticDialog (obj->getType(), obj); + g_win->endAction(); + ev->accept(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDOverlay* GLRenderer::findOverlayObject (EFixedCamera cam) +{ LDOverlay* ovlobj = null; + + for (LDObject * obj : getFile()->getObjects()) + { if (obj->getType() == LDObject::Overlay && static_cast<LDOverlay*> (obj)->getCamera() == cam) + { ovlobj = static_cast<LDOverlay*> (obj); + break; + } + } + + return ovlobj; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +// Read in overlays from the current file and update overlay info accordingly. +// ----------------------------------------------------------------------------- +void GLRenderer::initOverlaysFromObjects() +{ for (EFixedCamera cam : g_Cameras) + { if (cam == EFreeCamera) + continue; + + LDGLOverlay& meta = m_overlays[cam]; + LDOverlay* ovlobj = findOverlayObject (cam); + + if (!ovlobj && meta.img) + { delete meta.img; + meta.img = null; + } elif (ovlobj && (!meta.img || meta.fname != ovlobj->getFileName())) + setupOverlay (cam, ovlobj->getFileName(), ovlobj->getX(), + ovlobj->getY(), ovlobj->getWidth(), ovlobj->getHeight()); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::updateOverlayObjects() +{ for (EFixedCamera cam : g_Cameras) + { if (cam == EFreeCamera) + continue; + + LDGLOverlay& meta = m_overlays[cam]; + LDOverlay* ovlobj = findOverlayObject (cam); + + if (!meta.img && ovlobj) + { // If this is the last overlay image, we need to remove the empty space after it as well. + LDObject* nextobj = ovlobj->next(); + + if (nextobj && nextobj->getType() == LDObject::Empty) + { getFile()->forgetObject (nextobj); + delete nextobj; + } + + // If the overlay object was there and the overlay itself is + // not, remove the object. + getFile()->forgetObject (ovlobj); + delete ovlobj; + } elif (meta.img && !ovlobj) + { // Inverse case: image is there but the overlay object is + // not, thus create the object. + ovlobj = new LDOverlay; + + // Find a suitable position to place this object. We want to place + // this into the header, which is everything up to the first scemantic + // object. If we find another overlay object, place this object after + // the last one found. Otherwise, place it before the first schemantic + // object and put an empty object after it (though don't do this if + // there was no schemantic elements at all) + int i, lastOverlay = -1; + bool found = false; + + for (i = 0; i < getFile()->getObjectCount(); ++i) + { LDObject* obj = getFile()->getObject (i); + + if (obj->isScemantic()) + { found = true; + break; + } + + if (obj->getType() == LDObject::Overlay) + lastOverlay = i; + } + + if (lastOverlay != -1) + getFile()->insertObj (lastOverlay + 1, ovlobj); + else + { getFile()->insertObj (i, ovlobj); + + if (found) + getFile()->insertObj (i + 1, new LDEmpty); + } + } + + if (meta.img && ovlobj) + { ovlobj->setCamera (cam); + ovlobj->setFileName (meta.fname); + ovlobj->setX (meta.ox); + ovlobj->setY (meta.oy); + ovlobj->setWidth (meta.lw); + ovlobj->setHeight (meta.lh); + } + } + + if (g_win->R() == this) + g_win->refresh(); +}
--- a/src/gldraw.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1995 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QGLWidget> -#include <QWheelEvent> -#include <QMouseEvent> -#include <QContextMenuEvent> -#include <QInputDialog> -#include <QToolTip> -#include <QTimer> -#include <GL/glu.h> - -#include "main.h" -#include "config.h" -#include "document.h" -#include "gldraw.h" -#include "colors.h" -#include "gui.h" -#include "misc.h" -#include "history.h" -#include "dialogs.h" -#include "addObjectDialog.h" -#include "messagelog.h" -#include "primitives.h" -#include "moc_gldraw.cpp" - -static const LDFixedCameraInfo g_FixedCameras[6] = -{ {{ 1, 0, 0 }, X, Z, false, false }, - {{ 0, 0, 0 }, X, Y, false, true }, - {{ 0, 1, 0 }, Z, Y, true, true }, - {{ -1, 0, 0 }, X, Z, false, true }, - {{ 0, 0, 0 }, X, Y, true, true }, - {{ 0, -1, 0 }, Z, Y, false, true }, -}; - -static const matrix g_circleDrawTransforms[3] = -{ { 2, 0, 0, 0, 1, 0, 0, 0, 2 }, - { 2, 0, 0, 0, 0, 2, 0, 1, 0 }, - { 0, 1, 0, 2, 0, 0, 0, 0, 2 }, -}; - -cfg (String, gl_bgcolor, "#CCCCD9"); -cfg (String, gl_maincolor, "#707078"); -cfg (Float, gl_maincolor_alpha, 1.0); -cfg (Int, gl_linethickness, 2); -cfg (Bool, gl_colorbfc, false); -cfg (Int, gl_camera, GLRenderer::EFreeCamera); -cfg (Bool, gl_blackedges, false); -cfg (Bool, gl_axes, false); -cfg (Bool, gl_wireframe, false); -cfg (Bool, gl_logostuds, false); -cfg (Bool, gl_aa, true); - -// argh -const char* g_CameraNames[7] = -{ QT_TRANSLATE_NOOP ("GLRenderer", "Top"), - QT_TRANSLATE_NOOP ("GLRenderer", "Front"), - QT_TRANSLATE_NOOP ("GLRenderer", "Left"), - QT_TRANSLATE_NOOP ("GLRenderer", "Bottom"), - QT_TRANSLATE_NOOP ("GLRenderer", "Back"), - QT_TRANSLATE_NOOP ("GLRenderer", "Right"), - QT_TRANSLATE_NOOP ("GLRenderer", "Free") -}; - -const GL::EFixedCamera g_Cameras[7] = -{ GL::ETopCamera, - GL::EFrontCamera, - GL::ELeftCamera, - GL::EBottomCamera, - GL::EBackCamera, - GL::ERightCamera, - GL::EFreeCamera -}; - -const struct LDGLAxis -{ const QColor col; - const vertex vert; -} g_GLAxes[3] = -{ { QColor (255, 0, 0), vertex (10000, 0, 0) }, - { QColor (80, 192, 0), vertex (0, 10000, 0) }, - { QColor (0, 160, 192), vertex (0, 0, 10000) }, -}; - -static bool g_glInvert = false; -static QList<int> g_warnedColors; - -// ============================================================================= -// ----------------------------------------------------------------------------- -GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) -{ m_Picking = m_rangepick = false; - m_camera = (GL::EFixedCamera) gl_camera.value; - m_drawToolTip = false; - m_EditMode = ESelectMode; - m_rectdraw = false; - m_panning = false; - setFile (null); - setDrawOnly (false); - setMessageLog (null); - m_width = m_height = -1; - m_hoverpos = g_origin; - - m_toolTipTimer = new QTimer (this); - m_toolTipTimer->setSingleShot (true); - connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer())); - - m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); - m_thinBorderPen = m_thickBorderPen; - m_thinBorderPen.setWidth (1); - - // Init camera icons - for (const GL::EFixedCamera cam : g_Cameras) - { str iconname = fmt ("camera-%1", tr (g_CameraNames[cam]).toLower()); - - CameraIcon* info = &m_cameraIcons[cam]; - info->img = new QPixmap (getIcon (iconname)); - info->cam = cam; - } - - for (int i = 0; i < 6; ++i) - { m_overlays[i].img = null; - m_depthValues[i] = 0.0f; - } - - calcCameraIcons(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -GLRenderer::~GLRenderer() -{ for (int i = 0; i < 6; ++i) - delete m_overlays[i].img; - - for (CameraIcon& info : m_cameraIcons) - delete info.img; -} - -// ============================================================================= -// Calculates the "hitboxes" of the camera icons so that we can tell when the -// cursor is pointing at the camera icon. -// ----------------------------------------------------------------------------- -void GLRenderer::calcCameraIcons() -{ int i = 0; - - for (CameraIcon& info : m_cameraIcons) - { // MATH - const long x1 = (m_width - (info.cam != EFreeCamera ? 48 : 16)) + ((i % 3) * 16) - 1, - y1 = ((i / 3) * 16) + 1; - - info.srcRect = QRect (0, 0, 16, 16); - info.destRect = QRect (x1, y1, 16, 16); - info.selRect = QRect ( - info.destRect.x(), - info.destRect.y(), - info.destRect.width() + 1, - info.destRect.height() + 1 - ); - - ++i; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::initGLData() -{ glEnable (GL_BLEND); - glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable (GL_POLYGON_OFFSET_FILL); - glPolygonOffset (1.0f, 1.0f); - - glEnable (GL_DEPTH_TEST); - glShadeModel (GL_SMOOTH); - glEnable (GL_MULTISAMPLE); - - if (gl_aa) - { glEnable (GL_LINE_SMOOTH); - glEnable (GL_POLYGON_SMOOTH); - glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); - glHint (GL_POLYGON_SMOOTH_HINT, GL_NICEST); - } else - { glDisable (GL_LINE_SMOOTH); - glDisable (GL_POLYGON_SMOOTH); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::resetAngles() -{ rot (X) = 30.0f; - rot (Y) = 325.f; - pan (X) = pan (Y) = rot (Z) = 0.0f; - zoomToFit(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::resetAllAngles() -{ EFixedCamera oldcam = camera(); - - for (int i = 0; i < 7; ++i) - { setCamera ((EFixedCamera) i); - resetAngles(); - } - - setCamera (oldcam); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::initializeGL() -{ setBackground(); - - glLineWidth (gl_linethickness); - - setAutoFillBackground (false); - setMouseTracking (true); - setFocusPolicy (Qt::WheelFocus); - compileAllObjects(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QColor GLRenderer::getMainColor() -{ QColor col (gl_maincolor); - - if (!col.isValid()) - return QColor (0, 0, 0); - - col.setAlpha (gl_maincolor_alpha * 255.f); - return col; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::setBackground() -{ QColor col (gl_bgcolor); - - if (!col.isValid()) - return; - - col.setAlpha (255); - - m_darkbg = luma (col) < 80; - m_bgcolor = col; - qglClearColor (col); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::setObjectColor (LDObject* obj, const ListType list) -{ QColor qcol; - - if (!obj->isColored()) - return; - - if (list == GL::PickList) - { // Make the color by the object's ID if we're picking, so we can make the - // ID again from the color we get from the picking results. Be sure to use - // the top level parent's index since we want a subfile's children point - // to the subfile itself. - long i = obj->topLevelParent()->getID(); - - // Calculate a color based from this index. This method caters for - // 16777216 objects. I don't think that'll be exceeded anytime soon. :) - // ATM biggest is 53588.dat with 12600 lines. - double r = (i / (256 * 256)) % 256, - g = (i / 256) % 256, - b = i % 256; - - qglColor (QColor (r, g, b)); - return; - } - - if ((list == BFCFrontList || list == BFCBackList) && - obj->getType() != LDObject::Line && - obj->getType() != LDObject::CondLine) - { if (list == GL::BFCFrontList) - qcol = QColor (40, 192, 0); - else - qcol = QColor (224, 0, 0); - } - else - { if (obj->getColor() == maincolor) - qcol = getMainColor(); - else - { LDColor* col = getColor (obj->getColor()); - - if (col) - qcol = col->faceColor; - } - - if (obj->getColor() == edgecolor) - { qcol = luma (m_bgcolor) < 40 ? QColor (64, 64, 64) : Qt::black; - LDColor* col; - - if (!gl_blackedges && obj->getParent() && (col = getColor (obj->getParent()->getColor()))) - qcol = col->edgeColor; - } - - if (qcol.isValid() == false) - { // The color was unknown. Use main color to make the object at least - // not appear pitch-black. - if (obj->getColor() != edgecolor) - qcol = getMainColor(); - - // Warn about the unknown colors, but only once. - for (int i : g_warnedColors) - if (obj->getColor() == i) - return; - - log ("%1: Unknown color %2!\n", __func__, obj->getColor()); - g_warnedColors << obj->getColor(); - return; - } - } - - long r = qcol.red(), - g = qcol.green(), - b = qcol.blue(), - a = qcol.alpha(); - - if (obj->topLevelParent()->isSelected()) - { // Brighten it up for the select list. - const uchar add = 51; - - r = min (r + add, 255l); - g = min (g + add, 255l); - b = min (b + add, 255l); - } - - glColor4f ( - ((double) r) / 255.0f, - ((double) g) / 255.0f, - ((double) b) / 255.0f, - ((double) a) / 255.0f); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::refresh() -{ update(); - swapBuffers(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::hardRefresh() -{ compileAllObjects(); - refresh(); - - glLineWidth (gl_linethickness); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::resizeGL (int w, int h) -{ m_width = w; - m_height = h; - - calcCameraIcons(); - - glViewport (0, 0, w, h); - glMatrixMode (GL_PROJECTION); - glLoadIdentity(); - gluPerspective (45.0f, (double) w / (double) h, 1.0f, 10000.0f); - glMatrixMode (GL_MODELVIEW); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::drawGLScene() -{ if (getFile() == null) - return; - - if (gl_wireframe && !isPicking()) - glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); - - glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable (GL_DEPTH_TEST); - - if (m_camera != EFreeCamera) - { glMatrixMode (GL_PROJECTION); - glPushMatrix(); - - glLoadIdentity(); - glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f); - glTranslatef (pan (X), pan (Y), 0.0f); - - if (m_camera != EFrontCamera && m_camera != EBackCamera) - { glRotatef (90.0f, g_FixedCameras[camera()].glrotate[0], - g_FixedCameras[camera()].glrotate[1], - g_FixedCameras[camera()].glrotate[2]); - } - - // Back camera needs to be handled differently - if (m_camera == GLRenderer::EBackCamera) - { glRotatef (180.0f, 1.0f, 0.0f, 0.0f); - glRotatef (180.0f, 0.0f, 0.0f, 1.0f); - } - } - else - { glMatrixMode (GL_MODELVIEW); - glPushMatrix(); - glLoadIdentity(); - - glTranslatef (0.0f, 0.0f, -2.0f); - glTranslatef (pan (X), pan (Y), -zoom()); - glRotatef (rot (X), 1.0f, 0.0f, 0.0f); - glRotatef (rot (Y), 0.0f, 1.0f, 0.0f); - glRotatef (rot (Z), 0.0f, 0.0f, 1.0f); - } - - const GL::ListType list = (!isDrawOnly() && isPicking()) ? PickList : NormalList; - - if (gl_colorbfc && !isPicking() && !isDrawOnly()) - { glEnable (GL_CULL_FACE); - - for (LDObject* obj : getFile()->getObjects()) - { if (obj->isHidden()) - continue; - - glCullFace (GL_BACK); - glCallList (obj->glLists[BFCFrontList]); - - glCullFace (GL_FRONT); - glCallList (obj->glLists[BFCBackList]); - } - - glDisable (GL_CULL_FACE); - } - else - { for (LDObject* obj : getFile()->getObjects()) - { if (obj->isHidden()) - continue; - - glCallList (obj->glLists[list]); - } - } - - if (gl_axes && !isPicking() && !isDrawOnly()) - glCallList (m_axeslist); - - glPopMatrix(); - glMatrixMode (GL_MODELVIEW); - glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -// This converts a 2D point on the screen to a 3D point in the model. If 'snap' -// is true, the 3D point will snap to the current grid. -// ----------------------------------------------------------------------------- -vertex GLRenderer::coordconv2_3 (const QPoint& pos2d, bool snap) const -{ assert (camera() != EFreeCamera); - - vertex pos3d; - const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; - const Axis axisX = cam->axisX; - const Axis axisY = cam->axisY; - const int negXFac = cam->negX ? -1 : 1, - negYFac = cam->negY ? -1 : 1; - - // Calculate cx and cy - these are the LDraw unit coords the cursor is at. - double cx = (-m_virtWidth + ((2 * pos2d.x() * m_virtWidth) / m_width) - pan (X)); - double cy = (m_virtHeight - ((2 * pos2d.y() * m_virtHeight) / m_height) - pan (Y)); - - if (snap) - { cx = Grid::snap (cx, (Grid::Config) axisX); - cy = Grid::snap (cy, (Grid::Config) axisY); - } - - cx *= negXFac; - cy *= negYFac; - - str tmp; - // Create the vertex from the coordinates - pos3d[axisX] = tmp.sprintf ("%.3f", cx).toDouble(); - pos3d[axisY] = tmp.sprintf ("%.3f", cy).toDouble(); - pos3d[3 - axisX - axisY] = getDepthValue(); - return pos3d; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -// Inverse operation for the above - convert a 3D position to a 2D screen -// position -// ----------------------------------------------------------------------------- -QPoint GLRenderer::coordconv3_2 (const vertex& pos3d) const -{ GLfloat m[16]; - const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; - const Axis axisX = cam->axisX; - const Axis axisY = cam->axisY; - const int negXFac = cam->negX ? -1 : 1, - negYFac = cam->negY ? -1 : 1; - - glGetFloatv (GL_MODELVIEW_MATRIX, m); - - const double x = pos3d.x(); - const double y = pos3d.y(); - const double z = pos3d.z(); - - vertex transformed; - transformed[X] = (m[0] * x) + (m[1] * y) + (m[2] * z) + m[3]; - transformed[Y] = (m[4] * x) + (m[5] * y) + (m[6] * z) + m[7]; - transformed[Z] = (m[8] * x) + (m[9] * y) + (m[10] * z) + m[11]; - - double rx = (((transformed[axisX] * negXFac) + m_virtWidth + pan (X)) * m_width) / (2 * m_virtWidth); - double ry = (((transformed[axisY] * negYFac) - m_virtHeight + pan (Y)) * m_height) / (2 * m_virtHeight); - - return QPoint (rx, -ry); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::paintEvent (QPaintEvent* ev) -{ Q_UNUSED (ev) - - makeCurrent(); - m_virtWidth = zoom(); - m_virtHeight = (m_height * m_virtWidth) / m_width; - - initGLData(); - drawGLScene(); - - const QPen textpen = getTextPen(); - const QBrush polybrush (QColor (64, 192, 0, 128)); - QPainter paint (this); - QFontMetrics metrics = QFontMetrics (QFont()); - paint.setRenderHint (QPainter::HighQualityAntialiasing); - - // If we wish to only draw the brick, stop here - if (isDrawOnly()) - return; - - if (m_camera != EFreeCamera && !isPicking()) - { // Paint the overlay image if we have one - const LDGLOverlay& overlay = m_overlays[m_camera]; - - if (overlay.img != null) - { QPoint v0 = coordconv3_2 (m_overlays[m_camera].v0), - v1 = coordconv3_2 (m_overlays[m_camera].v1); - - QRect targRect (v0.x(), v0.y(), abs (v1.x() - v0.x()), abs (v1.y() - v0.y())), - srcRect (0, 0, overlay.img->width(), overlay.img->height()); - paint.drawImage (targRect, *overlay.img, srcRect); - } - - // Paint the coordinates onto the screen. - str text = fmt (tr ("X: %1, Y: %2, Z: %3"), m_hoverpos[X], m_hoverpos[Y], m_hoverpos[Z]); - - QFontMetrics metrics = QFontMetrics (font()); - QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); - - paint.setPen (getTextPen()); - paint.drawText (m_width - textSize.width(), m_height - 16, textSize.width(), - textSize.height(), Qt::AlignCenter, text); - - QPen linepen = m_thinBorderPen; - linepen.setWidth (2); - linepen.setColor (luma (m_bgcolor) < 40 ? Qt::white : Qt::black); - - // If we're drawing, draw the vertices onto the screen. - if (getEditMode() == EDrawMode) - { int numverts = 4; - - if (!m_rectdraw) - numverts = m_drawedVerts.size() + 1; - - if (numverts > 0) - { QPoint poly[4]; - vertex polyverts[4]; - - if (!m_rectdraw) - { uchar i = 0; - - for (vertex& vert : m_drawedVerts) - { poly[i] = coordconv3_2 (vert); - polyverts[i] = vert; - ++i; - } - - // Draw the cursor vertex as the last one in the list. - if (numverts <= 4) - { poly[i] = coordconv3_2 (m_hoverpos); - polyverts[i] = m_hoverpos; - } - else - { numverts = 4; - } - } - else - { if (m_drawedVerts.size() > 0) - { // Get vertex information from m_rectverts - for (int i = 0; i < numverts; ++i) - { polyverts[i] = m_rectverts[i]; - poly[i] = coordconv3_2 (polyverts[i]); - } - } - else - { poly[0] = coordconv3_2 (m_hoverpos); - polyverts[0] = m_hoverpos; - } - } - - // Draw the polygon-to-be - paint.setPen (linepen); - paint.setBrush (polybrush); - paint.drawPolygon (poly, numverts); - - // Draw vertex blips - for (int i = 0; i < numverts; ++i) - { QPoint& blip = poly[i]; - drawBlip (paint, blip); - - // Draw their coordinates - paint.drawText (blip.x(), blip.y() - 8, polyverts[i].stringRep (true)); - } - } - } - elif (getEditMode() == ECircleMode) - { // If we have not specified the center point of the circle yet, preview it on the screen. - if (m_drawedVerts.isEmpty()) - drawBlip (paint, coordconv3_2 (m_hoverpos)); - else - { QVector<vertex> verts, verts2; - const double dist0 = getCircleDrawDist (0), - dist1 = (m_drawedVerts.size() >= 2) ? getCircleDrawDist (1) : -1; - const int segs = lores; - const double angleUnit = (2 * pi) / segs; - Axis relX, relY; - QVector<QPoint> ringpoints, circlepoints, circle2points; - - getRelativeAxes (relX, relY); - - // Calculate the preview positions of vertices - for (int i = 0; i < segs; ++i) - { vertex v = g_origin; - v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist0); - v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist0); - verts << v; - - if (dist1 != -1) - { v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist1); - v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist1); - verts2 << v; - } - } - - int i = 0; - for (const vertex& v : verts + verts2) - { // Calculate the 2D point of the vertex - QPoint point = coordconv3_2 (v); - - // Draw a green blip at where it is - drawBlip (paint, point); - - // Add it to the list of points for the green ring fill. - ringpoints << point; - - // Also add the circle points to separate lists - if (i < verts.size()) - circlepoints << point; - else - circle2points << point; - - ++i; - } - - // Insert the first point as the seventeenth one so that - // the ring polygon is closed properly. - if (ringpoints.size() >= 16) - ringpoints.insert (16, ringpoints[0]); - - // Same for the outer ring. Note that the indices are offset by 1 - // because of the insertion done above bumps the values. - if (ringpoints.size() >= 33) - ringpoints.insert (33, ringpoints[17]); - - // Draw the ring - paint.setBrush ((m_drawedVerts.size() >= 2) ? polybrush : Qt::NoBrush); - paint.setPen (Qt::NoPen); - paint.drawPolygon (QPolygon (ringpoints)); - - // Draw the circles - paint.setBrush (Qt::NoBrush); - paint.setPen (linepen); - paint.drawPolygon (QPolygon (circlepoints)); - paint.drawPolygon (QPolygon (circle2points)); - - { // Draw the current radius in the middle of the circle. - QPoint origin = coordconv3_2 (m_drawedVerts[0]); - str label = str::number (dist0); - paint.setPen (textpen); - paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label); - - if (m_drawedVerts.size() >= 2) - { label = str::number (dist1); - paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y() + metrics.height(), label); - } - } - } - } - } - - // Camera icons - if (!isPicking()) - { // Draw a background for the selected camera - paint.setPen (m_thinBorderPen); - paint.setBrush (QBrush (QColor (0, 128, 160, 128))); - paint.drawRect (m_cameraIcons[camera()].selRect); - - // Draw the actual icons - for (CameraIcon& info : m_cameraIcons) - { // Don't draw the free camera icon when in draw mode - if (&info == &m_cameraIcons[GL::EFreeCamera] && getEditMode() != ESelectMode) - continue; - - paint.drawPixmap (info.destRect, *info.img, info.srcRect); - } - - str fmtstr = tr ("%1 Camera"); - - // Draw a label for the current camera in the bottom left corner - { const int margin = 4; - - str label; - label = fmt (fmtstr, tr (g_CameraNames[camera()])); - paint.setPen (textpen); - paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label); - } - - // Tool tips - if (m_drawToolTip) - { if (m_cameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false) - m_drawToolTip = false; - else - { str label = fmt (fmtstr, tr (g_CameraNames[m_toolTipCamera])); - QToolTip::showText (m_globalpos, label); - } - } - } - - // Message log - if (getMessageLog()) - { int y = 0; - const int margin = 2; - QColor penColor = getTextPen(); - - for (const MessageManager::Line& line : getMessageLog()->getLines()) - { penColor.setAlphaF (line.alpha); - paint.setPen (penColor); - paint.drawText (QPoint (margin, y + margin + metrics.ascent()), line.text); - y += metrics.height(); - } - } - - // If we're range-picking, draw a rectangle encompassing the selection area. - if (m_rangepick && !isPicking() && m_totalmove >= 10) - { int x0 = m_rangeStart.x(), - y0 = m_rangeStart.y(), - x1 = m_pos.x(), - y1 = m_pos.y(); - - QRect rect (x0, y0, x1 - x0, y1 - y0); - QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF"); - fillColor.setAlphaF (0.2f); - - paint.setPen (m_thickBorderPen); - paint.setBrush (QBrush (fillColor)); - paint.drawRect (rect); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::drawBlip (QPainter& paint, QPoint pos) const -{ QPen pen = m_thinBorderPen; - const int blipsize = 8; - pen.setWidth (1); - paint.setPen (pen); - paint.setBrush (QColor (64, 192, 0)); - paint.drawEllipse (pos.x() - blipsize / 2, pos.y() - blipsize / 2, blipsize, blipsize); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QColor GLRenderer::getTextPen () const -{ return m_darkbg ? Qt::white : Qt::black; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::compileAllObjects() -{ if (!getFile()) - return; - - // Compiling all is a big job, use a busy cursor - setCursor (Qt::BusyCursor); - - m_knownVerts.clear(); - - for (LDObject* obj : getFile()->getObjects()) - compileObject (obj); - - // Compile axes - glDeleteLists (m_axeslist, 1); - m_axeslist = glGenLists (1); - glNewList (m_axeslist, GL_COMPILE); - glBegin (GL_LINES); - - for (const LDGLAxis& ax : g_GLAxes) - { qglColor (ax.col); - compileVertex (ax.vert); - compileVertex (-ax.vert); - } - - glEnd(); - glEndList(); - - setCursor (Qt::ArrowCursor); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype) -{ glBegin (gltype); - - const int numverts = (obj->getType() != LDObject::CondLine) ? obj->vertices() : 2; - - if (g_glInvert == false) - for (int i = 0; i < numverts; ++i) - compileVertex (obj->getVertex (i)); - else - for (int i = numverts - 1; i >= 0; --i) - compileVertex (obj->getVertex (i)); - - glEnd(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::compileList (LDObject* obj, const GLRenderer::ListType list) -{ setObjectColor (obj, list); - - switch (obj->getType()) - { case LDObject::Line: - compileSubObject (obj, GL_LINES); - break; - - case LDObject::CondLine: - - // Draw conditional lines with a dash pattern - however, use a full - // line when drawing a pick list to make selecting them easier. - if (list != GL::PickList) - { glLineStipple (1, 0x6666); - glEnable (GL_LINE_STIPPLE); - } - - compileSubObject (obj, GL_LINES); - - glDisable (GL_LINE_STIPPLE); - break; - - case LDObject::Triangle: - compileSubObject (obj, GL_TRIANGLES); - break; - - case LDObject::Quad: - compileSubObject (obj, GL_QUADS); - break; - - case LDObject::Subfile: - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - QList<LDObject*> objs; - - objs = ref->inlineContents ( - LDSubfile::DeepInline | - LDSubfile::CacheInline | - LDSubfile::RendererInline); - bool oldinvert = g_glInvert; - - if (ref->getTransform().getDeterminant() < 0) - g_glInvert = !g_glInvert; - - LDObject* prev = ref->prev(); - - if (prev && prev->getType() == LDObject::BFC && static_cast<LDBFC*> (prev)->type == LDBFC::InvertNext) - g_glInvert = !g_glInvert; - - for (LDObject * obj : objs) - { compileList (obj, list); - delete obj; - } - - g_glInvert = oldinvert; - } - break; - - default: - break; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::compileVertex (const vertex& vrt) -{ glVertex3d (vrt[X], -vrt[Y], -vrt[Z]); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::clampAngle (double& angle) const -{ while (angle < 0) - angle += 360.0; - - while (angle > 360.0) - angle -= 360.0; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::addDrawnVertex (vertex pos) -{ // If we picked an already-existing vertex, stop drawing - if (getEditMode() != ECircleMode) - { for (vertex& vert : m_drawedVerts) - { if (vert == pos) - { endDraw (true); - return; - } - } - } - - m_drawedVerts << pos; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::mouseReleaseEvent (QMouseEvent* ev) -{ const bool wasLeft = (m_lastButtons & Qt::LeftButton) && ! (ev->buttons() & Qt::LeftButton), - wasRight = (m_lastButtons & Qt::RightButton) && ! (ev->buttons() & Qt::RightButton), - wasMid = (m_lastButtons & Qt::MidButton) && ! (ev->buttons() & Qt::MidButton); - - if (m_panning) - m_panning = false; - - if (wasLeft) - { // Check if we selected a camera icon - if (!m_rangepick) - { for (CameraIcon & info : m_cameraIcons) - { if (info.destRect.contains (ev->pos())) - { setCamera (info.cam); - goto end; - } - } - } - - switch (getEditMode()) - { case EDrawMode: - { if (m_rectdraw) - { if (m_drawedVerts.size() == 2) - { endDraw (true); - return; - } - } else - { // If we have 4 verts, stop drawing. - if (m_drawedVerts.size() >= 4) - { endDraw (true); - return; - } - - if (m_drawedVerts.isEmpty() && ev->modifiers() & Qt::ShiftModifier) - { m_rectdraw = true; - updateRectVerts(); - } - } - - addDrawnVertex (m_hoverpos); - } break; - - case ECircleMode: - { if (m_drawedVerts.size() == 3) - { endDraw (true); - return; - } - - addDrawnVertex (m_hoverpos); - } break; - - case ESelectMode: - { if (!isDrawOnly()) - { if (m_totalmove < 10) - m_rangepick = false; - - if (!m_rangepick) - m_addpick = (m_keymods & Qt::ControlModifier); - - if (m_totalmove < 10 || m_rangepick) - pick (ev->x(), ev->y()); - } - } break; - } - - m_rangepick = false; - } - - if (wasMid && getEditMode() != ESelectMode && m_drawedVerts.size() < 4 && m_totalmove < 10) - { // Find the closest vertex to our cursor - double mindist = 1024.0f; - vertex closest; - bool valid = false; - - QPoint curspos = coordconv3_2 (m_hoverpos); - - for (const vertex& pos3d: m_knownVerts) - { QPoint pos2d = coordconv3_2 (pos3d); - - // Measure squared distance - const double dx = abs (pos2d.x() - curspos.x()), - dy = abs (pos2d.y() - curspos.y()), - distsq = (dx * dx) + (dy * dy); - - if (distsq >= 1024.0f) // 32.0f ** 2 - continue; // too far away - - if (distsq < mindist) - { mindist = distsq; - closest = pos3d; - valid = true; - - // If it's only 4 pixels away, I think we found our vertex now. - if (distsq <= 16.0f) // 4.0f ** 2 - break; - } - } - - if (valid) - addDrawnVertex (closest); - } - - if (wasRight && !m_drawedVerts.isEmpty()) - { // Remove the last vertex - m_drawedVerts.removeLast(); - - if (m_drawedVerts.isEmpty()) - m_rectdraw = false; - } - -end: - update(); - m_totalmove = 0; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::mousePressEvent (QMouseEvent* ev) -{ m_totalmove = 0; - - if (ev->modifiers() & Qt::ControlModifier) - { m_rangepick = true; - m_rangeStart.setX (ev->x()); - m_rangeStart.setY (ev->y()); - m_addpick = (m_keymods & Qt::AltModifier); - ev->accept(); - } - - m_lastButtons = ev->buttons(); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::mouseMoveEvent (QMouseEvent* ev) -{ int dx = ev->x() - m_pos.x(); - int dy = ev->y() - m_pos.y(); - m_totalmove += abs (dx) + abs (dy); - - const bool left = ev->buttons() & Qt::LeftButton, - mid = ev->buttons() & Qt::MidButton, - shift = ev->modifiers() & Qt::ShiftModifier; - - if (mid || (left && shift)) - { pan (X) += 0.03f * dx * (zoom() / 7.5f); - pan (Y) -= 0.03f * dy * (zoom() / 7.5f); - m_panning = true; - } elif (left && !m_rangepick && camera() == EFreeCamera) - { rot (X) = rot (X) + dy; - rot (Y) = rot (Y) + dx; - - clampAngle (rot (X)); - clampAngle (rot (Y)); - } - - // Start the tool tip timer - if (!m_drawToolTip) - m_toolTipTimer->start (500); - - // Update 2d position - m_pos = ev->pos(); - m_globalpos = ev->globalPos(); - - // Calculate 3d position of the cursor - m_hoverpos = (camera() != EFreeCamera) ? coordconv2_3 (m_pos, true) : g_origin; - - // Update rect vertices since m_hoverpos may have changed - updateRectVerts(); - - update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::keyPressEvent (QKeyEvent* ev) -{ m_keymods = ev->modifiers(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::keyReleaseEvent (QKeyEvent* ev) -{ m_keymods = ev->modifiers(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::wheelEvent (QWheelEvent* ev) -{ makeCurrent(); - - zoomNotch (ev->delta() > 0); - zoom() = clamp (zoom(), 0.01, 10000.0); - - update(); - ev->accept(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::leaveEvent (QEvent* ev) -{ (void) ev; - m_drawToolTip = false; - m_toolTipTimer->stop(); - update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::contextMenuEvent (QContextMenuEvent* ev) -{ g_win->spawnContextMenu (ev->globalPos()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::setCamera (const GLRenderer::EFixedCamera cam) -{ m_camera = cam; - gl_camera = (int) cam; - g_win->updateEditModeActions(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::pick (int mouseX, int mouseY) -{ GLint viewport[4]; - makeCurrent(); - - // Use particularly thick lines while picking ease up selecting lines. - glLineWidth (max<double> (gl_linethickness, 6.5f)); - - // Clear the selection if we do not wish to add to it. - if (!m_addpick) - { QList<LDObject*> oldsel = selection(); - getCurrentDocument()->clearSelection(); - - for (LDObject* obj : oldsel) - compileObject (obj); - } - - setPicking (true); - - // Paint the picking scene - glDisable (GL_DITHER); - glClearColor (1.0f, 1.0f, 1.0f, 1.0f); - - drawGLScene(); - - glGetIntegerv (GL_VIEWPORT, viewport); - - int x0 = mouseX, - y0 = mouseY; - int x1, y1; - - // Determine how big an area to read - with range picking, we pick by - // the area given, with single pixel picking, we use an 1 x 1 area. - if (m_rangepick) - { x1 = m_rangeStart.x(); - y1 = m_rangeStart.y(); - } - else - { x1 = x0 + 1; - y1 = y0 + 1; - } - - // x0 and y0 must be less than x1 and y1, respectively. - if (x0 > x1) - qSwap (x0, x1); - - if (y0 > y1) - qSwap (y0, y1); - - // Clamp the values to ensure they're within bounds - x0 = max (0, x0); - y0 = max (0, y0); - x1 = min (x1, m_width); - y1 = min (y1, m_height); - - const int areawidth = (x1 - x0); - const int areaheight = (y1 - y0); - const qint32 numpixels = areawidth * areaheight; - - // Allocate space for the pixel data. - uchar* const pixeldata = new uchar[4 * numpixels]; - uchar* pixelptr = &pixeldata[0]; - - assert (viewport[3] == m_height); - - // Read pixels from the color buffer. - glReadPixels (x0, viewport[3] - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata); - - LDObject* removedObj = null; - - // Go through each pixel read and add them to the selection. - for (qint32 i = 0; i < numpixels; ++i) - { qint32 idx = - (*(pixelptr + 0) * 0x10000) + - (*(pixelptr + 1) * 0x00100) + - (*(pixelptr + 2) * 0x00001); - pixelptr += 4; - - if (idx == 0xFFFFFF) - continue; // White is background; skip - - LDObject* obj = LDObject::fromID (idx); - - // If this is an additive single pick and the object is currently selected, - // we remove it from selection instead. - if (!m_rangepick && m_addpick) - { if (obj->isSelected()) - { obj->unselect(); - removedObj = obj; - break; - } - } - - obj->select(); - } - - delete[] pixeldata; - - // Update everything now. - g_win->updateSelection(); - - // Recompile the objects now to update their color - for (LDObject* obj : selection()) - compileObject (obj); - - if (removedObj) - compileObject (removedObj); - - // Restore line thickness - glLineWidth (gl_linethickness); - - setPicking (false); - m_rangepick = false; - glEnable (GL_DITHER); - - setBackground(); - repaint(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::setEditMode (EditMode const& a) -{ m_EditMode = a; - - switch (a) - { case ESelectMode: - { unsetCursor(); - setContextMenuPolicy (Qt::DefaultContextMenu); - } break; - - case EDrawMode: - case ECircleMode: - { // Cannot draw into the free camera - use top instead. - if (m_camera == EFreeCamera) - setCamera (ETopCamera); - - // Disable the context menu - we need the right mouse button - // for removing vertices. - setContextMenuPolicy (Qt::NoContextMenu); - - // Use the crosshair cursor when drawing. - setCursor (Qt::CrossCursor); - - // Clear the selection when beginning to draw. - QList<LDObject*> priorsel = selection(); - getCurrentDocument()->clearSelection(); - - for (LDObject* obj : priorsel) - compileObject (obj); - - g_win->updateSelection(); - m_drawedVerts.clear(); - } break; - } - - g_win->updateEditModeActions(); - update(); -} - -void GLRenderer::setFile (LDDocument* const& a) -{ m_File = a; - - if (a != null) - initOverlaysFromObjects(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -matrix GLRenderer::getCircleDrawMatrix (double scale) -{ matrix transform = g_circleDrawTransforms[camera() % 3]; - - for (int i = 0; i < 9; ++i) - { if (transform[i] == 2) - transform[i] = scale; - elif (transform[i] == 1 && camera() >= 3) - transform[i] = -1; - } - - return transform; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::endDraw (bool accept) -{ (void) accept; - - // Clean the selection and create the object - QList<vertex>& verts = m_drawedVerts; - QList<LDObject*> objs; - - switch (getEditMode()) - { case EDrawMode: - { if (m_rectdraw) - { LDQuad* quad = new LDQuad; - - // Copy the vertices from m_rectverts - updateRectVerts(); - - for (int i = 0; i < quad->vertices(); ++i) - quad->setVertex (i, m_rectverts[i]); - - quad->setColor (maincolor); - objs << quad; - } - else - { switch (verts.size()) - { case 1: - { // 1 vertex - add a vertex object - LDVertex* obj = new LDVertex; - obj->pos = verts[0]; - obj->setColor (maincolor); - objs << obj; - } break; - - case 2: - { // 2 verts - make a line - LDLine* obj = new LDLine (verts[0], verts[1]); - obj->setColor (edgecolor); - objs << obj; - } break; - - case 3: - case 4: - { LDObject* obj = (verts.size() == 3) ? - static_cast<LDObject*> (new LDTriangle) : - static_cast<LDObject*> (new LDQuad); - - obj->setColor (maincolor); - - for (int i = 0; i < obj->vertices(); ++i) - obj->setVertex (i, verts[i]); - - objs << obj; - } break; - } - } - } break; - - case ECircleMode: - { const int segs = lores, divs = lores; // TODO: make customizable - double dist0 = getCircleDrawDist (0), - dist1 = getCircleDrawDist (1); - LDDocument* refFile = null; - matrix transform; - bool circleOrDisc = false; - - if (dist1 < dist0) - std::swap<double> (dist0, dist1); - - if (dist0 == dist1) - { // If the radii are the same, there's no ring space to fill. Use a circle. - refFile = ::getDocument ("4-4edge.dat"); - transform = getCircleDrawMatrix (dist0); - circleOrDisc = true; - } - elif (dist0 == 0 || dist1 == 0) - { // If either radii is 0, use a disc. - refFile = ::getDocument ("4-4disc.dat"); - transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); - circleOrDisc = true; - } - elif (g_RingFinder (dist0, dist1)) - { // The ring finder found a solution, use that. Add the component rings to the file. - for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents()) - { // Get a ref file for this primitive. If we cannot find it in the - // LDraw library, generate it. - if ((refFile = ::getDocument (radialFileName (::Ring, lores, lores, cmp.num))) == null) - { refFile = generatePrimitive (::Ring, lores, lores, cmp.num); - refFile->setImplicit (false); - } - - LDSubfile* ref = new LDSubfile; - ref->setFileInfo (refFile); - ref->setTransform (getCircleDrawMatrix (cmp.scale)); - ref->setPosition (m_drawedVerts[0]); - ref->setColor (maincolor); - objs << ref; - } - } - else - { // Ring finder failed, last resort: draw the ring with quads - QList<QLineF> c0, c1; - Axis relX, relY, relZ; - getRelativeAxes (relX, relY); - relZ = (Axis) (3 - relX - relY); - double x0 = m_drawedVerts[0][relX], - y0 = m_drawedVerts[0][relY]; - - vertex templ; - templ[relX] = x0; - templ[relY] = y0; - templ[relZ] = getDepthValue(); - - // Calculate circle coords - makeCircle (segs, divs, dist0, c0); - makeCircle (segs, divs, dist1, c1); - - for (int i = 0; i < segs; ++i) - { vertex v0, v1, v2, v3; - v0 = v1 = v2 = v3 = templ; - v0[relX] += c0[i].x1(); - v0[relY] += c0[i].y1(); - v1[relX] += c0[i].x2(); - v1[relY] += c0[i].y2(); - v2[relX] += c1[i].x2(); - v2[relY] += c1[i].y2(); - v3[relX] += c1[i].x1(); - v3[relY] += c1[i].y1(); - - LDQuad* q = new LDQuad (v0, v1, v2, v3); - q->setColor (maincolor); - - // Ensure the quads always are BFC-front towards the camera - if (camera() % 3 <= 0) - q->invert(); - - objs << q; - } - } - - if (circleOrDisc) - { LDSubfile* ref = new LDSubfile; - ref->setFileInfo (refFile); - ref->setTransform (transform); - ref->setPosition (m_drawedVerts[0]); - ref->setColor (maincolor); - objs << ref; - } - } break; - - case ESelectMode: - { // this shouldn't happen - assert (false); - return; - } break; - } - - if (objs.size() > 0) - { g_win->beginAction (null); - - for (LDObject* obj : objs) - { getFile()->addObject (obj); - compileObject (obj); - } - - g_win->refresh(); - g_win->endAction(); - } - - m_drawedVerts.clear(); - m_rectdraw = false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -double GLRenderer::getCircleDrawDist (int pos) const -{ assert (m_drawedVerts.size() >= pos + 1); - const vertex& v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : m_hoverpos; - Axis relX, relY; - getRelativeAxes (relX, relY); - - const double dx = m_drawedVerts[0][relX] - v1[relX]; - const double dy = m_drawedVerts[0][relY] - v1[relY]; - return sqrt ((dx * dx) + (dy * dy)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const -{ const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; - relX = cam->axisX; - relY = cam->axisY; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static QList<vertex> getVertices (LDObject* obj) -{ QList<vertex> verts; - - if (obj->vertices() >= 2) - { for (int i = 0; i < obj->vertices(); ++i) - verts << obj->getVertex (i); - } elif (obj->getType() == LDObject::Subfile) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepCacheInline); - - for (LDObject* obj : objs) - { verts << getVertices (obj); - delete obj; - } - } - - return verts; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::compileObject (LDObject* obj) -{ deleteLists (obj); - - for (const GL::ListType listType : g_glListTypes) - { if (isDrawOnly() && listType != GL::NormalList) - continue; - - GLuint list = glGenLists (1); - glNewList (list, GL_COMPILE); - - obj->glLists[listType] = list; - compileList (obj, listType); - - glEndList(); - } - - // Mark in known vertices of this object - QList<vertex> verts = getVertices (obj); - m_knownVerts << verts; - removeDuplicates (m_knownVerts); - - obj->m_glinit = true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -uchar* GLRenderer::getScreencap (int& w, int& h) -{ w = m_width; - h = m_height; - uchar* cap = new uchar[4 * w * h]; - - m_screencap = true; - update(); - m_screencap = false; - - // Capture the pixels - glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap); - - return cap; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::slot_toolTipTimer() -{ // We come here if the cursor has stayed in one place for longer than a - // a second. Check if we're holding it over a camera icon - if so, draw - // a tooltip. -for (CameraIcon & icon : m_cameraIcons) - { if (icon.destRect.contains (m_pos)) - { m_toolTipCamera = icon.cam; - m_drawToolTip = true; - update(); - break; - } - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::deleteLists (LDObject* obj) -{ // Delete the lists but only if they have been initialized - if (!obj->m_glinit) - return; - -for (const GL::ListType listType : g_glListTypes) - glDeleteLists (obj->glLists[listType], 1); - - obj->m_glinit = false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -Axis GLRenderer::getCameraAxis (bool y, GLRenderer::EFixedCamera camid) -{ if (camid == (GL::EFixedCamera) - 1) - camid = m_camera; - - const LDFixedCameraInfo* cam = &g_FixedCameras[camid]; - return (y) ? cam->axisY : cam->axisX; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool GLRenderer::setupOverlay (EFixedCamera cam, str file, int x, int y, int w, int h) -{ QImage* img = new QImage (file); - LDGLOverlay& info = getOverlay (cam); - - if (img->isNull()) - { critical (tr ("Failed to load overlay image!")); - delete img; - return false; - } - - delete info.img; // delete the old image - - info.fname = file; - info.lw = w; - info.lh = h; - info.ox = x; - info.oy = y; - info.img = img; - - if (info.lw == 0) - info.lw = (info.lh * img->width()) / img->height(); - elif (info.lh == 0) - info.lh = (info.lw * img->height()) / img->width(); - - const Axis x2d = getCameraAxis (false, cam), - y2d = getCameraAxis (true, cam); - const double negXFac = g_FixedCameras[cam].negX ? -1 : 1, - negYFac = g_FixedCameras[cam].negY ? -1 : 1; - - info.v0 = info.v1 = g_origin; - info.v0[x2d] = - (info.ox * info.lw * negXFac) / img->width(); - info.v0[y2d] = (info.oy * info.lh * negYFac) / img->height(); - info.v1[x2d] = info.v0[x2d] + info.lw; - info.v1[y2d] = info.v0[y2d] + info.lh; - - // Set alpha of all pixels to 0.5 - for (long i = 0; i < img->width(); ++i) - for (long j = 0; j < img->height(); ++j) - { uint32 pixel = img->pixel (i, j); - img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF)); - } - - updateOverlayObjects(); - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::clearOverlay() -{ if (camera() == EFreeCamera) - return; - - LDGLOverlay& info = m_overlays[camera()]; - delete info.img; - info.img = null; - - updateOverlayObjects(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::setDepthValue (double depth) -{ assert (camera() < EFreeCamera); - m_depthValues[camera()] = depth; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -double GLRenderer::getDepthValue() const -{ assert (camera() < EFreeCamera); - return m_depthValues[camera()]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const char* GLRenderer::getCameraName() const -{ return g_CameraNames[camera()]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDGLOverlay& GLRenderer::getOverlay (int newcam) -{ return m_overlays[newcam]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::zoomNotch (bool inward) -{ if (zoom() > 15) - zoom() *= inward ? 0.833f : 1.2f; - else - zoom() += inward ? -1.2f : 1.2f; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::zoomToFit() -{ if (getFile() == null || m_width == -1 || m_height == -1) - { zoom() = 30.0f; - return; - } - - bool lastfilled = false; - bool firstrun = true; - const uint32 white = 0xFFFFFFFF; - bool inward = true; - const int w = m_width, h = m_height; - - glClearColor (1.0, 1.0, 1.0, 1.0); - glDisable (GL_DITHER); - - // Use the pick list while drawing the scene, this way we can tell whether borders - // are background or not. - setPicking (true); - - for (;;) - { if (zoom() > 10000.0 || zoom() < 0.0) - { // Obviously, there's nothing to draw if we get here. - // Default to 30.0f and break out. - zoom() = 30.0; - break; - } - - zoomNotch (inward); - - uchar* cap = new uchar[4 * w * h]; - drawGLScene(); - glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap); - uint32* imgdata = reinterpret_cast<uint32*> (cap); - bool filled = false; - - // Check the top and bottom rows - for (int i = 0; i < w && !filled; ++i) - if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white) - filled = true; - - // Left and right edges - for (int i = 0; i < h && !filled; ++i) - if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white) - filled = true; - - delete[] cap; - - if (firstrun) - { // If this is the first run, we don't know enough to determine - // whether the zoom was to fit, so we mark in our knowledge so - // far and start over. - inward = !filled; - firstrun = false; - } - else - { // If this run filled the screen and the last one did not, the - // last run had ideal zoom - zoom a bit back and we should reach it. - if (filled && !lastfilled) - { zoomNotch (false); - break; - } - - // If this run did not fill the screen and the last one did, we've - // now reached ideal zoom so we're done here. - if (!filled && lastfilled) - break; - - inward = !filled; - } - - lastfilled = filled; - } - - setBackground(); - setPicking (false); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::zoomAllToFit() -{ EFixedCamera oldcam = camera(); - - for (int i = 0; i < 7; ++i) - { setCamera ((EFixedCamera) i); - zoomToFit(); - } - - setCamera (oldcam); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::updateRectVerts() -{ if (!m_rectdraw) - return; - - if (m_drawedVerts.isEmpty()) - { for (int i = 0; i < 4; ++i) - m_rectverts[i] = m_hoverpos; - - return; - } - - vertex v0 = m_drawedVerts[0], - v1 = (m_drawedVerts.size() >= 2) ? m_drawedVerts[1] : m_hoverpos; - - const Axis ax = getCameraAxis (false), - ay = getCameraAxis (true), - az = (Axis) (3 - ax - ay); - - for (int i = 0; i < 4; ++i) - m_rectverts[i][az] = getDepthValue(); - - m_rectverts[0][ax] = v0[ax]; - m_rectverts[0][ay] = v0[ay]; - m_rectverts[1][ax] = v1[ax]; - m_rectverts[1][ay] = v0[ay]; - m_rectverts[2][ax] = v1[ax]; - m_rectverts[2][ay] = v1[ay]; - m_rectverts[3][ax] = v0[ax]; - m_rectverts[3][ay] = v1[ay]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev) -{ if (!(ev->buttons() & Qt::LeftButton) || getEditMode() != ESelectMode) - return; - - pick (ev->x(), ev->y()); - - if (selection().isEmpty()) - return; - - g_win->beginAction (null); - LDObject* obj = selection().first(); - AddObjectDialog::staticDialog (obj->getType(), obj); - g_win->endAction(); - ev->accept(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDOverlay* GLRenderer::findOverlayObject (EFixedCamera cam) -{ LDOverlay* ovlobj = null; - - for (LDObject * obj : getFile()->getObjects()) - { if (obj->getType() == LDObject::Overlay && static_cast<LDOverlay*> (obj)->getCamera() == cam) - { ovlobj = static_cast<LDOverlay*> (obj); - break; - } - } - - return ovlobj; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -// Read in overlays from the current file and update overlay info accordingly. -// ----------------------------------------------------------------------------- -void GLRenderer::initOverlaysFromObjects() -{ for (EFixedCamera cam : g_Cameras) - { if (cam == EFreeCamera) - continue; - - LDGLOverlay& meta = m_overlays[cam]; - LDOverlay* ovlobj = findOverlayObject (cam); - - if (!ovlobj && meta.img) - { delete meta.img; - meta.img = null; - } elif (ovlobj && (!meta.img || meta.fname != ovlobj->getFileName())) - setupOverlay (cam, ovlobj->getFileName(), ovlobj->getX(), - ovlobj->getY(), ovlobj->getWidth(), ovlobj->getHeight()); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::updateOverlayObjects() -{ for (EFixedCamera cam : g_Cameras) - { if (cam == EFreeCamera) - continue; - - LDGLOverlay& meta = m_overlays[cam]; - LDOverlay* ovlobj = findOverlayObject (cam); - - if (!meta.img && ovlobj) - { // If this is the last overlay image, we need to remove the empty space after it as well. - LDObject* nextobj = ovlobj->next(); - - if (nextobj && nextobj->getType() == LDObject::Empty) - { getFile()->forgetObject (nextobj); - delete nextobj; - } - - // If the overlay object was there and the overlay itself is - // not, remove the object. - getFile()->forgetObject (ovlobj); - delete ovlobj; - } elif (meta.img && !ovlobj) - { // Inverse case: image is there but the overlay object is - // not, thus create the object. - ovlobj = new LDOverlay; - - // Find a suitable position to place this object. We want to place - // this into the header, which is everything up to the first scemantic - // object. If we find another overlay object, place this object after - // the last one found. Otherwise, place it before the first schemantic - // object and put an empty object after it (though don't do this if - // there was no schemantic elements at all) - int i, lastOverlay = -1; - bool found = false; - - for (i = 0; i < getFile()->getObjectCount(); ++i) - { LDObject* obj = getFile()->getObject (i); - - if (obj->isScemantic()) - { found = true; - break; - } - - if (obj->getType() == LDObject::Overlay) - lastOverlay = i; - } - - if (lastOverlay != -1) - getFile()->insertObj (lastOverlay + 1, ovlobj); - else - { getFile()->insertObj (i, ovlobj); - - if (found) - getFile()->insertObj (i + 1, new LDEmpty); - } - } - - if (meta.img && ovlobj) - { ovlobj->setCamera (cam); - ovlobj->setFileName (meta.fname); - ovlobj->setX (meta.ox); - ovlobj->setY (meta.oy); - ovlobj->setWidth (meta.lw); - ovlobj->setHeight (meta.lh); - } - } - - if (g_win->R() == this) - g_win->refresh(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,945 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QGridLayout> +#include <QMessageBox> +#include <QEvent> +#include <QContextMenuEvent> +#include <QMenuBar> +#include <QStatusBar> +#include <QSplitter> +#include <QListWidget> +#include <QToolButton> +#include <QComboBox> +#include <QDialogButtonBox> +#include <QToolBar> +#include <QProgressBar> +#include <QLabel> +#include <QFileDialog> +#include <QPushButton> +#include <QCoreApplication> +#include <QTimer> + +#include "main.h" +#include "gldraw.h" +#include "gui.h" +#include "document.h" +#include "config.h" +#include "misc.h" +#include "colors.h" +#include "history.h" +#include "widgets.h" +#include "addObjectDialog.h" +#include "messagelog.h" +#include "config.h" +#include "ui_ldforge.h" +#include "moc_gui.cpp" + +static bool g_isSelectionLocked = false; + +cfg (Bool, lv_colorize, true); +cfg (String, gui_colortoolbar, "16:24:|:1:2:4:14:0:15:|:33:34:36:46"); +cfg (Bool, gui_implicitfiles, false); +extern_cfg (List, io_recentfiles); +extern_cfg (Bool, gl_axes); +extern_cfg (String, gl_maincolor); +extern_cfg (Float, gl_maincolor_alpha); +extern_cfg (Bool, gl_wireframe); +extern_cfg (Bool, gl_colorbfc); + +#define act(N) extern_cfg (KeySequence, key_##N); +#include "actions.h" + +// ============================================================================= +// ----------------------------------------------------------------------------- +ForgeWindow::ForgeWindow() +{ g_win = this; + m_renderer = new GLRenderer; + + ui = new Ui_LDForgeUI; + ui->setupUi (this); + + // Stuff the renderer into its frame + QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame); + rendererLayout->addWidget (R()); + + connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged())); + connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*))); + connect (ui->fileList, SIGNAL (currentItemChanged (QListWidgetItem*, QListWidgetItem*)), this, SLOT (changeCurrentFile())); + + // Init message log manager + m_msglog = new MessageManager; + m_msglog->setRenderer (R()); + m_renderer->setMessageLog (m_msglog); + m_quickColors = quickColorsFromConfig(); + slot_selectionChanged(); + setStatusBar (new QStatusBar); + + // Init primitive loader task stuff + m_primLoaderBar = new QProgressBar; + m_primLoaderWidget = new QWidget; + QHBoxLayout* primLoaderLayout = new QHBoxLayout (m_primLoaderWidget); + primLoaderLayout->addWidget (new QLabel ("Loading primitives:")); + primLoaderLayout->addWidget (m_primLoaderBar); + statusBar()->addPermanentWidget (m_primLoaderWidget); + m_primLoaderWidget->hide(); + + // Make certain actions checkable + ui->actionAxes->setChecked (gl_axes); + ui->actionWireframe->setChecked (gl_wireframe); + ui->actionBFCView->setChecked (gl_colorbfc); + updateGridToolBar(); + updateEditModeActions(); + updateRecentFilesMenu(); + updateToolBars(); + updateTitle(); + + setMinimumSize (300, 200); + + connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup())); + + // Connect all actions and set shortcuts +#define act(N) \ + connect (ui->action##N, SIGNAL (triggered()), this, SLOT (slot_action())); \ + ui->action##N->setShortcut (key_##N); +#include "actions.h" +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::slot_action() +{ // Find out which action triggered this +#define act(N) if (sender() == ui->action##N) invokeAction (ui->action##N, &actiondef_##N); +#include "actions.h" +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::invokeAction (QAction* act, void (*func)()) +{ +#ifdef DEBUG + log ("Action %1 triggered", act->iconText()); +#endif + + beginAction (act); + (*func) (); + endAction(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::slot_lastSecondCleanup() +{ delete m_renderer; + delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::updateRecentFilesMenu() +{ // First, clear any items in the recent files menu +for (QAction * recent : m_recentFiles) + delete recent; + + m_recentFiles.clear(); + + QAction* first = null; + + for (const QVariant& it : io_recentfiles) + { str file = it.toString(); + QAction* recent = new QAction (getIcon ("open-recent"), file, this); + + connect (recent, SIGNAL (triggered()), this, SLOT (slot_recentFile())); + ui->menuOpenRecent->insertAction (first, recent); + m_recentFiles << recent; + first = recent; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QList<LDQuickColor> quickColorsFromConfig() +{ QList<LDQuickColor> colors; + + for (str colorname : gui_colortoolbar.value.split (":")) + { if (colorname == "|") + colors << LDQuickColor::getSeparator(); + else + { LDColor* col = getColor (colorname.toLong()); + + if (col != null) + colors << LDQuickColor (col, null); + } + } + + return colors; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::updateToolBars() +{ m_colorButtons.clear(); + ui->colorToolbar->clear(); + + for (LDQuickColor& entry : m_quickColors) + { if (entry.isSeparator()) + ui->colorToolbar->addSeparator(); + else + { QToolButton* colorButton = new QToolButton; + colorButton->setIcon (makeColorIcon (entry.getColor(), 22)); + colorButton->setIconSize (QSize (22, 22)); + colorButton->setToolTip (entry.getColor()->name); + + connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor())); + ui->colorToolbar->addWidget (colorButton); + m_colorButtons << colorButton; + + entry.setToolButton (colorButton); + } + } + + updateGridToolBar(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::updateGridToolBar() +{ // Ensure that the current grid - and only the current grid - is selected. + ui->actionGridCoarse->setChecked (grid == Grid::Coarse); + ui->actionGridMedium->setChecked (grid == Grid::Medium); + ui->actionGridFine->setChecked (grid == Grid::Fine); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::updateTitle() +{ str title = fmt (APPNAME " %1", fullVersionString()); + + // Append our current file if we have one + if (getCurrentDocument()) + { if (getCurrentDocument()->getName().length() > 0) + title += fmt (": %1", basename (getCurrentDocument()->getName())); + else + title += fmt (": <anonymous>"); + + if (getCurrentDocument()->getObjectCount() > 0 && + getCurrentDocument()->getObject (0)->getType() == LDObject::Comment) + { // Append title + LDComment* comm = static_cast<LDComment*> (getCurrentDocument()->getObject (0)); + title += fmt (": %1", comm->text); + } + + if (getCurrentDocument()->getHistory()->getPosition() != getCurrentDocument()->getSavePosition()) + title += '*'; + } + +#ifdef DEBUG + title += " [debug build]"; +#elif BUILD_ID != BUILD_RELEASE + title += " [release build]"; +#endif // DEBUG + + setWindowTitle (title); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int ForgeWindow::deleteSelection() +{ if (selection().isEmpty()) + return 0; + + QList<LDObject*> selCopy = selection(); + + // Delete the objects that were being selected + for (LDObject* obj : selCopy) + delete obj; + + refresh(); + return selCopy.size(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::buildObjList() +{ if (!getCurrentDocument()) + return; + + // Lock the selection while we do this so that refreshing the object list + // doesn't trigger selection updating so that the selection doesn't get lost + // while this is done. + g_isSelectionLocked = true; + + for (int i = 0; i < ui->objectList->count(); ++i) + delete ui->objectList->item (i); + + ui->objectList->clear(); + + for (LDObject* obj : getCurrentDocument()->getObjects()) + { str descr; + + switch (obj->getType()) + { case LDObject::Comment: + { descr = static_cast<LDComment*> (obj)->text; + + // Remove leading whitespace + while (descr[0] == ' ') + descr.remove (0, 1); + } break; + + case LDObject::Empty: + break; // leave it empty + + case LDObject::Line: + case LDObject::Triangle: + case LDObject::Quad: + case LDObject::CondLine: + { for (int i = 0; i < obj->vertices(); ++i) + { if (i != 0) + descr += ", "; + + descr += obj->getVertex (i).stringRep (true); + } + } break; + + case LDObject::Error: + { descr = fmt ("ERROR: %1", obj->raw()); + } break; + + case LDObject::Vertex: + { descr = static_cast<LDVertex*> (obj)->pos.stringRep (true); + } break; + + case LDObject::Subfile: + { LDSubfile* ref = static_cast<LDSubfile*> (obj); + + descr = fmt ("%1 %2, (", ref->getFileInfo()->getName(), ref->getPosition().stringRep (true)); + + for (int i = 0; i < 9; ++i) + descr += fmt ("%1%2", ref->getTransform()[i], (i != 8) ? " " : ""); + + descr += ')'; + } break; + + case LDObject::BFC: + { descr = LDBFC::statements[static_cast<LDBFC*> (obj)->type]; + } break; + + case LDObject::Overlay: + { LDOverlay* ovl = static_cast<LDOverlay*> (obj); + descr = fmt ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->getCamera()], + basename (ovl->getFileName()), ovl->getX(), ovl->getY(), + ovl->getWidth(), ovl->getHeight()); + } + break; + + default: + { descr = obj->getTypeName(); + } break; + } + + QListWidgetItem* item = new QListWidgetItem (descr); + item->setIcon (getIcon (obj->getTypeName())); + + // Use italic font if hidden + if (obj->isHidden()) + { QFont font = item->font(); + font.setItalic (true); + item->setFont (font); + } + + // Color gibberish orange on red so it stands out. + if (obj->getType() == LDObject::Error) + { item->setBackground (QColor ("#AA0000")); + item->setForeground (QColor ("#FFAA00")); + } + elif (lv_colorize && obj->isColored() && obj->getColor() != maincolor && obj->getColor() != edgecolor) + { // If the object isn't in the main or edge color, draw this + // list entry in said color. + LDColor* col = getColor (obj->getColor()); + + if (col) + item->setForeground (col->faceColor); + } + + obj->qObjListEntry = item; + ui->objectList->insertItem (ui->objectList->count(), item); + } + + g_isSelectionLocked = false; + updateSelection(); + scrollToSelection(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::scrollToSelection() +{ if (selection().isEmpty()) + return; + + LDObject* obj = selection().last(); + ui->objectList->scrollToItem (obj->qObjListEntry); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::slot_selectionChanged() +{ if (g_isSelectionLocked == true || getCurrentDocument() == null) + return; + + // Update the shared selection array, though don't do this if this was + // called during GL picking, in which case the GL renderer takes care + // of the selection. + if (m_renderer->isPicking()) + return; + + QList<LDObject*> priorSelection = selection(); + + // Get the objects from the object list selection + getCurrentDocument()->clearSelection(); + const QList<QListWidgetItem*> items = ui->objectList->selectedItems(); + + for (LDObject* obj : getCurrentDocument()->getObjects()) + { for (QListWidgetItem* item : items) + { if (item == obj->qObjListEntry) + { obj->select(); + break; + } + } + } + + // Update the GL renderer + QList<LDObject*> compound = priorSelection + selection(); + removeDuplicates (compound); + + for (LDObject* obj : compound) + m_renderer->compileObject (obj); + + m_renderer->update(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::slot_recentFile() +{ QAction* qAct = static_cast<QAction*> (sender()); + openMainFile (qAct->text()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::slot_quickColor() +{ beginAction (null); + QToolButton* button = static_cast<QToolButton*> (sender()); + LDColor* col = null; + + for (const LDQuickColor & entry : m_quickColors) + { if (entry.getToolButton() == button) + { col = entry.getColor(); + break; + } + } + + if (col == null) + return; + + int newColor = col->index; + + for (LDObject* obj : selection()) + { if (obj->isColored() == false) + continue; // uncolored object + + obj->setColor (newColor); + R()->compileObject (obj); + } + + refresh(); + endAction(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int ForgeWindow::getInsertionPoint() +{ // If we have a selection, put the item after it. + if (!selection().isEmpty()) + return selection().last()->getIndex() + 1; + + // Otherwise place the object at the end. + return getCurrentDocument()->getObjectCount(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::doFullRefresh() +{ buildObjList(); + m_renderer->hardRefresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::refresh() +{ buildObjList(); + m_renderer->update(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::updateSelection() +{ g_isSelectionLocked = true; + + for (LDObject* obj : getCurrentDocument()->getObjects()) + obj->setSelected (false); + + ui->objectList->clearSelection(); + + for (LDObject* obj : selection()) + { if (obj->qObjListEntry == null) + continue; + + obj->qObjListEntry->setSelected (true); + obj->setSelected (true); + } + + g_isSelectionLocked = false; + slot_selectionChanged(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int ForgeWindow::getSelectedColor() +{ int result = -1; + + for (LDObject* obj : selection()) + { if (obj->isColored() == false) + continue; // doesn't use color + + if (result != -1 && obj->getColor() != result) + return -1; // No consensus in object color + + if (result == -1) + result = obj->getColor(); + } + + return result; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject::Type ForgeWindow::getUniformSelectedType() +{ LDObject::Type result = LDObject::Unidentified; + + for (LDObject * obj : selection()) + { if (result != LDObject::Unidentified && obj->getColor() != result) + return LDObject::Unidentified; + + if (result == LDObject::Unidentified) + result = obj->getType(); + } + + return result; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::closeEvent (QCloseEvent* ev) +{ // Check whether it's safe to close all files. + if (!safeToCloseAll()) + { ev->ignore(); + return; + } + + // Save the configuration before leaving so that, for instance, grid choice + // is preserved across instances. + Config::save(); + + ev->accept(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::spawnContextMenu (const QPoint pos) +{ const bool single = (selection().size() == 1); + LDObject* singleObj = (single) ? selection()[0] : null; + + QMenu* contextMenu = new QMenu; + + if (single && singleObj->getType() != LDObject::Empty) + { contextMenu->addAction (ui->actionEdit); + contextMenu->addSeparator(); + } + + contextMenu->addAction (ui->actionCut); + contextMenu->addAction (ui->actionCopy); + contextMenu->addAction (ui->actionPaste); + contextMenu->addAction (ui->actionDelete); + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionSetColor); + + if (single) + contextMenu->addAction (ui->actionEditRaw); + + contextMenu->addAction (ui->actionBorders); + contextMenu->addAction (ui->actionSetOverlay); + contextMenu->addAction (ui->actionClearOverlay); + contextMenu->addAction (ui->actionModeSelect); + contextMenu->addAction (ui->actionModeDraw); + contextMenu->addAction (ui->actionModeCircle); + + if (R()->camera() != GL::EFreeCamera) + { contextMenu->addSeparator(); + contextMenu->addAction (ui->actionSetDrawDepth); + } + + contextMenu->exec (pos); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::deleteObjects (QList<LDObject*> objs) +{ for (LDObject * obj : objs) + { getCurrentDocument()->forgetObject (obj); + delete obj; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::deleteByColor (const int colnum) +{ QList<LDObject*> objs; + + for (LDObject* obj : getCurrentDocument()->getObjects()) + { if (!obj->isColored() || obj->getColor() != colnum) + continue; + + objs << obj; + } + + deleteObjects (objs); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::updateEditModeActions() +{ const EditMode mode = R()->getEditMode(); + ui->actionModeSelect->setChecked (mode == ESelectMode); + ui->actionModeDraw->setChecked (mode == EDrawMode); + ui->actionModeCircle->setChecked (mode == ECircleMode); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::slot_editObject (QListWidgetItem* listitem) +{ LDObject* obj = null; + + for (LDObject* it : getCurrentDocument()->getObjects()) + { if (it->qObjListEntry == listitem) + { obj = it; + break; + } + } + + AddObjectDialog::staticDialog (obj->getType(), obj); +} + +#if 0 +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::primitiveLoaderStart (int max) +{ m_primLoaderWidget->show(); + m_primLoaderBar->setRange (0, max); + m_primLoaderBar->setValue (0); + m_primLoaderBar->setFormat ("%p%"); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::primitiveLoaderUpdate (int prog) +{ m_primLoaderBar->setValue (prog); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::primitiveLoaderEnd() +{ QTimer* hidetimer = new QTimer; + connect (hidetimer, SIGNAL (timeout()), m_primLoaderWidget, SLOT (hide())); + hidetimer->setSingleShot (true); + hidetimer->start (1500); + m_primLoaderBar->setFormat (tr ("Done")); + log (tr ("Primitives scanned: %1 primitives listed"), m_primLoaderBar->value()); +} +#endif + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::save (LDDocument* f, bool saveAs) +{ str path = f->getName(); + + if (saveAs || path.isEmpty()) + { path = QFileDialog::getSaveFileName (g_win, tr ("Save As"), + (f->getName().isEmpty()) ? f->getName() : f->getDefaultName(), + tr ("LDraw files (*.dat *.ldr)")); + + if (path.isEmpty()) + { // User didn't give a file name, abort. + return; + } + } + + if (f->save (path)) + { f->setName (path); + + if (f == getCurrentDocument()) + g_win->updateTitle(); + + log ("Saved to %1.", path); + + // Add it to recent files + addRecentFile (path); + } + else + { str message = fmt (tr ("Failed to save to %1: %2"), path, strerror (errno)); + + // Tell the user the save failed, and give the option for saving as with it. + QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win); + + // Add a save-as button + QPushButton* saveAsBtn = new QPushButton (tr ("Save As")); + saveAsBtn->setIcon (getIcon ("file-save-as")); + dlg.addButton (saveAsBtn, QMessageBox::ActionRole); + dlg.setDefaultButton (QMessageBox::Close); + dlg.exec(); + + if (dlg.clickedButton() == saveAsBtn) + save (f, true); // yay recursion! + } +} + +void ForgeWindow::addMessage (str msg) +{ m_msglog->addLine (msg); +} + +// ============================================================================ +void ObjectList::contextMenuEvent (QContextMenuEvent* ev) +{ g_win->spawnContextMenu (ev->globalPos()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QPixmap getIcon (str iconName) +{ return (QPixmap (fmt (":/icons/%1.png", iconName))); +} + +// ============================================================================= +bool confirm (str msg) +{ return confirm (ForgeWindow::tr ("Confirm"), msg); +} + +bool confirm (str title, str msg) +{ return QMessageBox::question (g_win, title, msg, + (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes; +} + +// ============================================================================= +void critical (str msg) +{ QMessageBox::critical (g_win, ForgeWindow::tr ("Error"), msg, + (QMessageBox::Close), QMessageBox::Close); +} + +// ============================================================================= +QIcon makeColorIcon (LDColor* colinfo, const int size) +{ // Create an image object and link a painter to it. + QImage img (size, size, QImage::Format_ARGB32); + QPainter paint (&img); + QColor col = colinfo->faceColor; + + if (colinfo->index == maincolor) + { // Use the user preferences for main color here + col = gl_maincolor.value; + col.setAlphaF (gl_maincolor_alpha); + } + + // Paint the icon border + paint.fillRect (QRect (0, 0, size, size), colinfo->edgeColor); + + // Paint the checkerboard background, visible with translucent icons + paint.drawPixmap (QRect (1, 1, size - 2, size - 2), getIcon ("checkerboard"), QRect (0, 0, 8, 8)); + + // Paint the color above the checkerboard + paint.fillRect (QRect (1, 1, size - 2, size - 2), col); + return QIcon (QPixmap::fromImage (img)); +} + +// ============================================================================= +void makeColorComboBox (QComboBox* box) +{ std::map<int, int> counts; + + for (LDObject * obj : getCurrentDocument()->getObjects()) + { if (!obj->isColored()) + continue; + + if (counts.find (obj->getColor()) == counts.end()) + counts[obj->getColor()] = 1; + else + counts[obj->getColor()]++; + } + + box->clear(); + int row = 0; + + for (const auto& pair : counts) + { LDColor* col = getColor (pair.first); + assert (col != null); + + QIcon ico = makeColorIcon (col, 16); + box->addItem (ico, fmt ("[%1] %2 (%3 object%4)", + pair.first, col->name, pair.second, plural (pair.second))); + box->setItemData (row, pair.first); + + ++row; + } +} + +void ForgeWindow::setStatusBarText (str text) +{ statusBar()->showMessage (text); +} + +Ui_LDForgeUI* ForgeWindow::interface() const +{ return ui; +} + +#define act(N) QAction* ForgeWindow::action##N() { return ui->action##N; } +#include "actions.h" + +void ForgeWindow::updateDocumentList() +{ ui->fileList->clear(); + + for (LDDocument* f : g_loadedFiles) + { // Don't list implicit files unless explicitly desired. + if (f->isImplicit() && !gui_implicitfiles) + continue; + + // Add an item to the list for this file and store a pointer to it in + // the file, so we can find files by the list item. + ui->fileList->addItem (""); + QListWidgetItem* item = ui->fileList->item (ui->fileList->count() - 1); + f->setListItem (item); + + updateDocumentListItem (f); + } +} + +void ForgeWindow::updateDocumentListItem (LDDocument* f) +{ if (f->getListItem() == null) + { // We don't have a list item for this file, so the list either doesn't + // exist yet or is out of date. Build the list now. + updateDocumentList(); + return; + } + + // If this is the current file, it also needs to be the selected item on + // the list. + if (f == getCurrentDocument()) + ui->fileList->setCurrentItem (f->getListItem()); + + // If we list implicit files, draw them with a shade of gray to make them + // distinct. + if (f->isImplicit()) + f->getListItem()->setForeground (QColor (96, 96, 96)); + + f->getListItem()->setText (f->getShortName()); + + // If the document has unsaved changes, draw a little icon next to it to mark that. + f->getListItem()->setIcon (f->hasUnsavedChanges() ? getIcon ("file-save") : QIcon()); +} + +void ForgeWindow::beginAction (QAction* act) +{ // Open the history so we can record the edits done during this action. + if (act == ui->actionUndo || act == ui->actionRedo || act == ui->actionOpen) + getCurrentDocument()->getHistory()->setIgnoring (true); +} + +void ForgeWindow::endAction() +{ // Close the history now. + getCurrentDocument()->addHistoryStep(); + + // Update the list item of the current file - we may need to draw an icon + // now that marks it as having unsaved changes. + updateDocumentListItem (getCurrentDocument()); +} + +// ============================================================================= +// A file is selected from the list of files on the left of the screen. Find out +// which file was picked and change to it. +void ForgeWindow::changeCurrentFile() +{ LDDocument* f = null; + QListWidgetItem* item = ui->fileList->currentItem(); + + // Find the file pointer of the item that was selected. + for (LDDocument* it : g_loadedFiles) + { if (it->getListItem() == item) + { f = it; + break; + } + } + + // If we picked the same file we're currently on, we don't need to do + // anything. + if (!f || f == getCurrentDocument()) + return; + + LDDocument::setCurrent (f); +} + +void ForgeWindow::refreshObjectList() +{ +#if 0 + ui->objectList->clear(); + LDDocument* f = getCurrentDocument(); + +for (LDObject * obj : *f) + ui->objectList->addItem (obj->qObjListEntry); + +#endif + + buildObjList(); +} + +void ForgeWindow::updateActions() +{ History* his = getCurrentDocument()->getHistory(); + int pos = his->getPosition(); + ui->actionUndo->setEnabled (pos != -1); + ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1); + ui->actionAxes->setChecked (gl_axes); + ui->actionBFCView->setChecked (gl_colorbfc); +} + +QImage imageFromScreencap (uchar* data, int w, int h) +{ // GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well. + return QImage (data, w, h, QImage::Format_ARGB32).rgbSwapped().mirrored(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDQuickColor::LDQuickColor (LDColor* color, QToolButton* toolButton) : + m_Color (color), + m_ToolButton (toolButton) {} + +LDQuickColor LDQuickColor::getSeparator() +{ return LDQuickColor (null, null); +} + +bool LDQuickColor::isSeparator() const +{ return getColor() == null; +}
--- a/src/gui.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,942 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QGridLayout> -#include <QMessageBox> -#include <QEvent> -#include <QContextMenuEvent> -#include <QMenuBar> -#include <QStatusBar> -#include <QSplitter> -#include <QListWidget> -#include <QToolButton> -#include <QComboBox> -#include <QDialogButtonBox> -#include <QToolBar> -#include <QProgressBar> -#include <QLabel> -#include <QFileDialog> -#include <QPushButton> -#include <QCoreApplication> -#include <QTimer> - -#include "main.h" -#include "gldraw.h" -#include "gui.h" -#include "document.h" -#include "config.h" -#include "misc.h" -#include "colors.h" -#include "history.h" -#include "widgets.h" -#include "addObjectDialog.h" -#include "messagelog.h" -#include "config.h" -#include "ui_ldforge.h" -#include "moc_gui.cpp" - -static bool g_isSelectionLocked = false; - -cfg (Bool, lv_colorize, true); -cfg (String, gui_colortoolbar, "16:24:|:1:2:4:14:0:15:|:33:34:36:46"); -cfg (Bool, gui_implicitfiles, false); -extern_cfg (List, io_recentfiles); -extern_cfg (Bool, gl_axes); -extern_cfg (String, gl_maincolor); -extern_cfg (Float, gl_maincolor_alpha); -extern_cfg (Bool, gl_wireframe); -extern_cfg (Bool, gl_colorbfc); - -#define act(N) extern_cfg (KeySequence, key_##N); -#include "actions.h" - -// ============================================================================= -// ----------------------------------------------------------------------------- -ForgeWindow::ForgeWindow() -{ g_win = this; - m_renderer = new GLRenderer; - - ui = new Ui_LDForgeUI; - ui->setupUi (this); - - // Stuff the renderer into its frame - QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame); - rendererLayout->addWidget (R()); - - connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged())); - connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*))); - connect (ui->fileList, SIGNAL (currentItemChanged (QListWidgetItem*, QListWidgetItem*)), this, SLOT (changeCurrentFile())); - - // Init message log manager - m_msglog = new MessageManager; - m_msglog->setRenderer (R()); - m_renderer->setMessageLog (m_msglog); - m_quickColors = quickColorsFromConfig(); - slot_selectionChanged(); - setStatusBar (new QStatusBar); - - // Init primitive loader task stuff - m_primLoaderBar = new QProgressBar; - m_primLoaderWidget = new QWidget; - QHBoxLayout* primLoaderLayout = new QHBoxLayout (m_primLoaderWidget); - primLoaderLayout->addWidget (new QLabel ("Loading primitives:")); - primLoaderLayout->addWidget (m_primLoaderBar); - statusBar()->addPermanentWidget (m_primLoaderWidget); - m_primLoaderWidget->hide(); - - // Make certain actions checkable - ui->actionAxes->setChecked (gl_axes); - ui->actionWireframe->setChecked (gl_wireframe); - ui->actionBFCView->setChecked (gl_colorbfc); - updateGridToolBar(); - updateEditModeActions(); - updateRecentFilesMenu(); - updateToolBars(); - updateTitle(); - - setMinimumSize (300, 200); - - connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup())); - - // Connect all actions and set shortcuts -#define act(N) \ - connect (ui->action##N, SIGNAL (triggered()), this, SLOT (slot_action())); \ - ui->action##N->setShortcut (key_##N); -#include "actions.h" -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_action() -{ // Find out which action triggered this -#define act(N) if (sender() == ui->action##N) invokeAction (ui->action##N, &actiondef_##N); -#include "actions.h" -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::invokeAction (QAction* act, void (*func)()) -{ -#ifdef DEBUG - log ("Action %1 triggered", act->iconText()); -#endif - - beginAction (act); - (*func) (); - endAction(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_lastSecondCleanup() -{ delete m_renderer; - delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateRecentFilesMenu() -{ // First, clear any items in the recent files menu -for (QAction * recent : m_recentFiles) - delete recent; - - m_recentFiles.clear(); - - QAction* first = null; - - for (const QVariant& it : io_recentfiles) - { str file = it.toString(); - QAction* recent = new QAction (getIcon ("open-recent"), file, this); - - connect (recent, SIGNAL (triggered()), this, SLOT (slot_recentFile())); - ui->menuOpenRecent->insertAction (first, recent); - m_recentFiles << recent; - first = recent; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDQuickColor> quickColorsFromConfig() -{ QList<LDQuickColor> colors; - -for (str colorname : gui_colortoolbar.value.split (":")) - { if (colorname == "|") - colors << LDQuickColor::getSeparator(); - else - { LDColor* col = getColor (colorname.toLong()); - - if (col != null) - colors << LDQuickColor (col, null); - } - } - - return colors; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateToolBars() -{ m_colorButtons.clear(); - ui->colorToolbar->clear(); - -for (LDQuickColor & entry : m_quickColors) - { if (entry.isSeparator()) - ui->colorToolbar->addSeparator(); - else - { QToolButton* colorButton = new QToolButton; - colorButton->setIcon (makeColorIcon (entry.getColor(), 22)); - colorButton->setIconSize (QSize (22, 22)); - colorButton->setToolTip (entry.getColor()->name); - - connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor())); - ui->colorToolbar->addWidget (colorButton); - m_colorButtons << colorButton; - - entry.setToolButton (colorButton); - } - } - - updateGridToolBar(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateGridToolBar() -{ // Ensure that the current grid - and only the current grid - is selected. - ui->actionGridCoarse->setChecked (grid == Grid::Coarse); - ui->actionGridMedium->setChecked (grid == Grid::Medium); - ui->actionGridFine->setChecked (grid == Grid::Fine); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateTitle() -{ str title = fmt (APPNAME " %1", fullVersionString()); - - // Append our current file if we have one - if (getCurrentDocument()) - { if (getCurrentDocument()->getName().length() > 0) - title += fmt (": %1", basename (getCurrentDocument()->getName())); - else - title += fmt (": <anonymous>"); - - if (getCurrentDocument()->getObjectCount() > 0 && - getCurrentDocument()->getObject (0)->getType() == LDObject::Comment) - { // Append title - LDComment* comm = static_cast<LDComment*> (getCurrentDocument()->getObject (0)); - title += fmt (": %1", comm->text); - } - - if (getCurrentDocument()->getHistory()->getPosition() != getCurrentDocument()->getSavePosition()) - title += '*'; - } - -#ifdef DEBUG - title += " [debug build]"; -#elif BUILD_ID != BUILD_RELEASE - title += " [release build]"; -#endif // DEBUG - - setWindowTitle (title); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int ForgeWindow::deleteSelection() -{ if (selection().isEmpty()) - return 0; - - QList<LDObject*> selCopy = selection(); - - // Delete the objects that were being selected - for (LDObject* obj : selCopy) - delete obj; - - refresh(); - return selCopy.size(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::buildObjList() -{ if (!getCurrentDocument()) - return; - - // Lock the selection while we do this so that refreshing the object list - // doesn't trigger selection updating so that the selection doesn't get lost - // while this is done. - g_isSelectionLocked = true; - - for (int i = 0; i < ui->objectList->count(); ++i) - delete ui->objectList->item (i); - - ui->objectList->clear(); - - for (LDObject* obj : getCurrentDocument()->getObjects()) - { str descr; - - switch (obj->getType()) - { case LDObject::Comment: - { descr = static_cast<LDComment*> (obj)->text; - - // Remove leading whitespace - while (descr[0] == ' ') - descr.remove (0, 1); - } break; - - case LDObject::Empty: - break; // leave it empty - - case LDObject::Line: - case LDObject::Triangle: - case LDObject::Quad: - case LDObject::CondLine: - { for (int i = 0; i < obj->vertices(); ++i) - { if (i != 0) - descr += ", "; - - descr += obj->getVertex (i).stringRep (true); - } - } break; - - case LDObject::Error: - { descr = fmt ("ERROR: %1", obj->raw()); - } break; - - case LDObject::Vertex: - { descr = static_cast<LDVertex*> (obj)->pos.stringRep (true); - } break; - - case LDObject::Subfile: - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - - descr = fmt ("%1 %2, (", ref->getFileInfo()->getName(), ref->getPosition().stringRep (true)); - - for (int i = 0; i < 9; ++i) - descr += fmt ("%1%2", ref->getTransform()[i], (i != 8) ? " " : ""); - - descr += ')'; - } break; - - case LDObject::BFC: - { descr = LDBFC::statements[static_cast<LDBFC*> (obj)->type]; - } break; - - case LDObject::Overlay: - { LDOverlay* ovl = static_cast<LDOverlay*> (obj); - descr = fmt ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->getCamera()], - basename (ovl->getFileName()), ovl->getX(), ovl->getY(), - ovl->getWidth(), ovl->getHeight()); - } - break; - - default: - { descr = obj->getTypeName(); - } break; - } - - QListWidgetItem* item = new QListWidgetItem (descr); - item->setIcon (getIcon (obj->getTypeName())); - - // Use italic font if hidden - if (obj->isHidden()) - { QFont font = item->font(); - font.setItalic (true); - item->setFont (font); - } - - // Color gibberish orange on red so it stands out. - if (obj->getType() == LDObject::Error) - { item->setBackground (QColor ("#AA0000")); - item->setForeground (QColor ("#FFAA00")); - } - elif (lv_colorize && obj->isColored() && obj->getColor() != maincolor && obj->getColor() != edgecolor) - { // If the object isn't in the main or edge color, draw this - // list entry in said color. - LDColor* col = getColor (obj->getColor()); - - if (col) - item->setForeground (col->faceColor); - } - - obj->qObjListEntry = item; - ui->objectList->insertItem (ui->objectList->count(), item); - } - - g_isSelectionLocked = false; - updateSelection(); - scrollToSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::scrollToSelection() -{ if (selection().isEmpty()) - return; - - LDObject* obj = selection().last(); - ui->objectList->scrollToItem (obj->qObjListEntry); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_selectionChanged() -{ if (g_isSelectionLocked == true || getCurrentDocument() == null) - return; - - // Update the shared selection array, though don't do this if this was - // called during GL picking, in which case the GL renderer takes care - // of the selection. - if (m_renderer->isPicking()) - return; - - QList<LDObject*> priorSelection = selection(); - - // Get the objects from the object list selection - getCurrentDocument()->clearSelection(); - const QList<QListWidgetItem*> items = ui->objectList->selectedItems(); - - for (LDObject* obj : getCurrentDocument()->getObjects()) - { for (QListWidgetItem* item : items) - { if (item == obj->qObjListEntry) - { obj->select(); - break; - } - } - } - - // Update the GL renderer - QList<LDObject*> compound = priorSelection + selection(); - removeDuplicates (compound); - - for (LDObject* obj : compound) - m_renderer->compileObject (obj); - - m_renderer->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_recentFile() -{ QAction* qAct = static_cast<QAction*> (sender()); - openMainFile (qAct->text()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_quickColor() -{ beginAction (null); - QToolButton* button = static_cast<QToolButton*> (sender()); - LDColor* col = null; - - for (const LDQuickColor & entry : m_quickColors) - { if (entry.getToolButton() == button) - { col = entry.getColor(); - break; - } - } - - if (col == null) - return; - - int newColor = col->index; - - for (LDObject* obj : selection()) - { if (obj->isColored() == false) - continue; // uncolored object - - obj->setColor (newColor); - R()->compileObject (obj); - } - - refresh(); - endAction(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int ForgeWindow::getInsertionPoint() -{ // If we have a selection, put the item after it. - if (!selection().isEmpty()) - return selection().last()->getIndex() + 1; - - // Otherwise place the object at the end. - return getCurrentDocument()->getObjectCount(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::doFullRefresh() -{ buildObjList(); - m_renderer->hardRefresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::refresh() -{ buildObjList(); - m_renderer->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateSelection() -{ g_isSelectionLocked = true; - - for (LDObject* obj : getCurrentDocument()->getObjects()) - obj->setSelected (false); - - ui->objectList->clearSelection(); - - for (LDObject* obj : selection()) - { if (obj->qObjListEntry == null) - continue; - - obj->qObjListEntry->setSelected (true); - obj->setSelected (true); - } - - g_isSelectionLocked = false; - slot_selectionChanged(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int ForgeWindow::getSelectedColor() -{ int result = -1; - - for (LDObject* obj : selection()) - { if (obj->isColored() == false) - continue; // doesn't use color - - if (result != -1 && obj->getColor() != result) - return -1; // No consensus in object color - - if (result == -1) - result = obj->getColor(); - } - - return result; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject::Type ForgeWindow::getUniformSelectedType() -{ LDObject::Type result = LDObject::Unidentified; - - for (LDObject * obj : selection()) - { if (result != LDObject::Unidentified && obj->getColor() != result) - return LDObject::Unidentified; - - if (result == LDObject::Unidentified) - result = obj->getType(); - } - - return result; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::closeEvent (QCloseEvent* ev) -{ // Check whether it's safe to close all files. - if (!safeToCloseAll()) - { ev->ignore(); - return; - } - - // Save the configuration before leaving so that, for instance, grid choice - // is preserved across instances. - Config::save(); - - ev->accept(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::spawnContextMenu (const QPoint pos) -{ const bool single = (selection().size() == 1); - LDObject* singleObj = (single) ? selection()[0] : null; - - QMenu* contextMenu = new QMenu; - - if (single && singleObj->getType() != LDObject::Empty) - { contextMenu->addAction (ui->actionEdit); - contextMenu->addSeparator(); - } - - contextMenu->addAction (ui->actionCut); - contextMenu->addAction (ui->actionCopy); - contextMenu->addAction (ui->actionPaste); - contextMenu->addAction (ui->actionDelete); - contextMenu->addSeparator(); - contextMenu->addAction (ui->actionSetColor); - - if (single) - contextMenu->addAction (ui->actionEditRaw); - - contextMenu->addAction (ui->actionBorders); - contextMenu->addAction (ui->actionSetOverlay); - contextMenu->addAction (ui->actionClearOverlay); - contextMenu->addAction (ui->actionModeSelect); - contextMenu->addAction (ui->actionModeDraw); - contextMenu->addAction (ui->actionModeCircle); - - if (R()->camera() != GL::EFreeCamera) - { contextMenu->addSeparator(); - contextMenu->addAction (ui->actionSetDrawDepth); - } - - contextMenu->exec (pos); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::deleteObjects (QList<LDObject*> objs) -{ for (LDObject * obj : objs) - { getCurrentDocument()->forgetObject (obj); - delete obj; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::deleteByColor (const int colnum) -{ QList<LDObject*> objs; - - for (LDObject* obj : getCurrentDocument()->getObjects()) - { if (!obj->isColored() || obj->getColor() != colnum) - continue; - - objs << obj; - } - - deleteObjects (objs); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateEditModeActions() -{ const EditMode mode = R()->getEditMode(); - ui->actionModeSelect->setChecked (mode == ESelectMode); - ui->actionModeDraw->setChecked (mode == EDrawMode); - ui->actionModeCircle->setChecked (mode == ECircleMode); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_editObject (QListWidgetItem* listitem) -{ LDObject* obj = null; - - for (LDObject* it : getCurrentDocument()->getObjects()) - { if (it->qObjListEntry == listitem) - { obj = it; - break; - } - } - - AddObjectDialog::staticDialog (obj->getType(), obj); -} - -#if 0 -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::primitiveLoaderStart (int max) -{ m_primLoaderWidget->show(); - m_primLoaderBar->setRange (0, max); - m_primLoaderBar->setValue (0); - m_primLoaderBar->setFormat ("%p%"); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::primitiveLoaderUpdate (int prog) -{ m_primLoaderBar->setValue (prog); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::primitiveLoaderEnd() -{ QTimer* hidetimer = new QTimer; - connect (hidetimer, SIGNAL (timeout()), m_primLoaderWidget, SLOT (hide())); - hidetimer->setSingleShot (true); - hidetimer->start (1500); - m_primLoaderBar->setFormat (tr ("Done")); - log (tr ("Primitives scanned: %1 primitives listed"), m_primLoaderBar->value()); -} -#endif - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::save (LDDocument* f, bool saveAs) -{ str path = f->getName(); - - if (saveAs || path.isEmpty()) - { path = QFileDialog::getSaveFileName (g_win, tr ("Save As"), - (f->getName().isEmpty()) ? f->getName() : f->getDefaultName(), - tr ("LDraw files (*.dat *.ldr)")); - - if (path.isEmpty()) - { // User didn't give a file name, abort. - return; - } - } - - if (f->save (path)) - { f->setName (path); - - if (f == getCurrentDocument()) - g_win->updateTitle(); - - log ("Saved to %1.", path); - - // Add it to recent files - addRecentFile (path); - } - else - { str message = fmt (tr ("Failed to save to %1: %2"), path, strerror (errno)); - - // Tell the user the save failed, and give the option for saving as with it. - QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win); - - // Add a save-as button - QPushButton* saveAsBtn = new QPushButton (tr ("Save As")); - saveAsBtn->setIcon (getIcon ("file-save-as")); - dlg.addButton (saveAsBtn, QMessageBox::ActionRole); - dlg.setDefaultButton (QMessageBox::Close); - dlg.exec(); - - if (dlg.clickedButton() == saveAsBtn) - save (f, true); // yay recursion! - } -} - -void ForgeWindow::addMessage (str msg) -{ m_msglog->addLine (msg); -} - -// ============================================================================ -void ObjectList::contextMenuEvent (QContextMenuEvent* ev) -{ g_win->spawnContextMenu (ev->globalPos()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QPixmap getIcon (str iconName) -{ return (QPixmap (fmt (":/icons/%1.png", iconName))); -} - -// ============================================================================= -bool confirm (str msg) -{ return confirm (ForgeWindow::tr ("Confirm"), msg); -} - -bool confirm (str title, str msg) -{ return QMessageBox::question (g_win, title, msg, - (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes; -} - -// ============================================================================= -void critical (str msg) -{ QMessageBox::critical (g_win, ForgeWindow::tr ("Error"), msg, - (QMessageBox::Close), QMessageBox::Close); -} - -// ============================================================================= -QIcon makeColorIcon (LDColor* colinfo, const int size) -{ // Create an image object and link a painter to it. - QImage img (size, size, QImage::Format_ARGB32); - QPainter paint (&img); - - QColor col = colinfo->faceColor; - - if (colinfo->index == maincolor) - { // Use the user preferences for main color here - col = gl_maincolor.value; - col.setAlphaF (gl_maincolor_alpha); - } - - // Paint the icon - paint.fillRect (QRect (0, 0, size, size), colinfo->edgeColor); - paint.drawPixmap (QRect (1, 1, size - 2, size - 2), getIcon ("checkerboard"), QRect (0, 0, 8, 8)); - paint.fillRect (QRect (1, 1, size - 2, size - 2), col); - return QIcon (QPixmap::fromImage (img)); -} - -// ============================================================================= -void makeColorComboBox (QComboBox* box) -{ std::map<int, int> counts; - - for (LDObject * obj : getCurrentDocument()->getObjects()) - { if (!obj->isColored()) - continue; - - if (counts.find (obj->getColor()) == counts.end()) - counts[obj->getColor()] = 1; - else - counts[obj->getColor()]++; - } - - box->clear(); - int row = 0; - - for (const std::pair<int, int>& pair : counts) - { LDColor* col = getColor (pair.first); - assert (col != null); - - QIcon ico = makeColorIcon (col, 16); - box->addItem (ico, fmt ("[%1] %2 (%3 object%4)", - pair.first, col->name, pair.second, plural (pair.second))); - box->setItemData (row, pair.first); - - ++row; - } -} - -void ForgeWindow::setStatusBarText (str text) -{ statusBar()->showMessage (text); -} - -Ui_LDForgeUI* ForgeWindow::interface() const -{ return ui; -} - -#define act(N) QAction* ForgeWindow::action##N() { return ui->action##N; } -#include "actions.h" - -void ForgeWindow::updateDocumentList() -{ ui->fileList->clear(); - - for (LDDocument* f : g_loadedFiles) - { // Don't list implicit files unless explicitly desired. - if (f->isImplicit() && !gui_implicitfiles) - continue; - - // Add an item to the list for this file and store a pointer to it in - // the file, so we can find files by the list item. - ui->fileList->addItem (""); - QListWidgetItem* item = ui->fileList->item (ui->fileList->count() - 1); - f->setListItem (item); - - updateDocumentListItem (f); - } -} - -void ForgeWindow::updateDocumentListItem (LDDocument* f) -{ if (f->getListItem() == null) - { // We don't have a list item for this file, so the list either doesn't - // exist yet or is out of date. Build the list now. - updateDocumentList(); - return; - } - - // If this is the current file, it also needs to be the selected item on - // the list. - if (f == getCurrentDocument()) - ui->fileList->setCurrentItem (f->getListItem()); - - // If we list implicit files, draw them with a shade of gray to make them - // distinct. - if (f->isImplicit()) - f->getListItem()->setForeground (QColor (96, 96, 96)); - - f->getListItem()->setText (f->getShortName()); - - // If the document has unsaved changes, draw a little icon next to it to mark that. - f->getListItem()->setIcon (f->hasUnsavedChanges() ? getIcon ("file-save") : QIcon()); -} - -void ForgeWindow::beginAction (QAction* act) -{ // Open the history so we can record the edits done during this action. - if (act == ui->actionUndo || act == ui->actionRedo || act == ui->actionOpen) - getCurrentDocument()->getHistory()->setIgnoring (true); -} - -void ForgeWindow::endAction() -{ // Close the history now. - getCurrentDocument()->addHistoryStep(); - - // Update the list item of the current file - we may need to draw an icon - // now that marks it as having unsaved changes. - updateDocumentListItem (getCurrentDocument()); -} - -// ============================================================================= -// A file is selected from the list of files on the left of the screen. Find out -// which file was picked and change to it. -void ForgeWindow::changeCurrentFile() -{ LDDocument* f = null; - QListWidgetItem* item = ui->fileList->currentItem(); - - // Find the file pointer of the item that was selected. - for (LDDocument* it : g_loadedFiles) - { if (it->getListItem() == item) - { f = it; - break; - } - } - - // If we picked the same file we're currently on, we don't need to do - // anything. - if (!f || f == getCurrentDocument()) - return; - - LDDocument::setCurrent (f); -} - -void ForgeWindow::refreshObjectList() -{ -#if 0 - ui->objectList->clear(); - LDDocument* f = getCurrentDocument(); - -for (LDObject * obj : *f) - ui->objectList->addItem (obj->qObjListEntry); - -#endif - - buildObjList(); -} - -void ForgeWindow::updateActions() -{ History* his = getCurrentDocument()->getHistory(); - int pos = his->getPosition(); - ui->actionUndo->setEnabled (pos != -1); - ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1); - ui->actionAxes->setChecked (gl_axes); - ui->actionBFCView->setChecked (gl_colorbfc); -} - -QImage imageFromScreencap (uchar* data, int w, int h) -{ // GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well. - return QImage (data, w, h, QImage::Format_ARGB32).rgbSwapped().mirrored(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDQuickColor::LDQuickColor (LDColor* color, QToolButton* toolButton) : - m_Color (color), - m_ToolButton (toolButton) {} - -LDQuickColor LDQuickColor::getSeparator() -{ return LDQuickColor (null, null); -} - -bool LDQuickColor::isSeparator() const -{ return getColor() == null; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui_actions.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,634 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QFileDialog> +#include <QMessageBox> +#include <QTextEdit> +#include <QBoxLayout> +#include <QDialogButtonBox> +#include <QPushButton> +#include <QInputDialog> + +#include "gui.h" +#include "document.h" +#include "history.h" +#include "configDialog.h" +#include "addObjectDialog.h" +#include "misc.h" +#include "gldraw.h" +#include "dialogs.h" +#include "primitives.h" +#include "ui_newpart.h" +#include "widgets.h" + +extern_cfg (Bool, gl_wireframe); +extern_cfg (Bool, gl_colorbfc); +extern_cfg (String, ld_defaultname); +extern_cfg (String, ld_defaultuser); +extern_cfg (Int, ld_defaultlicense); + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (New, CTRL_SHIFT (N)) +{ QDialog* dlg = new QDialog (g_win); + Ui::NewPartUI ui; + ui.setupUi (dlg); + + str authortext = ld_defaultname; + + if (!ld_defaultuser.value.isEmpty()) + authortext.append (fmt (" [%1]", ld_defaultuser)); + + ui.le_author->setText (authortext); + + switch (ld_defaultlicense) + { case 0: + ui.rb_license_ca->setChecked (true); + break; + + case 1: + ui.rb_license_nonca->setChecked (true); + break; + + case 2: + ui.rb_license_none->setChecked (true); + break; + + default: + QMessageBox::warning (null, "Warning", + fmt ("Unknown ld_defaultlicense value %1!", ld_defaultlicense)); + break; + } + + if (dlg->exec() == false) + return; + + newFile(); + + const LDBFC::Type BFCType = + ui.rb_bfc_ccw->isChecked() ? LDBFC::CertifyCCW : + ui.rb_bfc_cw->isChecked() ? LDBFC::CertifyCW : LDBFC::NoCertify; + + const str license = + ui.rb_license_ca->isChecked() ? CALicense : + ui.rb_license_nonca->isChecked() ? NonCALicense : ""; + + getCurrentDocument()->addObjects ( + { new LDComment (ui.le_title->text()), + new LDComment ("Name: <untitled>.dat"), + new LDComment (fmt ("Author: %1", ui.le_author->text())), + new LDComment (fmt ("!LDRAW_ORG Unofficial_Part")), + (license != "" ? new LDComment (license) : null), + new LDEmpty, + new LDBFC (BFCType), + new LDEmpty, + }); + + g_win->doFullRefresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (NewFile, CTRL (N)) +{ newFile(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Open, CTRL (O)) +{ str name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)"); + + if (name.length() == 0) + return; + + openMainFile (name); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Save, CTRL (S)) +{ g_win->save (getCurrentDocument(), false); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SaveAs, CTRL_SHIFT (S)) +{ g_win->save (getCurrentDocument(), true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SaveAll, CTRL (L)) +{ for (LDDocument* file : g_loadedFiles) + { if (file->isImplicit()) + continue; + + g_win->save (file, false); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Close, CTRL (W)) +{ if (!getCurrentDocument()->isSafeToClose()) + return; + + delete getCurrentDocument(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (CloseAll, 0) +{ if (!safeToCloseAll()) + return; + + closeAll(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Settings, 0) +{ (new ConfigDialog)->exec(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SetLDrawPath, 0) +{ (new LDrawPathDialog (true))->exec(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Exit, CTRL (Q)) +{ exit (0); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (NewSubfile, 0) +{ AddObjectDialog::staticDialog (LDObject::Subfile, null); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (NewLine, 0) +{ AddObjectDialog::staticDialog (LDObject::Line, null); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (NewTriangle, 0) +{ AddObjectDialog::staticDialog (LDObject::Triangle, null); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (NewQuad, 0) +{ AddObjectDialog::staticDialog (LDObject::Quad, null); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (NewCLine, 0) +{ AddObjectDialog::staticDialog (LDObject::CondLine, null); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (NewComment, 0) +{ AddObjectDialog::staticDialog (LDObject::Comment, null); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (NewBFC, 0) +{ AddObjectDialog::staticDialog (LDObject::BFC, null); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (NewVertex, 0) +{ AddObjectDialog::staticDialog (LDObject::Vertex, null); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Edit, 0) +{ if (selection().size() != 1) + return; + + LDObject* obj = selection() [0]; + AddObjectDialog::staticDialog (obj->getType(), obj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Help, KEY (F1)) +{ +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (About, 0) +{ AboutDialog().exec(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (AboutQt, 0) +{ QMessageBox::aboutQt (g_win); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SelectAll, CTRL (A)) +{ for (LDObject* obj : getCurrentDocument()->getObjects()) + obj->select(); + + g_win->updateSelection(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SelectByColor, CTRL_SHIFT (A)) +{ int colnum = g_win->getSelectedColor(); + + if (colnum == -1) + return; // no consensus on color + + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : getCurrentDocument()->getObjects()) + if (obj->getColor() == colnum) + obj->select(); + + g_win->updateSelection(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SelectByType, 0) +{ if (selection().isEmpty()) + return; + + LDObject::Type type = g_win->getUniformSelectedType(); + + if (type == LDObject::Unidentified) + return; + + // If we're selecting subfile references, the reference filename must also + // be uniform. + str refName; + + if (type == LDObject::Subfile) + { refName = static_cast<LDSubfile*> (selection()[0])->getFileInfo()->getName(); + + for (LDObject* obj : selection()) + if (static_cast<LDSubfile*> (obj)->getFileInfo()->getName() != refName) + return; + } + + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : getCurrentDocument()->getObjects()) + { if (obj->getType() != type) + continue; + + if (type == LDObject::Subfile && static_cast<LDSubfile*> (obj)->getFileInfo()->getName() != refName) + continue; + + obj->select(); + } + + g_win->updateSelection(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (GridCoarse, 0) +{ grid = Grid::Coarse; + g_win->updateGridToolBar(); +} + +DEFINE_ACTION (GridMedium, 0) +{ grid = Grid::Medium; + g_win->updateGridToolBar(); +} + +DEFINE_ACTION (GridFine, 0) +{ grid = Grid::Fine; + g_win->updateGridToolBar(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (ResetView, CTRL (0)) +{ g_win->R()->resetAngles(); + g_win->R()->update(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (InsertFrom, 0) +{ str fname = QFileDialog::getOpenFileName(); + int idx = g_win->getInsertionPoint(); + + if (!fname.length()) + return; + + File f (fname, File::Read); + + if (!f) + { critical (fmt ("Couldn't open %1 (%2)", fname, strerror (errno))); + return; + } + + QList<LDObject*> objs = loadFileContents (&f, null); + + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : objs) + { getCurrentDocument()->insertObj (idx, obj); + obj->select(); + g_win->R()->compileObject (obj); + + idx++; + } + + g_win->refresh(); + g_win->scrollToSelection(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (ExportTo, 0) +{ if (selection().isEmpty()) + return; + + str fname = QFileDialog::getSaveFileName(); + + if (fname.length() == 0) + return; + + QFile file (fname); + + if (!file.open (QIODevice::WriteOnly | QIODevice::Text)) + { critical (fmt ("Unable to open %1 for writing (%2)", fname, strerror (errno))); + return; + } + + for (LDObject* obj : selection()) + { str contents = obj->raw(); + QByteArray data = contents.toUtf8(); + file.write (data, data.size()); + file.write ("\r\n", 2); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (InsertRaw, 0) +{ int idx = g_win->getInsertionPoint(); + + QDialog* const dlg = new QDialog; + QVBoxLayout* const layout = new QVBoxLayout; + QTextEdit* const te_edit = new QTextEdit; + QDialogButtonBox* const bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + layout->addWidget (te_edit); + layout->addWidget (bbx_buttons); + dlg->setLayout (layout); + dlg->setWindowTitle (APPNAME ": Insert Raw"); + dlg->connect (bbx_buttons, SIGNAL (accepted()), dlg, SLOT (accept())); + dlg->connect (bbx_buttons, SIGNAL (rejected()), dlg, SLOT (reject())); + + if (dlg->exec() == false) + return; + + getCurrentDocument()->clearSelection(); + + for (str line : str (te_edit->toPlainText()).split ("\n")) + { LDObject* obj = parseLine (line); + + getCurrentDocument()->insertObj (idx, obj); + obj->select(); + g_win->R()->compileObject (obj); + idx++; + } + + g_win->refresh(); + g_win->scrollToSelection(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Screenshot, 0) +{ setlocale (LC_ALL, "C"); + + int w, h; + uchar* imgdata = g_win->R()->getScreencap (w, h); + QImage img = imageFromScreencap (imgdata, w, h); + + str root = basename (getCurrentDocument()->getName()); + + if (root.right (4) == ".dat") + root.chop (4); + + str defaultname = (root.length() > 0) ? fmt ("%1.png", root) : ""; + str fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname, + "PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)"); + + if (fname.length() > 0 && !img.save (fname)) + critical (fmt ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno))); + + delete[] imgdata; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +extern_cfg (Bool, gl_axes); +DEFINE_ACTION (Axes, 0) +{ gl_axes = !gl_axes; + g_win->updateActions(); + g_win->R()->update(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (VisibilityToggle, 0) +{ for (LDObject* obj : selection()) + obj->toggleHidden(); + + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (VisibilityHide, 0) +{ for (LDObject* obj : selection()) + obj->setHidden (true); + + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (VisibilityReveal, 0) +{ for (LDObject* obj : selection()) + obj->setHidden (false); + + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Wireframe, 0) +{ gl_wireframe = !gl_wireframe; + g_win->R()->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SetOverlay, 0) +{ OverlayDialog dlg; + + if (!dlg.exec()) + return; + + g_win->R()->setupOverlay ((GL::EFixedCamera) dlg.camera(), dlg.fpath(), dlg.ofsx(), + dlg.ofsy(), dlg.lwidth(), dlg.lheight()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (ClearOverlay, 0) +{ g_win->R()->clearOverlay(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (ModeSelect, CTRL (1)) +{ g_win->R()->setEditMode (ESelectMode); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (ModeDraw, CTRL (2)) +{ g_win->R()->setEditMode (EDrawMode); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (ModeCircle, CTRL (3)) +{ g_win->R()->setEditMode (ECircleMode); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SetDrawDepth, 0) +{ if (g_win->R()->camera() == GL::EFreeCamera) + return; + + bool ok; + double depth = QInputDialog::getDouble (g_win, "Set Draw Depth", + fmt ("Depth value for %1 Camera:", g_win->R()->getCameraName()), + g_win->R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok); + + if (ok) + g_win->R()->setDepthValue (depth); +} + +#if 0 +// This is a test to draw a dummy axle. Meant to be used as a primitive gallery, +// but I can't figure how to generate these pictures properly. Multi-threading +// these is an immense pain. +DEFINE_ACTION (testpic, "Test picture", "", "", (0)) +{ LDDocument* file = getFile ("axle.dat"); + setlocale (LC_ALL, "C"); + + if (!file) + { critical ("couldn't load axle.dat"); + return; + } + + int w, h; + + GLRenderer* rend = new GLRenderer; + rend->resize (64, 64); + rend->setAttribute (Qt::WA_DontShowOnScreen); + rend->show(); + rend->setFile (file); + rend->setDrawOnly (true); + rend->compileAllObjects(); + rend->initGLData(); + rend->drawGLScene(); + + uchar* imgdata = rend->screencap (w, h); + QImage img = imageFromScreencap (imgdata, w, h); + + if (img.isNull()) + { critical ("Failed to create the image!\n"); + } + else + { QLabel* label = new QLabel; + QDialog* dlg = new QDialog; + label->setPixmap (QPixmap::fromImage (img)); + QVBoxLayout* layout = new QVBoxLayout (dlg); + layout->addWidget (label); + dlg->exec(); + } + + delete[] imgdata; + rend->deleteLater(); +} +#endif + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (ScanPrimitives, 0) +{ PrimitiveLister::start(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (BFCView, SHIFT (B)) +{ gl_colorbfc = !gl_colorbfc; + g_win->updateActions(); + g_win->R()->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (JumpTo, CTRL (G)) +{ bool ok; + int defval = 0; + LDObject* obj; + + if (selection().size() == 1) + defval = selection()[0]->getIndex(); + + int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval, + 1, getCurrentDocument()->getObjectCount(), 1, &ok); + + if (!ok || (obj = getCurrentDocument()->getObject (idx - 1)) == null) + return; + + getCurrentDocument()->clearSelection(); + obj->select(); + g_win->updateSelection(); +}
--- a/src/gui_actions.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,634 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QFileDialog> -#include <QMessageBox> -#include <QTextEdit> -#include <QBoxLayout> -#include <QDialogButtonBox> -#include <QPushButton> -#include <QInputDialog> - -#include "gui.h" -#include "document.h" -#include "history.h" -#include "configDialog.h" -#include "addObjectDialog.h" -#include "misc.h" -#include "gldraw.h" -#include "dialogs.h" -#include "primitives.h" -#include "ui_newpart.h" -#include "widgets.h" - -extern_cfg (Bool, gl_wireframe); -extern_cfg (Bool, gl_colorbfc); -extern_cfg (String, ld_defaultname); -extern_cfg (String, ld_defaultuser); -extern_cfg (Int, ld_defaultlicense); - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (New, CTRL_SHIFT (N)) -{ QDialog* dlg = new QDialog (g_win); - Ui::NewPartUI ui; - ui.setupUi (dlg); - - str authortext = ld_defaultname; - - if (!ld_defaultuser.value.isEmpty()) - authortext.append (fmt (" [%1]", ld_defaultuser)); - - ui.le_author->setText (authortext); - - switch (ld_defaultlicense) - { case 0: - ui.rb_license_ca->setChecked (true); - break; - - case 1: - ui.rb_license_nonca->setChecked (true); - break; - - case 2: - ui.rb_license_none->setChecked (true); - break; - - default: - QMessageBox::warning (null, "Warning", - fmt ("Unknown ld_defaultlicense value %1!", ld_defaultlicense)); - break; - } - - if (dlg->exec() == false) - return; - - newFile(); - - const LDBFC::Type BFCType = - ui.rb_bfc_ccw->isChecked() ? LDBFC::CertifyCCW : - ui.rb_bfc_cw->isChecked() ? LDBFC::CertifyCW : LDBFC::NoCertify; - - const str license = - ui.rb_license_ca->isChecked() ? CALicense : - ui.rb_license_nonca->isChecked() ? NonCALicense : ""; - - getCurrentDocument()->addObjects ( - { new LDComment (ui.le_title->text()), - new LDComment ("Name: <untitled>.dat"), - new LDComment (fmt ("Author: %1", ui.le_author->text())), - new LDComment (fmt ("!LDRAW_ORG Unofficial_Part")), - (license != "" ? new LDComment (license) : null), - new LDEmpty, - new LDBFC (BFCType), - new LDEmpty, - }); - - g_win->doFullRefresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewFile, CTRL (N)) -{ newFile(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Open, CTRL (O)) -{ str name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)"); - - if (name.length() == 0) - return; - - openMainFile (name); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Save, CTRL (S)) -{ g_win->save (getCurrentDocument(), false); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SaveAs, CTRL_SHIFT (S)) -{ g_win->save (getCurrentDocument(), true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SaveAll, CTRL (L)) -{ for (LDDocument* file : g_loadedFiles) - { if (file->isImplicit()) - continue; - - g_win->save (file, false); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Close, CTRL (W)) -{ if (!getCurrentDocument()->isSafeToClose()) - return; - - delete getCurrentDocument(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (CloseAll, 0) -{ if (!safeToCloseAll()) - return; - - closeAll(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Settings, 0) -{ (new ConfigDialog)->exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SetLDrawPath, 0) -{ (new LDrawPathDialog (true))->exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Exit, CTRL (Q)) -{ exit (0); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewSubfile, 0) -{ AddObjectDialog::staticDialog (LDObject::Subfile, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewLine, 0) -{ AddObjectDialog::staticDialog (LDObject::Line, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewTriangle, 0) -{ AddObjectDialog::staticDialog (LDObject::Triangle, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewQuad, 0) -{ AddObjectDialog::staticDialog (LDObject::Quad, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewCLine, 0) -{ AddObjectDialog::staticDialog (LDObject::CondLine, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewComment, 0) -{ AddObjectDialog::staticDialog (LDObject::Comment, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewBFC, 0) -{ AddObjectDialog::staticDialog (LDObject::BFC, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewVertex, 0) -{ AddObjectDialog::staticDialog (LDObject::Vertex, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Edit, 0) -{ if (selection().size() != 1) - return; - - LDObject* obj = selection() [0]; - AddObjectDialog::staticDialog (obj->getType(), obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Help, KEY (F1)) -{ -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (About, 0) -{ AboutDialog().exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (AboutQt, 0) -{ QMessageBox::aboutQt (g_win); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SelectAll, CTRL (A)) -{ for (LDObject* obj : getCurrentDocument()->getObjects()) - obj->select(); - - g_win->updateSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SelectByColor, CTRL_SHIFT (A)) -{ int colnum = g_win->getSelectedColor(); - - if (colnum == -1) - return; // no consensus on color - - getCurrentDocument()->clearSelection(); - - for (LDObject* obj : getCurrentDocument()->getObjects()) - if (obj->getColor() == colnum) - obj->select(); - - g_win->updateSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SelectByType, 0) -{ if (selection().isEmpty()) - return; - - LDObject::Type type = g_win->getUniformSelectedType(); - - if (type == LDObject::Unidentified) - return; - - // If we're selecting subfile references, the reference filename must also - // be uniform. - str refName; - - if (type == LDObject::Subfile) - { refName = static_cast<LDSubfile*> (selection()[0])->getFileInfo()->getName(); - - for (LDObject* obj : selection()) - if (static_cast<LDSubfile*> (obj)->getFileInfo()->getName() != refName) - return; - } - - getCurrentDocument()->clearSelection(); - - for (LDObject* obj : getCurrentDocument()->getObjects()) - { if (obj->getType() != type) - continue; - - if (type == LDObject::Subfile && static_cast<LDSubfile*> (obj)->getFileInfo()->getName() != refName) - continue; - - obj->select(); - } - - g_win->updateSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (GridCoarse, 0) -{ grid = Grid::Coarse; - g_win->updateGridToolBar(); -} - -DEFINE_ACTION (GridMedium, 0) -{ grid = Grid::Medium; - g_win->updateGridToolBar(); -} - -DEFINE_ACTION (GridFine, 0) -{ grid = Grid::Fine; - g_win->updateGridToolBar(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ResetView, CTRL (0)) -{ g_win->R()->resetAngles(); - g_win->R()->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (InsertFrom, 0) -{ str fname = QFileDialog::getOpenFileName(); - int idx = g_win->getInsertionPoint(); - - if (!fname.length()) - return; - - File f (fname, File::Read); - - if (!f) - { critical (fmt ("Couldn't open %1 (%2)", fname, strerror (errno))); - return; - } - - QList<LDObject*> objs = loadFileContents (&f, null); - - getCurrentDocument()->clearSelection(); - - for (LDObject* obj : objs) - { getCurrentDocument()->insertObj (idx, obj); - obj->select(); - g_win->R()->compileObject (obj); - - idx++; - } - - g_win->refresh(); - g_win->scrollToSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ExportTo, 0) -{ if (selection().isEmpty()) - return; - - str fname = QFileDialog::getSaveFileName(); - - if (fname.length() == 0) - return; - - QFile file (fname); - - if (!file.open (QIODevice::WriteOnly | QIODevice::Text)) - { critical (fmt ("Unable to open %1 for writing (%2)", fname, strerror (errno))); - return; - } - - for (LDObject* obj : selection()) - { str contents = obj->raw(); - QByteArray data = contents.toUtf8(); - file.write (data, data.size()); - file.write ("\r\n", 2); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (InsertRaw, 0) -{ int idx = g_win->getInsertionPoint(); - - QDialog* const dlg = new QDialog; - QVBoxLayout* const layout = new QVBoxLayout; - QTextEdit* const te_edit = new QTextEdit; - QDialogButtonBox* const bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - - layout->addWidget (te_edit); - layout->addWidget (bbx_buttons); - dlg->setLayout (layout); - dlg->setWindowTitle (APPNAME ": Insert Raw"); - dlg->connect (bbx_buttons, SIGNAL (accepted()), dlg, SLOT (accept())); - dlg->connect (bbx_buttons, SIGNAL (rejected()), dlg, SLOT (reject())); - - if (dlg->exec() == false) - return; - - getCurrentDocument()->clearSelection(); - - for (str line : str (te_edit->toPlainText()).split ("\n")) - { LDObject* obj = parseLine (line); - - getCurrentDocument()->insertObj (idx, obj); - obj->select(); - g_win->R()->compileObject (obj); - idx++; - } - - g_win->refresh(); - g_win->scrollToSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Screenshot, 0) -{ setlocale (LC_ALL, "C"); - - int w, h; - uchar* imgdata = g_win->R()->getScreencap (w, h); - QImage img = imageFromScreencap (imgdata, w, h); - - str root = basename (getCurrentDocument()->getName()); - - if (root.right (4) == ".dat") - root.chop (4); - - str defaultname = (root.length() > 0) ? fmt ("%1.png", root) : ""; - str fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname, - "PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)"); - - if (fname.length() > 0 && !img.save (fname)) - critical (fmt ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno))); - - delete[] imgdata; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -extern_cfg (Bool, gl_axes); -DEFINE_ACTION (Axes, 0) -{ gl_axes = !gl_axes; - g_win->updateActions(); - g_win->R()->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (VisibilityToggle, 0) -{ for (LDObject* obj : selection()) - obj->toggleHidden(); - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (VisibilityHide, 0) -{ for (LDObject* obj : selection()) - obj->setHidden (true); - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (VisibilityReveal, 0) -{ for (LDObject* obj : selection()) - obj->setHidden (false); - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Wireframe, 0) -{ gl_wireframe = !gl_wireframe; - g_win->R()->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SetOverlay, 0) -{ OverlayDialog dlg; - - if (!dlg.exec()) - return; - - g_win->R()->setupOverlay ((GL::EFixedCamera) dlg.camera(), dlg.fpath(), dlg.ofsx(), - dlg.ofsy(), dlg.lwidth(), dlg.lheight()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ClearOverlay, 0) -{ g_win->R()->clearOverlay(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ModeSelect, CTRL (1)) -{ g_win->R()->setEditMode (ESelectMode); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ModeDraw, CTRL (2)) -{ g_win->R()->setEditMode (EDrawMode); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ModeCircle, CTRL (3)) -{ g_win->R()->setEditMode (ECircleMode); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SetDrawDepth, 0) -{ if (g_win->R()->camera() == GL::EFreeCamera) - return; - - bool ok; - double depth = QInputDialog::getDouble (g_win, "Set Draw Depth", - fmt ("Depth value for %1 Camera:", g_win->R()->getCameraName()), - g_win->R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok); - - if (ok) - g_win->R()->setDepthValue (depth); -} - -#if 0 -// This is a test to draw a dummy axle. Meant to be used as a primitive gallery, -// but I can't figure how to generate these pictures properly. Multi-threading -// these is an immense pain. -DEFINE_ACTION (testpic, "Test picture", "", "", (0)) -{ LDDocument* file = getFile ("axle.dat"); - setlocale (LC_ALL, "C"); - - if (!file) - { critical ("couldn't load axle.dat"); - return; - } - - int w, h; - - GLRenderer* rend = new GLRenderer; - rend->resize (64, 64); - rend->setAttribute (Qt::WA_DontShowOnScreen); - rend->show(); - rend->setFile (file); - rend->setDrawOnly (true); - rend->compileAllObjects(); - rend->initGLData(); - rend->drawGLScene(); - - uchar* imgdata = rend->screencap (w, h); - QImage img = imageFromScreencap (imgdata, w, h); - - if (img.isNull()) - { critical ("Failed to create the image!\n"); - } - else - { QLabel* label = new QLabel; - QDialog* dlg = new QDialog; - label->setPixmap (QPixmap::fromImage (img)); - QVBoxLayout* layout = new QVBoxLayout (dlg); - layout->addWidget (label); - dlg->exec(); - } - - delete[] imgdata; - rend->deleteLater(); -} -#endif - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ScanPrimitives, 0) -{ PrimitiveLister::start(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (BFCView, SHIFT (B)) -{ gl_colorbfc = !gl_colorbfc; - g_win->updateActions(); - g_win->R()->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (JumpTo, CTRL (G)) -{ bool ok; - int defval = 0; - LDObject* obj; - - if (selection().size() == 1) - defval = selection()[0]->getIndex(); - - int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval, - 1, getCurrentDocument()->getObjectCount(), 1, &ok); - - if (!ok || (obj = getCurrentDocument()->getObject (idx - 1)) == null) - return; - - getCurrentDocument()->clearSelection(); - obj->select(); - g_win->updateSelection(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui_editactions.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,757 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QSpinBox> +#include <QCheckBox> +#include <QBoxLayout> +#include <QClipboard> + +#include "gui.h" +#include "main.h" +#include "document.h" +#include "colorSelectDialog.h" +#include "misc.h" +#include "widgets.h" +#include "gldraw.h" +#include "dialogs.h" +#include "colors.h" +#include "ui_replcoords.h" +#include "ui_editraw.h" +#include "ui_flip.h" +#include "ui_addhistoryline.h" + +cfg (Bool, edit_schemanticinline, false); +extern_cfg (String, ld_defaultuser); + +// ============================================================================= +// ----------------------------------------------------------------------------- +static int copyToClipboard() +{ QList<LDObject*> objs = selection(); + int num = 0; + + // Clear the clipboard first. + qApp->clipboard()->clear(); + + // Now, copy the contents into the clipboard. + str data; + + for (LDObject* obj : objs) + { if (data.length() > 0) + data += "\n"; + + data += obj->raw(); + ++num; + } + + qApp->clipboard()->setText (data); + return num; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Cut, CTRL (X)) +{ int num = copyToClipboard(); + g_win->deleteSelection(); + log (ForgeWindow::tr ("%1 objects cut"), num); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Copy, CTRL (C)) +{ int num = copyToClipboard(); + log (ForgeWindow::tr ("%1 objects copied"), num); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Paste, CTRL (V)) +{ const str clipboardText = qApp->clipboard()->text(); + int idx = g_win->getInsertionPoint(); + getCurrentDocument()->clearSelection(); + int num = 0; + + for (str line : clipboardText.split ("\n")) + { LDObject* pasted = parseLine (line); + getCurrentDocument()->insertObj (idx++, pasted); + pasted->select(); + g_win->R()->compileObject (pasted); + ++num; + } + + log (ForgeWindow::tr ("%1 objects pasted"), num); + g_win->refresh(); + g_win->scrollToSelection(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Delete, KEY (Delete)) +{ int num = g_win->deleteSelection(); + log (ForgeWindow::tr ("%1 objects deleted"), num); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void doInline (bool deep) +{ QList<LDObject*> sel = selection(); + + for (LDObject* obj : sel) + { // Get the index of the subfile so we know where to insert the + // inlined contents. + long idx = obj->getIndex(); + + if (idx == -1) + continue; + + QList<LDObject*> objs; + + if (obj->getType() == LDObject::Subfile) + objs = static_cast<LDSubfile*> (obj)->inlineContents ( + (LDSubfile::InlineFlags) + ( (deep) ? LDSubfile::DeepInline : 0) | + LDSubfile::CacheInline + ); + else + continue; + + // Merge in the inlined objects + for (LDObject * inlineobj : objs) + { str line = inlineobj->raw(); + delete inlineobj; + + LDObject* newobj = parseLine (line); + getCurrentDocument()->insertObj (idx++, newobj); + newobj->select(); + g_win->R()->compileObject (newobj); + } + + // Delete the subfile now as it's been inlined. + getCurrentDocument()->forgetObject (obj); + delete obj; + } + + g_win->refresh(); +} + +DEFINE_ACTION (Inline, CTRL (I)) +{ doInline (false); +} + +DEFINE_ACTION (InlineDeep, CTRL_SHIFT (I)) +{ doInline (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SplitQuads, 0) +{ QList<LDObject*> objs = selection(); + int num = 0; + + for (LDObject* obj : objs) + { if (obj->getType() != LDObject::Quad) + continue; + + // Find the index of this quad + long index = obj->getIndex(); + + if (index == -1) + return; + + QList<LDTriangle*> triangles = static_cast<LDQuad*> (obj)->splitToTriangles(); + + // Replace the quad with the first triangle and add the second triangle + // after the first one. + getCurrentDocument()->setObject (index, triangles[0]); + getCurrentDocument()->insertObj (index + 1, triangles[1]); + + for (LDTriangle* t : triangles) + g_win->R()->compileObject (t); + + // Delete this quad now, it has been split. + delete obj; + + num++; + } + + log ("%1 quadrilaterals split", num); + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (EditRaw, KEY (F9)) +{ if (selection().size() != 1) + return; + + LDObject* obj = selection()[0]; + QDialog* dlg = new QDialog; + Ui::EditRawUI ui; + + ui.setupUi (dlg); + ui.code->setText (obj->raw()); + + if (obj->getType() == LDObject::Error) + ui.errorDescription->setText (static_cast<LDError*> (obj)->reason); + else + { ui.errorDescription->hide(); + ui.errorIcon->hide(); + } + + if (!dlg->exec()) + return; + + LDObject* oldobj = obj; + + // Reinterpret it from the text of the input field + obj = parseLine (ui.code->text()); + oldobj->replace (obj); + + // Refresh + g_win->R()->compileObject (obj); + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SetColor, KEY (C)) +{ if (selection().isEmpty()) + return; + + int colnum; + int defcol = -1; + + QList<LDObject*> objs = selection(); + + // If all selected objects have the same color, said color is our default + // value to the color selection dialog. + defcol = g_win->getSelectedColor(); + + // Show the dialog to the user now and ask for a color. + if (ColorSelector::selectColor (colnum, defcol, g_win)) + { for (LDObject* obj : objs) + { if (obj->isColored() == false) + continue; + + obj->setColor (colnum); + g_win->R()->compileObject (obj); + } + + g_win->refresh(); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Borders, CTRL_SHIFT (B)) +{ QList<LDObject*> objs = selection(); + int num = 0; + + for (LDObject* obj : objs) + { const LDObject::Type type = obj->getType(); + if (type != LDObject::Quad && type != LDObject::Triangle) + continue; + + int numLines; + LDLine* lines[4]; + + if (type == LDObject::Quad) + { numLines = 4; + + LDQuad* quad = static_cast<LDQuad*> (obj); + lines[0] = new LDLine (quad->getVertex (0), quad->getVertex (1)); + lines[1] = new LDLine (quad->getVertex (1), quad->getVertex (2)); + lines[2] = new LDLine (quad->getVertex (2), quad->getVertex (3)); + lines[3] = new LDLine (quad->getVertex (3), quad->getVertex (0)); + } + else + { numLines = 3; + + LDTriangle* tri = static_cast<LDTriangle*> (obj); + lines[0] = new LDLine (tri->getVertex (0), tri->getVertex (1)); + lines[1] = new LDLine (tri->getVertex (1), tri->getVertex (2)); + lines[2] = new LDLine (tri->getVertex (2), tri->getVertex (0)); + } + + for (int i = 0; i < numLines; ++i) + { long idx = obj->getIndex() + i + 1; + + lines[i]->setColor (edgecolor); + getCurrentDocument()->insertObj (idx, lines[i]); + g_win->R()->compileObject (lines[i]); + } + + num += numLines; + } + + log (ForgeWindow::tr ("Added %1 border lines"), num); + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (CornerVerts, 0) +{ int num = 0; + + for (LDObject* obj : selection()) + { if (obj->vertices() < 2) + continue; + + int idx = obj->getIndex(); + + for (int i = 0; i < obj->vertices(); ++i) + { LDVertex* vert = new LDVertex; + vert->pos = obj->getVertex (i); + vert->setColor (obj->getColor()); + + getCurrentDocument()->insertObj (++idx, vert); + g_win->R()->compileObject (vert); + ++num; + } + } + + log (ForgeWindow::tr ("Added %1 vertices"), num); + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void doMoveSelection (const bool up) +{ QList<LDObject*> objs = selection(); + LDObject::moveObjects (objs, up); + g_win->buildObjList(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (MoveUp, KEY (PageUp)) +{ doMoveSelection (true); +} + +DEFINE_ACTION (MoveDown, KEY (PageDown)) +{ doMoveSelection (false); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Undo, CTRL (Z)) +{ getCurrentDocument()->undo(); +} + +DEFINE_ACTION (Redo, CTRL_SHIFT (Z)) +{ getCurrentDocument()->redo(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void doMoveObjects (vertex vect) +{ // Apply the grid values + vect[X] *= currentGrid().confs[Grid::X]->value; + vect[Y] *= currentGrid().confs[Grid::Y]->value; + vect[Z] *= currentGrid().confs[Grid::Z]->value; + + for (LDObject* obj : selection()) + { obj->move (vect); + g_win->R()->compileObject (obj); + } + + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (MoveXNeg, KEY (Left)) +{ doMoveObjects ({ -1, 0, 0}); +} + +DEFINE_ACTION (MoveYNeg, KEY (Home)) +{ doMoveObjects ({0, -1, 0}); +} + +DEFINE_ACTION (MoveZNeg, KEY (Down)) +{ doMoveObjects ({0, 0, -1}); +} + +DEFINE_ACTION (MoveXPos, KEY (Right)) +{ doMoveObjects ({1, 0, 0}); +} + +DEFINE_ACTION (MoveYPos, KEY (End)) +{ doMoveObjects ({0, 1, 0}); +} + +DEFINE_ACTION (MoveZPos, KEY (Up)) +{ doMoveObjects ({0, 0, 1}); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Invert, CTRL_SHIFT (W)) +{ QList<LDObject*> sel = selection(); + + for (LDObject* obj : sel) + { obj->invert(); + g_win->R()->compileObject (obj); + } + + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void rotateVertex (vertex& v, const vertex& rotpoint, const matrix& transform) +{ v.move (-rotpoint); + v.transform (transform, g_origin); + v.move (rotpoint); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void doRotate (const int l, const int m, const int n) +{ QList<LDObject*> sel = selection(); + QList<vertex*> queue; + const vertex rotpoint = rotPoint (sel); + const double angle = (pi * currentGrid().confs[Grid::Angle]->value) / 180, + cosangle = cos (angle), + sinangle = sin (angle); + + // ref: http://en.wikipedia.org/wiki/Transformation_matrix#Rotation_2 + matrix transform ( + { (l* l * (1 - cosangle)) + cosangle, + (m* l * (1 - cosangle)) - (n* sinangle), + (n* l * (1 - cosangle)) + (m* sinangle), + + (l* m * (1 - cosangle)) + (n* sinangle), + (m* m * (1 - cosangle)) + cosangle, + (n* m * (1 - cosangle)) - (l* sinangle), + + (l* n * (1 - cosangle)) - (m* sinangle), + (m* n * (1 - cosangle)) + (l* sinangle), + (n* n * (1 - cosangle)) + cosangle + }); + + // Apply the above matrix to everything + for (LDObject* obj : sel) + { if (obj->vertices()) + { for (int i = 0; i < obj->vertices(); ++i) + { vertex v = obj->getVertex (i); + rotateVertex (v, rotpoint, transform); + obj->setVertex (i, v); + } + } elif (obj->hasMatrix()) + { LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj); + + // Transform the position + vertex v = mo->getPosition(); + rotateVertex (v, rotpoint, transform); + mo->setPosition (v); + + // Transform the matrix + mo->setTransform (mo->getTransform() * transform); + } elif (obj->getType() == LDObject::Vertex) + { LDVertex* vert = static_cast<LDVertex*> (obj); + vertex v = vert->pos; + rotateVertex (v, rotpoint, transform); + vert->pos = v; + } + + g_win->R()->compileObject (obj); + } + + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (RotateXPos, CTRL (Right)) +{ doRotate (1, 0, 0); +} +DEFINE_ACTION (RotateYPos, CTRL (End)) +{ doRotate (0, 1, 0); +} +DEFINE_ACTION (RotateZPos, CTRL (Up)) +{ doRotate (0, 0, 1); +} +DEFINE_ACTION (RotateXNeg, CTRL (Left)) +{ doRotate (-1, 0, 0); +} +DEFINE_ACTION (RotateYNeg, CTRL (Home)) +{ doRotate (0, -1, 0); +} +DEFINE_ACTION (RotateZNeg, CTRL (Down)) +{ doRotate (0, 0, -1); +} + +DEFINE_ACTION (RotationPoint, (0)) +{ configRotationPoint(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (RoundCoordinates, 0) +{ setlocale (LC_ALL, "C"); + int num = 0; + + for (LDObject* obj : selection()) + { LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj); + + if (mo != null) + { vertex v = mo->getPosition(); + matrix t = mo->getTransform(); + + for_axes (ax) + roundToDecimals (v[ax], 3); + + // Let matrix values be rounded to 4 decimals, + // they need that extra precision + for (int i = 0; i < 9; ++i) + roundToDecimals (t[i], 4); + + mo->setPosition (v); + mo->setTransform (t); + num += 10; + } + else + { for (int i = 0; i < obj->vertices(); ++i) + { vertex v = obj->getVertex (i); + + for_axes (ax) + roundToDecimals (v[ax], 3); + + obj->setVertex (i, v); + g_win->R()->compileObject (obj); + num += 3; + } + } + } + + log (ForgeWindow::tr ("Rounded %1 values"), num); + g_win->refreshObjectList(); + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Uncolorize, 0) +{ int num = 0; + + for (LDObject* obj : selection()) + { if (obj->isColored() == false) + continue; + + int col = maincolor; + + if (obj->getType() == LDObject::Line || obj->getType() == LDObject::CondLine) + col = edgecolor; + + obj->setColor (col); + g_win->R()->compileObject (obj); + num++; + } + + log (ForgeWindow::tr ("%1 objects uncolored"), num); + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (ReplaceCoords, CTRL (R)) +{ QDialog* dlg = new QDialog (g_win); + Ui::ReplaceCoordsUI ui; + ui.setupUi (dlg); + + if (!dlg->exec()) + return; + + const double search = ui.search->value(), + replacement = ui.replacement->value(); + const bool any = ui.any->isChecked(), + rel = ui.relative->isChecked(); + + QList<Axis> sel; + int num = 0; + + if (ui.x->isChecked()) sel << X; + if (ui.y->isChecked()) sel << Y; + if (ui.z->isChecked()) sel << Z; + + for (LDObject* obj : selection()) + { for (int i = 0; i < obj->vertices(); ++i) + { vertex v = obj->getVertex (i); + + for (Axis ax : sel) + { double& coord = v[ax]; + + if (any || coord == search) + { if (!rel) + coord = 0; + + coord += replacement; + num++; + } + } + + obj->setVertex (i, v); + g_win->R()->compileObject (obj); + } + } + + log (ForgeWindow::tr ("Altered %1 values"), num); + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Flip, CTRL_SHIFT (F)) +{ QDialog* dlg = new QDialog; + Ui::FlipUI ui; + ui.setupUi (dlg); + + if (!dlg->exec()) + return; + + QList<Axis> sel; + + if (ui.x->isChecked()) sel << X; + if (ui.y->isChecked()) sel << Y; + if (ui.z->isChecked()) sel << Z; + + for (LDObject* obj : selection()) + { for (int i = 0; i < obj->vertices(); ++i) + { vertex v = obj->getVertex (i); + + for (Axis ax : sel) + v[ax] *= -1; + + obj->setVertex (i, v); + g_win->R()->compileObject (obj); + } + } + + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Demote, 0) +{ QList<LDObject*> sel = selection(); + int num = 0; + + for (LDObject* obj : sel) + { if (obj->getType() != LDObject::CondLine) + continue; + + LDLine* repl = static_cast<LDCondLine*> (obj)->demote(); + g_win->R()->compileObject (repl); + ++num; + } + + log (ForgeWindow::tr ("Demoted %1 conditional lines"), num); + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static bool isColorUsed (int colnum) +{ for (LDObject* obj : getCurrentDocument()->getObjects()) + if (obj->isColored() && obj->getColor() == colnum) + return true; + + return false; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Autocolor, 0) +{ int colnum = 0; + + while (colnum < MAX_COLORS && (getColor (colnum) == null || isColorUsed (colnum))) + colnum++; + + if (colnum >= MAX_COLORS) + { log (ForgeWindow::tr ("Cannot auto-color: all colors are in use!")); + return; + } + + for (LDObject* obj : selection()) + { if (obj->isColored() == false) + continue; + + obj->setColor (colnum); + g_win->R()->compileObject (obj); + } + + log (ForgeWindow::tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name); + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (AddHistoryLine, 0) +{ LDObject* obj; + bool ishistory = false, + prevIsHistory = false; + + QDialog* dlg = new QDialog; + Ui_AddHistoryLine* ui = new Ui_AddHistoryLine; + ui->setupUi (dlg); + ui->m_username->setText (ld_defaultuser); + ui->m_date->setDate (QDate::currentDate()); + ui->m_comment->setFocus(); + + if (!dlg->exec()) + return; + + // Create the comment object based on input + str commentText = fmt ("!HISTORY %1 [%2] %3", + ui->m_date->date().toString ("yyyy-MM-dd"), + ui->m_username->text(), + ui->m_comment->text()); + + LDComment* comm = new LDComment (commentText); + + // Find a spot to place the new comment + for ( + obj = getCurrentDocument()->getObject (0); + obj && obj->next() && !obj->next()->isScemantic(); + obj = obj->next() + ) + { LDComment* comm = dynamic_cast<LDComment*> (obj); + + if (comm && comm->text.startsWith ("!HISTORY ")) + ishistory = true; + + if (prevIsHistory && !ishistory) + { // Last line was history, this isn't, thus insert the new history + // line here. + break; + } + + prevIsHistory = ishistory; + } + + int idx = obj ? obj->getIndex() : 0; + getCurrentDocument()->insertObj (idx++, comm); + + // If we're adding a history line right before a scemantic object, pad it + // an empty line + if (obj && obj->next() && obj->next()->isScemantic()) + getCurrentDocument()->insertObj (idx, new LDEmpty); + + g_win->buildObjList(); + delete ui; +}
--- a/src/gui_editactions.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,757 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QSpinBox> -#include <QCheckBox> -#include <QBoxLayout> -#include <QClipboard> - -#include "gui.h" -#include "main.h" -#include "document.h" -#include "colorSelectDialog.h" -#include "misc.h" -#include "widgets.h" -#include "gldraw.h" -#include "dialogs.h" -#include "colors.h" -#include "ui_replcoords.h" -#include "ui_editraw.h" -#include "ui_flip.h" -#include "ui_addhistoryline.h" - -cfg (Bool, edit_schemanticinline, false); -extern_cfg (String, ld_defaultuser); - -// ============================================================================= -// ----------------------------------------------------------------------------- -static int copyToClipboard() -{ QList<LDObject*> objs = selection(); - int num = 0; - - // Clear the clipboard first. - qApp->clipboard()->clear(); - - // Now, copy the contents into the clipboard. - str data; - - for (LDObject* obj : objs) - { if (data.length() > 0) - data += "\n"; - - data += obj->raw(); - ++num; - } - - qApp->clipboard()->setText (data); - return num; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Cut, CTRL (X)) -{ int num = copyToClipboard(); - g_win->deleteSelection(); - log (ForgeWindow::tr ("%1 objects cut"), num); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Copy, CTRL (C)) -{ int num = copyToClipboard(); - log (ForgeWindow::tr ("%1 objects copied"), num); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Paste, CTRL (V)) -{ const str clipboardText = qApp->clipboard()->text(); - int idx = g_win->getInsertionPoint(); - getCurrentDocument()->clearSelection(); - int num = 0; - - for (str line : clipboardText.split ("\n")) - { LDObject* pasted = parseLine (line); - getCurrentDocument()->insertObj (idx++, pasted); - pasted->select(); - g_win->R()->compileObject (pasted); - ++num; - } - - log (ForgeWindow::tr ("%1 objects pasted"), num); - g_win->refresh(); - g_win->scrollToSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Delete, KEY (Delete)) -{ int num = g_win->deleteSelection(); - log (ForgeWindow::tr ("%1 objects deleted"), num); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void doInline (bool deep) -{ QList<LDObject*> sel = selection(); - - for (LDObject* obj : sel) - { // Get the index of the subfile so we know where to insert the - // inlined contents. - long idx = obj->getIndex(); - - if (idx == -1) - continue; - - QList<LDObject*> objs; - - if (obj->getType() == LDObject::Subfile) - objs = static_cast<LDSubfile*> (obj)->inlineContents ( - (LDSubfile::InlineFlags) - ( (deep) ? LDSubfile::DeepInline : 0) | - LDSubfile::CacheInline - ); - else - continue; - - // Merge in the inlined objects - for (LDObject * inlineobj : objs) - { str line = inlineobj->raw(); - delete inlineobj; - - LDObject* newobj = parseLine (line); - getCurrentDocument()->insertObj (idx++, newobj); - newobj->select(); - g_win->R()->compileObject (newobj); - } - - // Delete the subfile now as it's been inlined. - getCurrentDocument()->forgetObject (obj); - delete obj; - } - - g_win->refresh(); -} - -DEFINE_ACTION (Inline, CTRL (I)) -{ doInline (false); -} - -DEFINE_ACTION (InlineDeep, CTRL_SHIFT (I)) -{ doInline (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SplitQuads, 0) -{ QList<LDObject*> objs = selection(); - int num = 0; - - for (LDObject* obj : objs) - { if (obj->getType() != LDObject::Quad) - continue; - - // Find the index of this quad - long index = obj->getIndex(); - - if (index == -1) - return; - - QList<LDTriangle*> triangles = static_cast<LDQuad*> (obj)->splitToTriangles(); - - // Replace the quad with the first triangle and add the second triangle - // after the first one. - getCurrentDocument()->setObject (index, triangles[0]); - getCurrentDocument()->insertObj (index + 1, triangles[1]); - - for (LDTriangle* t : triangles) - g_win->R()->compileObject (t); - - // Delete this quad now, it has been split. - delete obj; - - num++; - } - - log ("%1 quadrilaterals split", num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (EditRaw, KEY (F9)) -{ if (selection().size() != 1) - return; - - LDObject* obj = selection()[0]; - QDialog* dlg = new QDialog; - Ui::EditRawUI ui; - - ui.setupUi (dlg); - ui.code->setText (obj->raw()); - - if (obj->getType() == LDObject::Error) - ui.errorDescription->setText (static_cast<LDError*> (obj)->reason); - else - { ui.errorDescription->hide(); - ui.errorIcon->hide(); - } - - if (!dlg->exec()) - return; - - LDObject* oldobj = obj; - - // Reinterpret it from the text of the input field - obj = parseLine (ui.code->text()); - oldobj->replace (obj); - - // Refresh - g_win->R()->compileObject (obj); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SetColor, KEY (C)) -{ if (selection().isEmpty()) - return; - - int colnum; - int defcol = -1; - - QList<LDObject*> objs = selection(); - - // If all selected objects have the same color, said color is our default - // value to the color selection dialog. - defcol = g_win->getSelectedColor(); - - // Show the dialog to the user now and ask for a color. - if (ColorSelector::selectColor (colnum, defcol, g_win)) - { for (LDObject* obj : objs) - { if (obj->isColored() == false) - continue; - - obj->setColor (colnum); - g_win->R()->compileObject (obj); - } - - g_win->refresh(); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Borders, CTRL_SHIFT (B)) -{ QList<LDObject*> objs = selection(); - int num = 0; - - for (LDObject* obj : objs) - { const LDObject::Type type = obj->getType(); - if (type != LDObject::Quad && type != LDObject::Triangle) - continue; - - int numLines; - LDLine* lines[4]; - - if (type == LDObject::Quad) - { numLines = 4; - - LDQuad* quad = static_cast<LDQuad*> (obj); - lines[0] = new LDLine (quad->getVertex (0), quad->getVertex (1)); - lines[1] = new LDLine (quad->getVertex (1), quad->getVertex (2)); - lines[2] = new LDLine (quad->getVertex (2), quad->getVertex (3)); - lines[3] = new LDLine (quad->getVertex (3), quad->getVertex (0)); - } - else - { numLines = 3; - - LDTriangle* tri = static_cast<LDTriangle*> (obj); - lines[0] = new LDLine (tri->getVertex (0), tri->getVertex (1)); - lines[1] = new LDLine (tri->getVertex (1), tri->getVertex (2)); - lines[2] = new LDLine (tri->getVertex (2), tri->getVertex (0)); - } - - for (int i = 0; i < numLines; ++i) - { long idx = obj->getIndex() + i + 1; - - lines[i]->setColor (edgecolor); - getCurrentDocument()->insertObj (idx, lines[i]); - g_win->R()->compileObject (lines[i]); - } - - num += numLines; - } - - log (ForgeWindow::tr ("Added %1 border lines"), num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (CornerVerts, 0) -{ int num = 0; - - for (LDObject* obj : selection()) - { if (obj->vertices() < 2) - continue; - - int idx = obj->getIndex(); - - for (int i = 0; i < obj->vertices(); ++i) - { LDVertex* vert = new LDVertex; - vert->pos = obj->getVertex (i); - vert->setColor (obj->getColor()); - - getCurrentDocument()->insertObj (++idx, vert); - g_win->R()->compileObject (vert); - ++num; - } - } - - log (ForgeWindow::tr ("Added %1 vertices"), num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void doMoveSelection (const bool up) -{ QList<LDObject*> objs = selection(); - LDObject::moveObjects (objs, up); - g_win->buildObjList(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (MoveUp, KEY (PageUp)) -{ doMoveSelection (true); -} - -DEFINE_ACTION (MoveDown, KEY (PageDown)) -{ doMoveSelection (false); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Undo, CTRL (Z)) -{ getCurrentDocument()->undo(); -} - -DEFINE_ACTION (Redo, CTRL_SHIFT (Z)) -{ getCurrentDocument()->redo(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void doMoveObjects (vertex vect) -{ // Apply the grid values - vect[X] *= currentGrid().confs[Grid::X]->value; - vect[Y] *= currentGrid().confs[Grid::Y]->value; - vect[Z] *= currentGrid().confs[Grid::Z]->value; - - for (LDObject* obj : selection()) - { obj->move (vect); - g_win->R()->compileObject (obj); - } - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (MoveXNeg, KEY (Left)) -{ doMoveObjects ({ -1, 0, 0}); -} - -DEFINE_ACTION (MoveYNeg, KEY (Home)) -{ doMoveObjects ({0, -1, 0}); -} - -DEFINE_ACTION (MoveZNeg, KEY (Down)) -{ doMoveObjects ({0, 0, -1}); -} - -DEFINE_ACTION (MoveXPos, KEY (Right)) -{ doMoveObjects ({1, 0, 0}); -} - -DEFINE_ACTION (MoveYPos, KEY (End)) -{ doMoveObjects ({0, 1, 0}); -} - -DEFINE_ACTION (MoveZPos, KEY (Up)) -{ doMoveObjects ({0, 0, 1}); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Invert, CTRL_SHIFT (W)) -{ QList<LDObject*> sel = selection(); - - for (LDObject* obj : sel) - { obj->invert(); - g_win->R()->compileObject (obj); - } - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void rotateVertex (vertex& v, const vertex& rotpoint, const matrix& transform) -{ v.move (-rotpoint); - v.transform (transform, g_origin); - v.move (rotpoint); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void doRotate (const int l, const int m, const int n) -{ QList<LDObject*> sel = selection(); - QList<vertex*> queue; - const vertex rotpoint = rotPoint (sel); - const double angle = (pi * currentGrid().confs[Grid::Angle]->value) / 180, - cosangle = cos (angle), - sinangle = sin (angle); - - // ref: http://en.wikipedia.org/wiki/Transformation_matrix#Rotation_2 - matrix transform ( - { (l* l * (1 - cosangle)) + cosangle, - (m* l * (1 - cosangle)) - (n* sinangle), - (n* l * (1 - cosangle)) + (m* sinangle), - - (l* m * (1 - cosangle)) + (n* sinangle), - (m* m * (1 - cosangle)) + cosangle, - (n* m * (1 - cosangle)) - (l* sinangle), - - (l* n * (1 - cosangle)) - (m* sinangle), - (m* n * (1 - cosangle)) + (l* sinangle), - (n* n * (1 - cosangle)) + cosangle - }); - - // Apply the above matrix to everything - for (LDObject* obj : sel) - { if (obj->vertices()) - { for (int i = 0; i < obj->vertices(); ++i) - { vertex v = obj->getVertex (i); - rotateVertex (v, rotpoint, transform); - obj->setVertex (i, v); - } - } elif (obj->hasMatrix()) - { LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj); - - // Transform the position - vertex v = mo->getPosition(); - rotateVertex (v, rotpoint, transform); - mo->setPosition (v); - - // Transform the matrix - mo->setTransform (mo->getTransform() * transform); - } elif (obj->getType() == LDObject::Vertex) - { LDVertex* vert = static_cast<LDVertex*> (obj); - vertex v = vert->pos; - rotateVertex (v, rotpoint, transform); - vert->pos = v; - } - - g_win->R()->compileObject (obj); - } - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (RotateXPos, CTRL (Right)) -{ doRotate (1, 0, 0); -} -DEFINE_ACTION (RotateYPos, CTRL (End)) -{ doRotate (0, 1, 0); -} -DEFINE_ACTION (RotateZPos, CTRL (Up)) -{ doRotate (0, 0, 1); -} -DEFINE_ACTION (RotateXNeg, CTRL (Left)) -{ doRotate (-1, 0, 0); -} -DEFINE_ACTION (RotateYNeg, CTRL (Home)) -{ doRotate (0, -1, 0); -} -DEFINE_ACTION (RotateZNeg, CTRL (Down)) -{ doRotate (0, 0, -1); -} - -DEFINE_ACTION (RotationPoint, (0)) -{ configRotationPoint(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (RoundCoordinates, 0) -{ setlocale (LC_ALL, "C"); - int num = 0; - - for (LDObject* obj : selection()) - { LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj); - - if (mo != null) - { vertex v = mo->getPosition(); - matrix t = mo->getTransform(); - - for (const Axis ax : g_Axes) - roundToDecimals (v[ax], 3); - - // Let matrix values be rounded to 4 decimals, - // they need that extra precision - for (int i = 0; i < 9; ++i) - roundToDecimals (t[i], 4); - - mo->setPosition (v); - mo->setTransform (t); - num += 10; - } - else - { for (int i = 0; i < obj->vertices(); ++i) - { vertex v = obj->getVertex (i); - - for (const Axis ax : g_Axes) - roundToDecimals (v[ax], 3); - - obj->setVertex (i, v); - g_win->R()->compileObject (obj); - num += 3; - } - } - } - - log (ForgeWindow::tr ("Rounded %1 values"), num); - g_win->refreshObjectList(); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Uncolorize, 0) -{ int num = 0; - - for (LDObject* obj : selection()) - { if (obj->isColored() == false) - continue; - - int col = maincolor; - - if (obj->getType() == LDObject::Line || obj->getType() == LDObject::CondLine) - col = edgecolor; - - obj->setColor (col); - g_win->R()->compileObject (obj); - num++; - } - - log (ForgeWindow::tr ("%1 objects uncolored"), num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ReplaceCoords, CTRL (R)) -{ QDialog* dlg = new QDialog (g_win); - Ui::ReplaceCoordsUI ui; - ui.setupUi (dlg); - - if (!dlg->exec()) - return; - - const double search = ui.search->value(), - replacement = ui.replacement->value(); - const bool any = ui.any->isChecked(), - rel = ui.relative->isChecked(); - - QList<Axis> sel; - int num = 0; - - if (ui.x->isChecked()) sel << X; - if (ui.y->isChecked()) sel << Y; - if (ui.z->isChecked()) sel << Z; - - for (LDObject* obj : selection()) - { for (int i = 0; i < obj->vertices(); ++i) - { vertex v = obj->getVertex (i); - - for (Axis ax : sel) - { double& coord = v[ax]; - - if (any || coord == search) - { if (!rel) - coord = 0; - - coord += replacement; - num++; - } - } - - obj->setVertex (i, v); - g_win->R()->compileObject (obj); - } - } - - log (ForgeWindow::tr ("Altered %1 values"), num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Flip, CTRL_SHIFT (F)) -{ QDialog* dlg = new QDialog; - Ui::FlipUI ui; - ui.setupUi (dlg); - - if (!dlg->exec()) - return; - - QList<Axis> sel; - - if (ui.x->isChecked()) sel << X; - if (ui.y->isChecked()) sel << Y; - if (ui.z->isChecked()) sel << Z; - - for (LDObject* obj : selection()) - { for (int i = 0; i < obj->vertices(); ++i) - { vertex v = obj->getVertex (i); - - for (Axis ax : sel) - v[ax] *= -1; - - obj->setVertex (i, v); - g_win->R()->compileObject (obj); - } - } - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Demote, 0) -{ QList<LDObject*> sel = selection(); - int num = 0; - - for (LDObject* obj : sel) - { if (obj->getType() != LDObject::CondLine) - continue; - - LDLine* repl = static_cast<LDCondLine*> (obj)->demote(); - g_win->R()->compileObject (repl); - ++num; - } - - log (ForgeWindow::tr ("Demoted %1 conditional lines"), num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static bool isColorUsed (int colnum) -{ for (LDObject* obj : getCurrentDocument()->getObjects()) - if (obj->isColored() && obj->getColor() == colnum) - return true; - - return false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Autocolor, 0) -{ int colnum = 0; - - while (colnum < MAX_COLORS && (getColor (colnum) == null || isColorUsed (colnum))) - colnum++; - - if (colnum >= MAX_COLORS) - { log (ForgeWindow::tr ("Cannot auto-color: all colors are in use!")); - return; - } - - for (LDObject* obj : selection()) - { if (obj->isColored() == false) - continue; - - obj->setColor (colnum); - g_win->R()->compileObject (obj); - } - - log (ForgeWindow::tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (AddHistoryLine, 0) -{ LDObject* obj; - bool ishistory = false, - prevIsHistory = false; - - QDialog* dlg = new QDialog; - Ui_AddHistoryLine* ui = new Ui_AddHistoryLine; - ui->setupUi (dlg); - ui->m_username->setText (ld_defaultuser); - ui->m_date->setDate (QDate::currentDate()); - ui->m_comment->setFocus(); - - if (!dlg->exec()) - return; - - // Create the comment object based on input - str commentText = fmt ("!HISTORY %1 [%2] %3", - ui->m_date->date().toString ("yyyy-MM-dd"), - ui->m_username->text(), - ui->m_comment->text()); - - LDComment* comm = new LDComment (commentText); - - // Find a spot to place the new comment - for ( - obj = getCurrentDocument()->getObject (0); - obj && obj->next() && !obj->next()->isScemantic(); - obj = obj->next() - ) - { LDComment* comm = dynamic_cast<LDComment*> (obj); - - if (comm && comm->text.startsWith ("!HISTORY ")) - ishistory = true; - - if (prevIsHistory && !ishistory) - { // Last line was history, this isn't, thus insert the new history - // line here. - break; - } - - prevIsHistory = ishistory; - } - - int idx = obj ? obj->getIndex() : 0; - getCurrentDocument()->insertObj (idx++, comm); - - // If we're adding a history line right before a scemantic object, pad it - // an empty line - if (obj && obj->next() && obj->next()->isScemantic()) - getCurrentDocument()->insertObj (idx, new LDEmpty); - - g_win->buildObjList(); - delete ui; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/history.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,183 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 "history.h" +#include "ldtypes.h" +#include "document.h" +#include "misc.h" +#include "gui.h" +#include "gldraw.h" + +bool g_fullRefresh = false; + +// ============================================================================= +// ----------------------------------------------------------------------------- +History::History() : + m_Position (-1) {} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void History::undo() +{ if (m_changesets.isEmpty() || getPosition() == -1) + return; + + const Changeset& set = getChangeset (getPosition()); + g_fullRefresh = false; + + // Iterate the list in reverse and undo all actions + for (auto it = set.end() - 1; it != set.begin(); --it) + (*it)->undo(); + + decreasePosition(); + + if (!g_fullRefresh) + g_win->refresh(); + else + g_win->doFullRefresh(); + + g_win->updateActions(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void History::redo() +{ if (getPosition() == (long) m_changesets.size()) + return; + + const Changeset& set = getChangeset (getPosition() + 1); + g_fullRefresh = false; + + // Redo things - in the order as they were done in the first place + for (const AbstractHistoryEntry* change : set) + change->redo(); + + setPosition (getPosition() + 1); + + if (!g_fullRefresh) + g_win->refresh(); + else + g_win->doFullRefresh(); + + g_win->updateActions(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void History::clear() +{ for (Changeset set : m_changesets) + for (AbstractHistoryEntry* change : set) + delete change; + + m_changesets.clear(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void History::addStep() +{ if (m_currentChangeset.isEmpty()) + return; + + while (getPosition() < getSize() - 1) + m_changesets.removeLast(); + + m_changesets << m_currentChangeset; + m_currentChangeset.clear(); + setPosition (getPosition() + 1); + g_win->updateActions(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void History::add (AbstractHistoryEntry* entry) +{ if (isIgnoring()) + { delete entry; + return; + } + + entry->setParent (this); + m_currentChangeset << entry; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddHistory::undo() const +{ LDDocument* f = getParent()->getFile(); + LDObject* obj = f->getObject (getIndex()); + f->forgetObject (obj); + delete obj; + + g_fullRefresh = true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddHistory::redo() const +{ LDDocument* f = getParent()->getFile(); + LDObject* obj = parseLine (getCode()); + f->insertObj (getIndex(), obj); + g_win->R()->compileObject (obj); +} + +// ============================================================================= +// heh +// ----------------------------------------------------------------------------- +void DelHistory::undo() const +{ LDDocument* f = getParent()->getFile(); + LDObject* obj = parseLine (getCode()); + f->insertObj (getIndex(), obj); + g_win->R()->compileObject (obj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void DelHistory::redo() const +{ LDDocument* f = getParent()->getFile(); + LDObject* obj = f->getObject (getIndex()); + f->forgetObject (obj); + delete obj; + + g_fullRefresh = true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void EditHistory::undo() const +{ LDObject* obj = getCurrentDocument()->getObject (getIndex()); + LDObject* newobj = parseLine (getOldCode()); + obj->replace (newobj); + g_win->R()->compileObject (newobj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void EditHistory::redo() const +{ LDObject* obj = getCurrentDocument()->getObject (getIndex()); + LDObject* newobj = parseLine (getNewCode()); + obj->replace (newobj); + g_win->R()->compileObject (newobj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void SwapHistory::undo() const +{ LDObject::fromID (a)->swap (LDObject::fromID (b)); +} + +void SwapHistory::redo() const +{ undo(); // :v +} \ No newline at end of file
--- a/src/history.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,183 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 "history.h" -#include "ldtypes.h" -#include "document.h" -#include "misc.h" -#include "gui.h" -#include "gldraw.h" - -bool g_fullRefresh = false; - -// ============================================================================= -// ----------------------------------------------------------------------------- -History::History() : - m_Position (-1) {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void History::undo() -{ if (m_changesets.isEmpty() || getPosition() == -1) - return; - - const Changeset& set = getChangeset (getPosition()); - g_fullRefresh = false; - - // Iterate the list in reverse and undo all actions - for (auto it = set.end() - 1; it != set.begin(); --it) - (*it)->undo(); - - decreasePosition(); - - if (!g_fullRefresh) - g_win->refresh(); - else - g_win->doFullRefresh(); - - g_win->updateActions(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void History::redo() -{ if (getPosition() == (long) m_changesets.size()) - return; - - const Changeset& set = getChangeset (getPosition() + 1); - g_fullRefresh = false; - - // Redo things - in the order as they were done in the first place - for (const AbstractHistoryEntry* change : set) - change->redo(); - - setPosition (getPosition() + 1); - - if (!g_fullRefresh) - g_win->refresh(); - else - g_win->doFullRefresh(); - - g_win->updateActions(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void History::clear() -{ for (Changeset set : m_changesets) - for (AbstractHistoryEntry* change : set) - delete change; - - m_changesets.clear(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void History::addStep() -{ if (m_currentChangeset.isEmpty()) - return; - - while (getPosition() < getSize() - 1) - m_changesets.removeLast(); - - m_changesets << m_currentChangeset; - m_currentChangeset.clear(); - setPosition (getPosition() + 1); - g_win->updateActions(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void History::add (AbstractHistoryEntry* entry) -{ if (isIgnoring()) - { delete entry; - return; - } - - entry->setParent (this); - m_currentChangeset << entry; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddHistory::undo() const -{ LDDocument* f = getParent()->getFile(); - LDObject* obj = f->getObject (getIndex()); - f->forgetObject (obj); - delete obj; - - g_fullRefresh = true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddHistory::redo() const -{ LDDocument* f = getParent()->getFile(); - LDObject* obj = parseLine (getCode()); - f->insertObj (getIndex(), obj); - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// heh -// ----------------------------------------------------------------------------- -void DelHistory::undo() const -{ LDDocument* f = getParent()->getFile(); - LDObject* obj = parseLine (getCode()); - f->insertObj (getIndex(), obj); - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void DelHistory::redo() const -{ LDDocument* f = getParent()->getFile(); - LDObject* obj = f->getObject (getIndex()); - f->forgetObject (obj); - delete obj; - - g_fullRefresh = true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void EditHistory::undo() const -{ LDObject* obj = getCurrentDocument()->getObject (getIndex()); - LDObject* newobj = parseLine (getOldCode()); - obj->replace (newobj); - g_win->R()->compileObject (newobj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void EditHistory::redo() const -{ LDObject* obj = getCurrentDocument()->getObject (getIndex()); - LDObject* newobj = parseLine (getNewCode()); - obj->replace (newobj); - g_win->R()->compileObject (newobj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void SwapHistory::undo() const -{ LDObject::fromID (a)->swap (LDObject::fromID (b)); -} - -void SwapHistory::redo() const -{ undo(); // :v -} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldconfig.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,194 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 "document.h" +#include "ldconfig.h" +#include "gui.h" +#include "misc.h" +#include "colors.h" + +// ============================================================================= +// Helper function for parseLDConfig +// ----------------------------------------------------------------------------- +static bool parseLDConfigTag (LDConfigParser& pars, char const* tag, str& val) +{ int pos; + + // Try find the token and get its position + if (!pars.findToken (pos, tag, 1)) + return false; + + // Get the token after it and store it into val + return pars.getToken (val, pos + 1); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void parseLDConfig() +{ File* f = openLDrawFile ("LDConfig.ldr", false); + + if (!f) + { critical (fmt (QObject::tr ("Unable to open LDConfig.ldr for parsing: %1"), + strerror (errno))); + return; + } + + // Read in the lines + for (str line : *f) + { if (line.length() == 0 || line[0] != '0') + continue; // empty or illogical + + line.remove ('\r'); + line.remove ('\n'); + + // Parse the line + LDConfigParser pars (line, ' '); + + int code = 0, alpha = 255; + str name, facename, edgename, valuestr; + + // Check 0 !COLOUR, parse the name + if (!pars.tokenCompare (0, "0") || !pars.tokenCompare (1, "!COLOUR") || !pars.getToken (name, 2)) + continue; + + // Replace underscores in the name with spaces for readability + name.replace ("_", " "); + + // Get the CODE tag + if (!parseLDConfigTag (pars, "CODE", valuestr)) + continue; + + if (!numeric (valuestr)) + continue; // not a number + + // Ensure that the code is within [0 - 511] + bool ok; + code = valuestr.toShort (&ok); + + if (!ok || code < 0 || code >= 512) + continue; + + // VALUE and EDGE tags + if (!parseLDConfigTag (pars, "VALUE", facename) || !parseLDConfigTag (pars, "EDGE", edgename)) + continue; + + // Ensure that our colors are correct + QColor faceColor (facename), + edgeColor (edgename); + + if (!faceColor.isValid() || !edgeColor.isValid()) + continue; + + // Parse alpha if given. + if (parseLDConfigTag (pars, "ALPHA", valuestr)) + alpha = clamp (valuestr.toInt(), 0, 255); + + LDColor* col = new LDColor; + col->name = name; + col->faceColor = faceColor; + col->edgeColor = edgeColor; + col->hexcode = facename; + col->faceColor.setAlpha (alpha); + col->index = code; + setColor (code, col); + } + + delete f; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDConfigParser::LDConfigParser (str inText, char sep) +{ m_tokens = inText.split (sep, QString::SkipEmptyParts); + m_pos = -1; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool LDConfigParser::isAtBeginning() +{ return m_pos == -1; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool LDConfigParser::isAtEnd() +{ return m_pos == m_tokens.size() - 1; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool LDConfigParser::getToken (str& val, const int pos) +{ if (pos >= m_tokens.size()) + return false; + + val = m_tokens[pos]; + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool LDConfigParser::getNextToken (str& val) +{ return getToken (val, ++m_pos); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool LDConfigParser::peekNextToken (str& val) +{ return getToken (val, m_pos + 1); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool LDConfigParser::findToken (int& result, char const* needle, int args) +{ for (int i = 0; i < (m_tokens.size() - args); ++i) + { if (m_tokens[i] == needle) + { result = i; + return true; + } + } + + return false; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDConfigParser::rewind() +{ m_pos = -1; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDConfigParser::seek (int amount, bool rel) +{ m_pos = (rel ? m_pos : 0) + amount; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int LDConfigParser::getSize() +{ return m_tokens.size(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool LDConfigParser::tokenCompare (int inPos, const char* sOther) +{ str tok; + + if (!getToken (tok, inPos)) + return false; + + return (tok == sOther); +}
--- a/src/ldconfig.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 "document.h" -#include "ldconfig.h" -#include "gui.h" -#include "misc.h" -#include "colors.h" - -// ============================================================================= -// Helper function for parseLDConfig -// ----------------------------------------------------------------------------- -static bool parseLDConfigTag (LDConfigParser& pars, char const* tag, str& val) -{ int pos; - - // Try find the token and get its position - if (!pars.findToken (pos, tag, 1)) - return false; - - // Get the token after it and store it into val - return pars.getToken (val, pos + 1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void parseLDConfig() -{ File* f = openLDrawFile ("LDConfig.ldr", false); - - if (!f) - { critical (fmt (QObject::tr ("Unable to open LDConfig.ldr for parsing: %1"), - strerror (errno))); - return; - } - - // Read in the lines - for (str line : *f) - { if (line.length() == 0 || line[0] != '0') - continue; // empty or illogical - - line.remove ('\r'); - line.remove ('\n'); - - // Parse the line - LDConfigParser pars (line, ' '); - - int code = 0, alpha = 255; - str name, facename, edgename, valuestr; - - // Check 0 !COLOUR, parse the name - if (!pars.tokenCompare (0, "0") || !pars.tokenCompare (1, "!COLOUR") || !pars.getToken (name, 2)) - continue; - - // Replace underscores in the name with spaces for readability - name.replace ("_", " "); - - // Get the CODE tag - if (!parseLDConfigTag (pars, "CODE", valuestr)) - continue; - - if (!numeric (valuestr)) - continue; // not a number - - // Ensure that the code is within [0 - 511] - bool ok; - code = valuestr.toShort (&ok); - - if (!ok || code < 0 || code >= 512) - continue; - - // VALUE and EDGE tags - if (!parseLDConfigTag (pars, "VALUE", facename) || !parseLDConfigTag (pars, "EDGE", edgename)) - continue; - - // Ensure that our colors are correct - QColor faceColor (facename), - edgeColor (edgename); - - if (!faceColor.isValid() || !edgeColor.isValid()) - continue; - - // Parse alpha if given. - if (parseLDConfigTag (pars, "ALPHA", valuestr)) - alpha = clamp (valuestr.toInt(), 0, 255); - - LDColor* col = new LDColor; - col->name = name; - col->faceColor = faceColor; - col->edgeColor = edgeColor; - col->hexcode = facename; - col->faceColor.setAlpha (alpha); - col->index = code; - setColor (code, col); - } - - delete f; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDConfigParser::LDConfigParser (str inText, char sep) -{ m_tokens = inText.split (sep, QString::SkipEmptyParts); - m_pos = -1; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::isAtBeginning() -{ return m_pos == -1; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::isAtEnd() -{ return m_pos == m_tokens.size() - 1; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::getToken (str& val, const int pos) -{ if (pos >= m_tokens.size()) - return false; - - val = m_tokens[pos]; - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::getNextToken (str& val) -{ return getToken (val, ++m_pos); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::peekNextToken (str& val) -{ return getToken (val, m_pos + 1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::findToken (int& result, char const* needle, int args) -{ for (int i = 0; i < (m_tokens.size() - args); ++i) - { if (m_tokens[i] == needle) - { result = i; - return true; - } - } - - return false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDConfigParser::rewind() -{ m_pos = -1; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDConfigParser::seek (int amount, bool rel) -{ m_pos = (rel ? m_pos : 0) + amount; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int LDConfigParser::getSize() -{ return m_tokens.size(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::tokenCompare (int inPos, const char* sOther) -{ str tok; - - if (!getToken (tok, inPos)) - return false; - - return (tok == sOther); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldtypes.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,746 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 "main.h" +#include "ldtypes.h" +#include "document.h" +#include "misc.h" +#include "gui.h" +#include "history.h" +#include "gldraw.h" +#include "colors.h" + +cfg (String, ld_defaultname, ""); +cfg (String, ld_defaultuser, ""); +cfg (Int, ld_defaultlicense, 0); + +// List of all LDObjects +QList<LDObject*> g_LDObjects; + +// ============================================================================= +// LDObject constructors +// ----------------------------------------------------------------------------- +LDObject::LDObject() : + m_Hidden (false), + m_Selected (false), + m_Parent (null), + m_File (null), + qObjListEntry (null), + m_glinit (false) +{ + memset (m_coords, 0, sizeof m_coords); + + // Determine ID + int32 id = 1; // 0 is invalid + + for (LDObject* obj : g_LDObjects) + if (obj->getID() >= id) + id = obj->getID() + 1; + + setID (id); + g_LDObjects << this; +} + +// ============================================================================= +// Default implementations for LDObject's virtual methods. These should never be +// actually called, for a subclass-less LDObject should never come into existance. +// These exist only to satisfy the linker. +// ----------------------------------------------------------------------------- +LDObject::Type LDObject::getType() const +{ return LDObject::Unidentified; +} + +bool LDObject::hasMatrix() const +{ return false; +} + +bool LDObject::isColored() const +{ return false; +} + +bool LDObject::isScemantic() const +{ return false; +} + +str LDObject::getTypeName() const +{ return ""; +} + +int LDObject::vertices() const +{ return 0; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::setVertexCoord (int i, Axis ax, double value) +{ vertex v = getVertex (i); + v[ax] = value; + setVertex (i, v); +} + +LDError::LDError() {} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDComment::raw() +{ return fmt ("0 %1", text); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDSubfile::raw() +{ str val = fmt ("1 %1 %2 ", getColor(), getPosition()); + val += getTransform().stringRep(); + val += ' '; + val += getFileInfo()->getName(); + return val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDLine::raw() +{ str val = fmt ("2 %1", getColor()); + + for (int i = 0; i < 2; ++i) + val += fmt (" %1", getVertex (i)); + + return val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDTriangle::raw() +{ str val = fmt ("3 %1", getColor()); + + for (int i = 0; i < 3; ++i) + val += fmt (" %1", getVertex (i)); + + return val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDQuad::raw() +{ str val = fmt ("4 %1", getColor()); + + for (int i = 0; i < 4; ++i) + val += fmt (" %1", getVertex (i)); + + return val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDCondLine::raw() +{ str val = fmt ("5 %1", getColor()); + + // Add the coordinates + for (int i = 0; i < 4; ++i) + val += fmt (" %1", getVertex (i)); + + return val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDError::raw() +{ return contents; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDVertex::raw() +{ return fmt ("0 !LDFORGE VERTEX %1 %2", getColor(), pos); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDEmpty::raw() +{ return ""; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +const char* LDBFC::statements[] = +{ "CERTIFY CCW", + "CCW", + "CERTIFY CW", + "CW", + "NOCERTIFY", + "INVERTNEXT", + "CLIP", + "CLIP CCW", + "CLIP CW", + "NOCLIP", +}; + +str LDBFC::raw() +{ return fmt ("0 BFC %1", LDBFC::statements[type]); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QList<LDTriangle*> LDQuad::splitToTriangles() +{ // Create the two triangles based on this quadrilateral: + // 0---3 0---3 3 + // | | | / /| + // | | ==> | / / | + // | | |/ / | + // 1---2 1 1---2 + LDTriangle* tri1 = new LDTriangle (getVertex (0), getVertex (1), getVertex (3)); + LDTriangle* tri2 = new LDTriangle (getVertex (1), getVertex (2), getVertex (3)); + + // The triangles also inherit the quad's color + tri1->setColor (getColor()); + tri2->setColor (getColor()); + + QList<LDTriangle*> triangles; + triangles << tri1; + triangles << tri2; + return triangles; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::replace (LDObject* other) +{ long idx = getIndex(); + assert (idx != -1); + + // Replace the instance of the old object with the new object + getFile()->setObject (idx, other); + + // Remove the old object + delete this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::swap (LDObject* other) +{ int i = 0; + + for (LDObject* obj : getFile()->getObjects()) + { if (obj == this) + getFile()->setObject (i, other); + elif (obj == other) + getFile()->setObject (i, this); + + ++i; + } + + getFile()->addToHistory (new SwapHistory (getID(), other->getID())); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDLine::LDLine (vertex v1, vertex v2) +{ setVertex (0, v1); + setVertex (1, v2); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDQuad::LDQuad (const vertex& v0, const vertex& v1, const vertex& v2, const vertex& v3) +{ setVertex (0, v0); + setVertex (1, v1); + setVertex (2, v2); + setVertex (3, v3); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject::~LDObject() +{ // If this object was selected, unselect it now + if (isSelected()) + unselect(); + + // If this object was associated to a file, remove it off it now + if (getFile()) + getFile()->forgetObject (this); + + // Delete the GL lists + GL::deleteLists (this); + + // Remove this object from the list of LDObjects + g_LDObjects.removeOne (this); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void transformObject (LDObject* obj, matrix transform, vertex pos, int parentcolor) +{ switch (obj->getType()) + { case LDObject::Line: + case LDObject::CondLine: + case LDObject::Triangle: + case LDObject::Quad: + + for (int i = 0; i < obj->vertices(); ++i) + { vertex v = obj->getVertex (i); + v.transform (transform, pos); + obj->setVertex (i, v); + } + + break; + + case LDObject::Subfile: + { LDSubfile* ref = static_cast<LDSubfile*> (obj); + matrix newMatrix = transform * ref->getTransform(); + vertex newpos = ref->getPosition(); + + newpos.transform (transform, pos); + ref->setPosition (newpos); + ref->setTransform (newMatrix); + } + break; + + default: + break; + } + + if (obj->getColor() == maincolor) + obj->setColor (parentcolor); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QList<LDObject*> LDSubfile::inlineContents (InlineFlags flags) +{ QList<LDObject*> objs = getFileInfo()->inlineContents (flags); + + // Transform the objects +for (LDObject * obj : objs) + { // Set the parent now so we know what inlined this. + obj->setParent (this); + transformObject (obj, getTransform(), getPosition(), getColor()); + } + + return objs; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +long LDObject::getIndex() const +{ +#ifndef RELEASE + assert (getFile() != null); +#endif + + for (int i = 0; i < getFile()->getObjectCount(); ++i) + if (getFile()->getObject (i) == this) + return i; + + return -1; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::moveObjects (QList<LDObject*> objs, const bool up) +{ if (objs.isEmpty()) + return; + + // If we move down, we need to iterate the array in reverse order. + const long start = up ? 0 : (objs.size() - 1); + const long end = up ? objs.size() : -1; + const long incr = up ? 1 : -1; + QList<LDObject*> objsToCompile; + LDDocument* file = objs[0]->getFile(); + + for (long i = start; i != end; i += incr) + { LDObject* obj = objs[i]; + + const long idx = obj->getIndex(), + target = idx + (up ? -1 : 1); + + if ( (up && idx == 0) || (!up && idx == (long) (file->getObjects().size() - 1))) + { // One of the objects hit the extrema. If this happens, this should be the first + // object to be iterated on. Thus, nothing has changed yet and it's safe to just + // abort the entire operation. + assert (i == start); + return; + } + + objsToCompile << obj; + objsToCompile << file->getObject (target); + + obj->swap (file->getObject (target)); + } + + removeDuplicates (objsToCompile); + + // The objects need to be recompiled, otherwise their pick lists are left with + // the wrong index colors which messes up selection. + for (LDObject* obj : objsToCompile) + g_win->R()->compileObject (obj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDObject::typeName (LDObject::Type type) +{ LDObject* obj = LDObject::getDefault (type); + str name = obj->getTypeName(); + delete obj; + return name; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDObject::describeObjects (const QList<LDObject*>& objs) +{ bool firstDetails = true; + str text = ""; + + if (objs.isEmpty()) + return "nothing"; // :) + + for (long i = 0; i < LDObject::NumTypes; ++i) + { LDObject::Type objType = (LDObject::Type) i; + int count = 0; + + for (LDObject * obj : objs) + if (obj->getType() == objType) + count++; + + if (count == 0) + continue; + + if (!firstDetails) + text += ", "; + + str noun = fmt ("%1%2", typeName (objType), plural (count)); + + // Plural of "vertex" is "vertices", correct that + + if (objType == LDObject::Vertex && count != 1) + noun = "vertices"; + + text += fmt ("%1 %2", count, noun); + firstDetails = false; + } + + return text; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject* LDObject::topLevelParent() +{ if (!getParent()) + return this; + + LDObject* it = this; + + while (it->getParent()) + it = it->getParent(); + + return it; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject* LDObject::next() const +{ long idx = getIndex(); + assert (idx != -1); + + if (idx == (long) getFile()->getObjectCount() - 1) + return null; + + return getFile()->getObject (idx + 1); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject* LDObject::prev() const +{ long idx = getIndex(); + assert (idx != -1); + + if (idx == 0) + return null; + + return getFile()->getObject (idx - 1); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::move (vertex vect) +{ if (hasMatrix()) + { LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (this); + mo->setPosition (mo->getPosition() + vect); + } + elif (getType() == LDObject::Vertex) + { // ugh + static_cast<LDVertex*> (this)->pos += vect; + } + else + { for (int i = 0; i < vertices(); ++i) + setVertex (i, getVertex (i) + vect); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +#define CHECK_FOR_OBJ(N) \ + if (type == LDObject::N) \ + return new LD##N; + +LDObject* LDObject::getDefault (const LDObject::Type type) +{ CHECK_FOR_OBJ (Comment) + CHECK_FOR_OBJ (BFC) + CHECK_FOR_OBJ (Line) + CHECK_FOR_OBJ (CondLine) + CHECK_FOR_OBJ (Subfile) + CHECK_FOR_OBJ (Triangle) + CHECK_FOR_OBJ (Quad) + CHECK_FOR_OBJ (Empty) + CHECK_FOR_OBJ (BFC) + CHECK_FOR_OBJ (Error) + CHECK_FOR_OBJ (Vertex) + CHECK_FOR_OBJ (Overlay) + return null; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::invert() {} +void LDBFC::invert() {} +void LDEmpty::invert() {} +void LDComment::invert() {} +void LDError::invert() {} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDTriangle::invert() +{ // Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1. + // Thus, we swap 1 and 2. + vertex tmp = getVertex (1); + setVertex (1, getVertex (2)); + setVertex (2, tmp); + + return; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDQuad::invert() +{ // Quad: 0 -> 1 -> 2 -> 3 + // rev: 0 -> 3 -> 2 -> 1 + // Thus, we swap 1 and 3. + vertex tmp = getVertex (1); + setVertex (1, getVertex (3)); + setVertex (3, tmp); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDSubfile::invert() +{ // Subfiles are inverted when they're prefixed with + // a BFC INVERTNEXT statement. Thus we need to toggle this status. + // For flat primitives it's sufficient that the determinant is + // flipped but I don't have a method for checking flatness yet. + // Food for thought... + + int idx = getIndex(); + + if (idx > 0) + { LDBFC* bfc = dynamic_cast<LDBFC*> (prev()); + + if (bfc && bfc->type == LDBFC::InvertNext) + { // This is prefixed with an invertnext, thus remove it. + getFile()->forgetObject (bfc); + delete bfc; + return; + } + } + + // Not inverted, thus prefix it with a new invertnext. + LDBFC* bfc = new LDBFC (LDBFC::InvertNext); + getFile()->insertObj (idx, bfc); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void invertLine (LDObject* line) +{ // For lines, we swap the vertices. I don't think that a + // cond-line's control points need to be swapped, do they? + vertex tmp = line->getVertex (0); + line->setVertex (0, line->getVertex (1)); + line->setVertex (1, tmp); +} + +void LDLine::invert() +{ invertLine (this); +} + +void LDCondLine::invert() +{ invertLine (this); +} + +void LDVertex::invert() {} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDLine* LDCondLine::demote() +{ LDLine* repl = new LDLine; + + for (int i = 0; i < repl->vertices(); ++i) + repl->setVertex (i, getVertex (i)); + + repl->setColor (getColor()); + + replace (repl); + return repl; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject* LDObject::fromID (int id) +{ for (LDObject * obj : g_LDObjects) + if (obj->getID() == id) + return obj; + + return null; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str LDOverlay::raw() +{ return fmt ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6", + getFileName(), getCamera(), getX(), getY(), getWidth(), getHeight()); +} + +void LDOverlay::invert() {} + +// ============================================================================= +// Hook the set accessors of certain properties to this changeProperty function. +// It takes care of history management so we can capture low-level changes, this +// makes history stuff work out of the box. +// ----------------------------------------------------------------------------- +template<class T> static void changeProperty (LDObject* obj, T* ptr, const T& val) +{ long idx; + + if (*ptr == val) + return; + + if (obj->getFile() && (idx = obj->getIndex()) != -1) + { str before = obj->raw(); + *ptr = val; + str after = obj->raw(); + + obj->getFile()->addToHistory (new EditHistory (idx, before, after)); + } + else + *ptr = val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::setColor (const int& val) +{ changeProperty (this, &m_Color, val); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +const vertex& LDObject::getVertex (int i) const +{ return m_coords[i]->data(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::setVertex (int i, const vertex& vert) +{ changeProperty (this, &m_coords[i], LDSharedVertex::getSharedVertex (vert)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDMatrixObject::setPosition (const vertex& a) +{ changeProperty (getLinkPointer(), &m_Position, LDSharedVertex::getSharedVertex (a)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDMatrixObject::setTransform (const matrix& val) +{ changeProperty (getLinkPointer(), &m_Transform, val); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static QMap<vertex, LDSharedVertex*> g_sharedVerts; + +LDSharedVertex* LDSharedVertex::getSharedVertex (const vertex& a) +{ auto it = g_sharedVerts.find (a); + + if (it == g_sharedVerts.end()) + { LDSharedVertex* v = new LDSharedVertex (a); + g_sharedVerts[a] = v; + return v; + } + + return *it; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDSharedVertex::addRef (LDObject* a) +{ m_refs << a; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDSharedVertex::delRef (LDObject* a) +{ m_refs.removeOne (a); + + if (m_refs.empty()) + { g_sharedVerts.remove (m_data); + delete this; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::select() +{ if (!getFile()) + { log ("Warning: Object #%1 cannot be selected as it is not assigned a file!\n", getID()); + return; + } + + getFile()->addToSelection (this); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::unselect() +{ if (!getFile()) + { log ("Warning: Object #%1 cannot be unselected as it is not assigned a file!\n", getID()); + return; + } + + getFile()->removeFromSelection (this); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str getLicenseText (int id) +{ switch (id) + { case 0: + return CALicense; + + case 1: + return NonCALicense; + + case 2: + return ""; + } + + assert (false); + return ""; +} \ No newline at end of file
--- a/src/ldtypes.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,746 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 "main.h" -#include "ldtypes.h" -#include "document.h" -#include "misc.h" -#include "gui.h" -#include "history.h" -#include "gldraw.h" -#include "colors.h" - -cfg (String, ld_defaultname, ""); -cfg (String, ld_defaultuser, ""); -cfg (Int, ld_defaultlicense, 0); - -// List of all LDObjects -QList<LDObject*> g_LDObjects; - -// ============================================================================= -// LDObject constructors -// ----------------------------------------------------------------------------- -LDObject::LDObject() : - m_Hidden (false), - m_Selected (false), - m_Parent (null), - m_File (null), - qObjListEntry (null), - m_glinit (false) -{ - memset (m_coords, 0, sizeof m_coords); - - // Determine ID - int32 id = 1; // 0 is invalid - - for (LDObject* obj : g_LDObjects) - if (obj->getID() >= id) - id = obj->getID() + 1; - - setID (id); - g_LDObjects << this; -} - -// ============================================================================= -// Default implementations for LDObject's virtual methods. These should never be -// actually called, for a subclass-less LDObject should never come into existance. -// These exist only to satisfy the linker. -// ----------------------------------------------------------------------------- -LDObject::Type LDObject::getType() const -{ return LDObject::Unidentified; -} - -bool LDObject::hasMatrix() const -{ return false; -} - -bool LDObject::isColored() const -{ return false; -} - -bool LDObject::isScemantic() const -{ return false; -} - -str LDObject::getTypeName() const -{ return ""; -} - -int LDObject::vertices() const -{ return 0; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::setVertexCoord (int i, Axis ax, double value) -{ vertex v = getVertex (i); - v[ax] = value; - setVertex (i, v); -} - -LDError::LDError() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDComment::raw() -{ return fmt ("0 %1", text); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDSubfile::raw() -{ str val = fmt ("1 %1 %2 ", getColor(), getPosition()); - val += getTransform().stringRep(); - val += ' '; - val += getFileInfo()->getName(); - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDLine::raw() -{ str val = fmt ("2 %1", getColor()); - - for (int i = 0; i < 2; ++i) - val += fmt (" %1", getVertex (i)); - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDTriangle::raw() -{ str val = fmt ("3 %1", getColor()); - - for (int i = 0; i < 3; ++i) - val += fmt (" %1", getVertex (i)); - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDQuad::raw() -{ str val = fmt ("4 %1", getColor()); - - for (int i = 0; i < 4; ++i) - val += fmt (" %1", getVertex (i)); - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDCondLine::raw() -{ str val = fmt ("5 %1", getColor()); - - // Add the coordinates - for (int i = 0; i < 4; ++i) - val += fmt (" %1", getVertex (i)); - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDError::raw() -{ return contents; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDVertex::raw() -{ return fmt ("0 !LDFORGE VERTEX %1 %2", getColor(), pos); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDEmpty::raw() -{ return ""; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const char* LDBFC::statements[] = -{ "CERTIFY CCW", - "CCW", - "CERTIFY CW", - "CW", - "NOCERTIFY", - "INVERTNEXT", - "CLIP", - "CLIP CCW", - "CLIP CW", - "NOCLIP", -}; - -str LDBFC::raw() -{ return fmt ("0 BFC %1", LDBFC::statements[type]); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDTriangle*> LDQuad::splitToTriangles() -{ // Create the two triangles based on this quadrilateral: - // 0---3 0---3 3 - // | | | / /| - // | | ==> | / / | - // | | |/ / | - // 1---2 1 1---2 - LDTriangle* tri1 = new LDTriangle (getVertex (0), getVertex (1), getVertex (3)); - LDTriangle* tri2 = new LDTriangle (getVertex (1), getVertex (2), getVertex (3)); - - // The triangles also inherit the quad's color - tri1->setColor (getColor()); - tri2->setColor (getColor()); - - QList<LDTriangle*> triangles; - triangles << tri1; - triangles << tri2; - return triangles; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::replace (LDObject* other) -{ long idx = getIndex(); - assert (idx != -1); - - // Replace the instance of the old object with the new object - getFile()->setObject (idx, other); - - // Remove the old object - delete this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::swap (LDObject* other) -{ int i = 0; - - for (LDObject* obj : getFile()->getObjects()) - { if (obj == this) - getFile()->setObject (i, other); - elif (obj == other) - getFile()->setObject (i, this); - - ++i; - } - - getFile()->addToHistory (new SwapHistory (getID(), other->getID())); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDLine::LDLine (vertex v1, vertex v2) -{ setVertex (0, v1); - setVertex (1, v2); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDQuad::LDQuad (const vertex& v0, const vertex& v1, const vertex& v2, const vertex& v3) -{ setVertex (0, v0); - setVertex (1, v1); - setVertex (2, v2); - setVertex (3, v3); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject::~LDObject() -{ // If this object was selected, unselect it now - if (isSelected()) - unselect(); - - // If this object was associated to a file, remove it off it now - if (getFile()) - getFile()->forgetObject (this); - - // Delete the GL lists - GL::deleteLists (this); - - // Remove this object from the list of LDObjects - g_LDObjects.removeOne (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void transformObject (LDObject* obj, matrix transform, vertex pos, int parentcolor) -{ switch (obj->getType()) - { case LDObject::Line: - case LDObject::CondLine: - case LDObject::Triangle: - case LDObject::Quad: - - for (int i = 0; i < obj->vertices(); ++i) - { vertex v = obj->getVertex (i); - v.transform (transform, pos); - obj->setVertex (i, v); - } - - break; - - case LDObject::Subfile: - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - matrix newMatrix = transform * ref->getTransform(); - vertex newpos = ref->getPosition(); - - newpos.transform (transform, pos); - ref->setPosition (newpos); - ref->setTransform (newMatrix); - } - break; - - default: - break; - } - - if (obj->getColor() == maincolor) - obj->setColor (parentcolor); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDObject*> LDSubfile::inlineContents (InlineFlags flags) -{ QList<LDObject*> objs = getFileInfo()->inlineContents (flags); - - // Transform the objects -for (LDObject * obj : objs) - { // Set the parent now so we know what inlined this. - obj->setParent (this); - transformObject (obj, getTransform(), getPosition(), getColor()); - } - - return objs; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -long LDObject::getIndex() const -{ -#ifndef RELEASE - assert (getFile() != null); -#endif - - for (int i = 0; i < getFile()->getObjectCount(); ++i) - if (getFile()->getObject (i) == this) - return i; - - return -1; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::moveObjects (QList<LDObject*> objs, const bool up) -{ if (objs.isEmpty()) - return; - - // If we move down, we need to iterate the array in reverse order. - const long start = up ? 0 : (objs.size() - 1); - const long end = up ? objs.size() : -1; - const long incr = up ? 1 : -1; - QList<LDObject*> objsToCompile; - LDDocument* file = objs[0]->getFile(); - - for (long i = start; i != end; i += incr) - { LDObject* obj = objs[i]; - - const long idx = obj->getIndex(), - target = idx + (up ? -1 : 1); - - if ( (up && idx == 0) || (!up && idx == (long) (file->getObjects().size() - 1))) - { // One of the objects hit the extrema. If this happens, this should be the first - // object to be iterated on. Thus, nothing has changed yet and it's safe to just - // abort the entire operation. - assert (i == start); - return; - } - - objsToCompile << obj; - objsToCompile << file->getObject (target); - - obj->swap (file->getObject (target)); - } - - removeDuplicates (objsToCompile); - - // The objects need to be recompiled, otherwise their pick lists are left with - // the wrong index colors which messes up selection. - for (LDObject* obj : objsToCompile) - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDObject::typeName (LDObject::Type type) -{ LDObject* obj = LDObject::getDefault (type); - str name = obj->getTypeName(); - delete obj; - return name; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDObject::describeObjects (const QList<LDObject*>& objs) -{ bool firstDetails = true; - str text = ""; - - if (objs.isEmpty()) - return "nothing"; // :) - - for (long i = 0; i < LDObject::NumTypes; ++i) - { LDObject::Type objType = (LDObject::Type) i; - int count = 0; - - for (LDObject * obj : objs) - if (obj->getType() == objType) - count++; - - if (count == 0) - continue; - - if (!firstDetails) - text += ", "; - - str noun = fmt ("%1%2", typeName (objType), plural (count)); - - // Plural of "vertex" is "vertices", correct that - - if (objType == LDObject::Vertex && count != 1) - noun = "vertices"; - - text += fmt ("%1 %2", count, noun); - firstDetails = false; - } - - return text; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDObject::topLevelParent() -{ if (!getParent()) - return this; - - LDObject* it = this; - - while (it->getParent()) - it = it->getParent(); - - return it; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDObject::next() const -{ long idx = getIndex(); - assert (idx != -1); - - if (idx == (long) getFile()->getObjectCount() - 1) - return null; - - return getFile()->getObject (idx + 1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDObject::prev() const -{ long idx = getIndex(); - assert (idx != -1); - - if (idx == 0) - return null; - - return getFile()->getObject (idx - 1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::move (vertex vect) -{ if (hasMatrix()) - { LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (this); - mo->setPosition (mo->getPosition() + vect); - } - elif (getType() == LDObject::Vertex) - { // ugh - static_cast<LDVertex*> (this)->pos += vect; - } - else - { for (int i = 0; i < vertices(); ++i) - setVertex (i, getVertex (i) + vect); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -#define CHECK_FOR_OBJ(N) \ - if (type == LDObject::N) \ - return new LD##N; - -LDObject* LDObject::getDefault (const LDObject::Type type) -{ CHECK_FOR_OBJ (Comment) - CHECK_FOR_OBJ (BFC) - CHECK_FOR_OBJ (Line) - CHECK_FOR_OBJ (CondLine) - CHECK_FOR_OBJ (Subfile) - CHECK_FOR_OBJ (Triangle) - CHECK_FOR_OBJ (Quad) - CHECK_FOR_OBJ (Empty) - CHECK_FOR_OBJ (BFC) - CHECK_FOR_OBJ (Error) - CHECK_FOR_OBJ (Vertex) - CHECK_FOR_OBJ (Overlay) - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::invert() {} -void LDBFC::invert() {} -void LDEmpty::invert() {} -void LDComment::invert() {} -void LDError::invert() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDTriangle::invert() -{ // Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1. - // Thus, we swap 1 and 2. - vertex tmp = getVertex (1); - setVertex (1, getVertex (2)); - setVertex (2, tmp); - - return; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDQuad::invert() -{ // Quad: 0 -> 1 -> 2 -> 3 - // rev: 0 -> 3 -> 2 -> 1 - // Thus, we swap 1 and 3. - vertex tmp = getVertex (1); - setVertex (1, getVertex (3)); - setVertex (3, tmp); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDSubfile::invert() -{ // Subfiles are inverted when they're prefixed with - // a BFC INVERTNEXT statement. Thus we need to toggle this status. - // For flat primitives it's sufficient that the determinant is - // flipped but I don't have a method for checking flatness yet. - // Food for thought... - - int idx = getIndex(); - - if (idx > 0) - { LDBFC* bfc = dynamic_cast<LDBFC*> (prev()); - - if (bfc && bfc->type == LDBFC::InvertNext) - { // This is prefixed with an invertnext, thus remove it. - getFile()->forgetObject (bfc); - delete bfc; - return; - } - } - - // Not inverted, thus prefix it with a new invertnext. - LDBFC* bfc = new LDBFC (LDBFC::InvertNext); - getFile()->insertObj (idx, bfc); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void invertLine (LDObject* line) -{ // For lines, we swap the vertices. I don't think that a - // cond-line's control points need to be swapped, do they? - vertex tmp = line->getVertex (0); - line->setVertex (0, line->getVertex (1)); - line->setVertex (1, tmp); -} - -void LDLine::invert() -{ invertLine (this); -} - -void LDCondLine::invert() -{ invertLine (this); -} - -void LDVertex::invert() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDLine* LDCondLine::demote() -{ LDLine* repl = new LDLine; - - for (int i = 0; i < repl->vertices(); ++i) - repl->setVertex (i, getVertex (i)); - - repl->setColor (getColor()); - - replace (repl); - return repl; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDObject::fromID (int id) -{ for (LDObject * obj : g_LDObjects) - if (obj->getID() == id) - return obj; - - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDOverlay::raw() -{ return fmt ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6", - getFileName(), getCamera(), getX(), getY(), getWidth(), getHeight()); -} - -void LDOverlay::invert() {} - -// ============================================================================= -// Hook the set accessors of certain properties to this changeProperty function. -// It takes care of history management so we can capture low-level changes, this -// makes history stuff work out of the box. -// ----------------------------------------------------------------------------- -template<class T> static void changeProperty (LDObject* obj, T* ptr, const T& val) -{ long idx; - - if (*ptr == val) - return; - - if (obj->getFile() && (idx = obj->getIndex()) != -1) - { str before = obj->raw(); - *ptr = val; - str after = obj->raw(); - - obj->getFile()->addToHistory (new EditHistory (idx, before, after)); - } - else - *ptr = val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::setColor (const int& val) -{ changeProperty (this, &m_Color, val); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const vertex& LDObject::getVertex (int i) const -{ return m_coords[i]->data(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::setVertex (int i, const vertex& vert) -{ changeProperty (this, &m_coords[i], LDSharedVertex::getSharedVertex (vert)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDMatrixObject::setPosition (const vertex& a) -{ changeProperty (getLinkPointer(), &m_Position, LDSharedVertex::getSharedVertex (a)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDMatrixObject::setTransform (const matrix& val) -{ changeProperty (getLinkPointer(), &m_Transform, val); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static QMap<vertex, LDSharedVertex*> g_sharedVerts; - -LDSharedVertex* LDSharedVertex::getSharedVertex (const vertex& a) -{ auto it = g_sharedVerts.find (a); - - if (it == g_sharedVerts.end()) - { LDSharedVertex* v = new LDSharedVertex (a); - g_sharedVerts[a] = v; - return v; - } - - return *it; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDSharedVertex::addRef (LDObject* a) -{ m_refs << a; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDSharedVertex::delRef (LDObject* a) -{ m_refs.removeOne (a); - - if (m_refs.empty()) - { g_sharedVerts.remove (m_data); - delete this; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::select() -{ if (!getFile()) - { log ("Warning: Object #%1 cannot be selected as it is not assigned a file!\n", getID()); - return; - } - - getFile()->addToSelection (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::unselect() -{ if (!getFile()) - { log ("Warning: Object #%1 cannot be unselected as it is not assigned a file!\n", getID()); - return; - } - - getFile()->removeFromSelection (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str getLicenseText (int id) -{ switch (id) - { case 0: - return CALicense; - - case 1: - return NonCALicense; - - case 2: - return ""; - } - - assert (false); - return ""; -} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,141 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QApplication> +#include <QMessageBox> +#include <QAbstractButton> +#include <QFile> +#include <QTextStream> +#include "gui.h" +#include "document.h" +#include "misc.h" +#include "config.h" +#include "colors.h" +#include "types.h" +#include "primitives.h" +#include "gldraw.h" +#include "configDialog.h" +#include "dialogs.h" +#include "crashcatcher.h" + +QList<LDDocument*> g_loadedFiles; +ForgeWindow* g_win = null; +const QApplication* g_app = null; +File g_file_stdout (stdout, File::Write); +File g_file_stderr (stderr, File::Write); +static str g_versionString, g_fullVersionString; + +const vertex g_origin (0.0f, 0.0f, 0.0f); +const matrix g_identity ( {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}); + +cfg (Bool, firststart, true); + +// ============================================================================= +// ----------------------------------------------------------------------------- +int main (int argc, char* argv[]) +{ QApplication app (argc, argv); + app.setOrganizationName (APPNAME); + app.setApplicationName (APPNAME); + g_app = &app; + + initCrashCatcher(); + LDDocument::setCurrent (null); + + // Load or create the configuration + if (!Config::load()) + { log ("Creating configuration file...\n"); + + if (Config::save()) + log ("Configuration file successfully created.\n"); + else + log ("failed to create configuration file!\n"); + } + + LDPaths::initPaths(); + initColors(); + loadLogoedStuds(); + + ForgeWindow* win = new ForgeWindow; + newFile(); + win->show(); + + // If this is the first start, get the user to configuration. Especially point + // them to the profile tab, it's the most important form to fill in. + if (firststart) + { (new ConfigDialog (ConfigDialog::ProfileTab))->exec(); + firststart = false; + Config::save(); + } + + loadPrimitives(); + return app.exec(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void doPrint (File& f, initlist<StringFormatArg> args) +{ str msg = DoFormat (args); + f.write (msg.toUtf8()); + f.flush(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void doPrint (FILE* fp, initlist<StringFormatArg> args) +{ str msg = DoFormat (args); + fwrite (msg.toStdString().c_str(), 1, msg.length(), fp); + fflush (fp); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString versionString() +{ if (g_versionString.length() == 0) + { +#if VERSION_PATCH == 0 + g_versionString = fmt ("%1.%2", VERSION_MAJOR, VERSION_MINOR); +#else + g_versionString = fmt ("%1.%2.%3", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); +#endif // VERSION_PATCH + } + + return g_versionString; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString versionMoniker() +{ +#if BUILD_ID == BUILD_INTERNAL + return "Internal"; +#elif BUILD_ID == BUILD_ALPHA + return "Alpha"; +#elif BUILD_ID == BUILD_BETA + return "Beta"; +#elif BUILD_ID == BUILD_RC + return fmt ("RC %1", RC_NUMBER); +#else + return ""; +#endif // BUILD_ID +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString fullVersionString() +{ return fmt ("v%1 %2", versionString(), versionMoniker()); +} \ No newline at end of file
--- a/src/main.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QApplication> -#include <QMessageBox> -#include <QAbstractButton> -#include <QFile> -#include <QTextStream> -#include "gui.h" -#include "document.h" -#include "misc.h" -#include "config.h" -#include "colors.h" -#include "types.h" -#include "primitives.h" -#include "gldraw.h" -#include "configDialog.h" -#include "dialogs.h" -#include "crashcatcher.h" - -QList<LDDocument*> g_loadedFiles; -ForgeWindow* g_win = null; -const QApplication* g_app = null; -File g_file_stdout (stdout, File::Write); -File g_file_stderr (stderr, File::Write); -static str g_versionString, g_fullVersionString; - -const vertex g_origin (0.0f, 0.0f, 0.0f); -const matrix g_identity ( {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}); - -cfg (Bool, firststart, true); - -// ============================================================================= -// ----------------------------------------------------------------------------- -int main (int argc, char* argv[]) -{ QApplication app (argc, argv); - app.setOrganizationName (APPNAME); - app.setApplicationName (APPNAME); - g_app = &app; - - initCrashCatcher(); - LDDocument::setCurrent (null); - - // Load or create the configuration - if (!Config::load()) - { log ("Creating configuration file...\n"); - - if (Config::save()) - log ("Configuration file successfully created.\n"); - else - log ("failed to create configuration file!\n"); - } - - LDPaths::initPaths(); - initColors(); - loadLogoedStuds(); - - ForgeWindow* win = new ForgeWindow; - newFile(); - win->show(); - - // If this is the first start, get the user to configuration. Especially point - // them to the profile tab, it's the most important form to fill in. - if (firststart) - { (new ConfigDialog (ConfigDialog::ProfileTab))->exec(); - firststart = false; - Config::save(); - } - - loadPrimitives(); - return app.exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void doPrint (File& f, initlist<StringFormatArg> args) -{ str msg = DoFormat (args); - f.write (msg.toUtf8()); - f.flush(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void doPrint (FILE* fp, initlist<StringFormatArg> args) -{ str msg = DoFormat (args); - fwrite (msg.toStdString().c_str(), 1, msg.length(), fp); - fflush (fp); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString versionString() -{ if (g_versionString.length() == 0) - { -#if VERSION_PATCH == 0 - g_versionString = fmt ("%1.%2", VERSION_MAJOR, VERSION_MINOR); -#else - g_versionString = fmt ("%1.%2.%3", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); -#endif // VERSION_PATCH - } - - return g_versionString; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString versionMoniker() -{ -#if BUILD_ID == BUILD_INTERNAL - return "Internal"; -#elif BUILD_ID == BUILD_ALPHA - return "Alpha"; -#elif BUILD_ID == BUILD_BETA - return "Beta"; -#elif BUILD_ID == BUILD_RC - return fmt ("RC %1", RC_NUMBER); -#else - return ""; -#endif // BUILD_ID -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString fullVersionString() -{ return fmt ("v%1 %2", versionString(), versionMoniker()); -} \ No newline at end of file
--- a/src/main.h Fri Dec 13 00:39:49 2013 +0200 +++ b/src/main.h Fri Dec 13 20:01:49 2013 +0200 @@ -122,6 +122,7 @@ #define properties private #define typedefs public #define methods +#define for_axes(AX) for (const Axis ax : std::initializer_list<const Axis> ({X, Y, Z})) // ----------------------------------------------------------------------------- #ifdef IN_IDE_PARSER // KDevelop workarounds:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/messagelog.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,129 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QTimer> +#include <QDate> +#include "messagelog.h" +#include "gldraw.h" +#include "gui.h" +#include "moc_messagelog.cpp" + +static const int g_maxMessages = 5; +static const int g_expiry = 5; +static const int g_fadeTime = 500; // msecs + +// ============================================================================= +// ----------------------------------------------------------------------------- +MessageManager::MessageManager (QObject* parent) : + QObject (parent) +{ m_ticker = new QTimer; + m_ticker->start (100); + connect (m_ticker, SIGNAL (timeout()), this, SLOT (tick())); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +MessageManager::Line::Line (str text) : + text (text), + alpha (1.0f), + expiry (QDateTime::currentDateTime().addSecs (g_expiry)) {} + +// ============================================================================= +// Check this line's expiry and update alpha accordingly. Returns true if the +// line is to still stick around, false if it expired. 'changed' is updated to +// whether the line has somehow changed since the last update. +// ----------------------------------------------------------------------------- +bool MessageManager::Line::update (bool& changed) +{ changed = false; + QDateTime now = QDateTime::currentDateTime(); + int msec = now.msecsTo (expiry); + + if (now >= expiry) + { // Message line has expired + changed = true; + return false; + } + + if (msec <= g_fadeTime) + { // Message line has not expired but is fading out + alpha = ( (float) msec) / g_fadeTime; + changed = true; + } + + return true; +} + +// ============================================================================= +// Add a line to the message manager. +// ----------------------------------------------------------------------------- +void MessageManager::addLine (str line) +{ // If there's too many entries, pop the excess out + while (m_lines.size() >= g_maxMessages) + m_lines.removeFirst(); + + m_lines << Line (line); + + // Update the renderer view + if (getRenderer()) + getRenderer()->update(); +} + +// ============================================================================= +// Ticks the message manager. All lines are ticked and the renderer scene is +// redrawn if something changed. +// ----------------------------------------------------------------------------- +void MessageManager::tick() +{ if (m_lines.isEmpty()) + return; + + bool changed = false; + + for (int i = 0; i < m_lines.size(); ++i) + { bool lineChanged; + + if (!m_lines[i].update (lineChanged)) + m_lines.removeAt (i--); + + changed |= lineChanged; + } + + if (changed && getRenderer()) + getRenderer()->update(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +const QList<MessageManager::Line>& MessageManager::getLines() const +{ return m_lines; +} + +// ============================================================================= +// log() interface - format the argument list and add the resulting string to +// the main message manager. +// ----------------------------------------------------------------------------- +void DoLog (std::initializer_list<StringFormatArg> args) +{ const str msg = DoFormat (args); + + for (str& a : msg.split ("\n", QString::SkipEmptyParts)) + { if (g_win) + g_win->addMessage (a); + + // Also print it to stdout + fprint (stdout, "%1\n", a); + } +}
--- a/src/messagelog.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QTimer> -#include <QDate> -#include "messagelog.h" -#include "gldraw.h" -#include "gui.h" -#include "moc_messagelog.cpp" - -static const int g_maxMessages = 5; -static const int g_expiry = 5; -static const int g_fadeTime = 500; // msecs - -// ============================================================================= -// ----------------------------------------------------------------------------- -MessageManager::MessageManager (QObject* parent) : - QObject (parent) -{ m_ticker = new QTimer; - m_ticker->start (100); - connect (m_ticker, SIGNAL (timeout()), this, SLOT (tick())); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -MessageManager::Line::Line (str text) : - text (text), - alpha (1.0f), - expiry (QDateTime::currentDateTime().addSecs (g_expiry)) {} - -// ============================================================================= -// Check this line's expiry and update alpha accordingly. Returns true if the -// line is to still stick around, false if it expired. 'changed' is updated to -// whether the line has somehow changed since the last update. -// ----------------------------------------------------------------------------- -bool MessageManager::Line::update (bool& changed) -{ changed = false; - QDateTime now = QDateTime::currentDateTime(); - int msec = now.msecsTo (expiry); - - if (now >= expiry) - { // Message line has expired - changed = true; - return false; - } - - if (msec <= g_fadeTime) - { // Message line has not expired but is fading out - alpha = ( (float) msec) / g_fadeTime; - changed = true; - } - - return true; -} - -// ============================================================================= -// Add a line to the message manager. -// ----------------------------------------------------------------------------- -void MessageManager::addLine (str line) -{ // If there's too many entries, pop the excess out - while (m_lines.size() >= g_maxMessages) - m_lines.removeFirst(); - - m_lines << Line (line); - - // Update the renderer view - if (getRenderer()) - getRenderer()->update(); -} - -// ============================================================================= -// Ticks the message manager. All lines are ticked and the renderer scene is -// redrawn if something changed. -// ----------------------------------------------------------------------------- -void MessageManager::tick() -{ if (m_lines.isEmpty()) - return; - - bool changed = false; - - for (int i = 0; i < m_lines.size(); ++i) - { bool lineChanged; - - if (!m_lines[i].update (lineChanged)) - m_lines.removeAt (i--); - - changed |= lineChanged; - } - - if (changed && getRenderer()) - getRenderer()->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const QList<MessageManager::Line>& MessageManager::getLines() const -{ return m_lines; -} - -// ============================================================================= -// log() interface - format the argument list and add the resulting string to -// the main message manager. -// ----------------------------------------------------------------------------- -void DoLog (std::initializer_list<StringFormatArg> args) -{ const str msg = DoFormat (args); - - for (str& a : msg.split ("\n", QString::SkipEmptyParts)) - { if (g_win) - g_win->addMessage (a); - - // Also print it to stdout - fprint (stdout, "%1\n", a); - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,428 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <math.h> +#include <locale.h> +#include <QColor> +#include "main.h" +#include "misc.h" +#include "gui.h" +#include "dialogs.h" +#include "ui_rotpoint.h" + +RingFinder g_RingFinder; + +// Prime number table. +const int g_primes[NUM_PRIMES] = +{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, + 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, + 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, + 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, + 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, + 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, + 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, + 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, + 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, + 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, + 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, + 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, + 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, + 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, + 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, + 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, + 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, + 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, + 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, + 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, + 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, + 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, + 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, + 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, + 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, + 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, + 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, + 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, + 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, + 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, + 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, + 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, + 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, + 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, + 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, + 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, + 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, + 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, + 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, + 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, + 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, + 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, + 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, + 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, + 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, + 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, + 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, + 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, + 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, + 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, +}; + +static const int32 g_e10[] = +{ 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, +}; + +// ============================================================================= +// ----------------------------------------------------------------------------- +// Grid stuff +cfg (Int, grid, Grid::Medium); + +cfg (Float, grid_coarse_x, 5.0f); +cfg (Float, grid_coarse_y, 5.0f); +cfg (Float, grid_coarse_z, 5.0f); +cfg (Float, grid_coarse_angle, 45.0f); +cfg (Float, grid_medium_x, 1.0f); +cfg (Float, grid_medium_y, 1.0f); +cfg (Float, grid_medium_z, 1.0f); +cfg (Float, grid_medium_angle, 22.5f); +cfg (Float, grid_fine_x, 0.1f); +cfg (Float, grid_fine_y, 0.1f); +cfg (Float, grid_fine_z, 0.1f); +cfg (Float, grid_fine_angle, 7.5f); +cfg (Int, edit_rotpoint, 0); +cfg (Float, edit_rotpoint_x, 0.0f); // TODO: make a VertexConfig and use it here +cfg (Float, edit_rotpoint_y, 0.0f); +cfg (Float, edit_rotpoint_z, 0.0f); + +const gridinfo g_GridInfo[3] = +{ { "Coarse", { &grid_coarse_x, &grid_coarse_y, &grid_coarse_z, &grid_coarse_angle }}, + { "Medium", { &grid_medium_x, &grid_medium_y, &grid_medium_z, &grid_medium_angle }}, + { "Fine", { &grid_fine_x, &grid_fine_y, &grid_fine_z, &grid_fine_angle }} +}; + +// ============================================================================= +// Snap the given coordinate value on the current grid's given axis. +// ----------------------------------------------------------------------------- +double Grid::snap (double in, const Grid::Config axis) +{ const double gridval = currentGrid().confs[axis]->value; + const long mult = abs (in / gridval); + const bool neg = (in < 0); + double out = mult * gridval; + + if (abs<double> (in) - (mult * gridval) > gridval / 2) + out += gridval; + + if (neg && out != 0) + out *= -1; + + return out; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool numeric (const str& tok) +{ bool gotDot = false; + + for (int i = 0; i < tok.length(); ++i) + { const QChar c = tok[i]; + + // Allow leading hyphen for negatives + if (i == 0 && c == '-') + continue; + + // Check for decimal point + if (!gotDot && c == '.') + { gotDot = true; + continue; + } + + if (c >= '0' && c <= '9') + continue; // Digit + + // If the above cases didn't catch this character, it was + // illegal and this is therefore not a number. + return false; + } + + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void simplify (int& numer, int& denom) +{ bool repeat; + + do + { repeat = false; + + for (int x = 0; x < NUM_PRIMES; x++) + { const int prime = g_primes[NUM_PRIMES - x - 1]; + + if (numer <= prime || denom <= prime) + continue; + + if ( (numer % prime == 0) && (denom % prime == 0)) + { numer /= prime; + denom /= prime; + repeat = true; + break; + } + } + } + while (repeat); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +vertex rotPoint (const QList<LDObject*>& objs) +{ LDBoundingBox box; + + switch (edit_rotpoint) + { case ObjectOrigin: + { // Calculate center vertex + for (LDObject* obj : objs) + if (obj->hasMatrix()) + box << dynamic_cast<LDMatrixObject*> (obj)->getPosition(); + else + box << obj; + + return box.center(); + } + + case WorldOrigin: + { return g_origin; + } + + case CustomPoint: + { return vertex (edit_rotpoint_x, edit_rotpoint_y, edit_rotpoint_z); + } + } + + return vertex(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void configRotationPoint() +{ QDialog* dlg = new QDialog; + Ui::RotPointUI ui; + ui.setupUi (dlg); + + switch (edit_rotpoint) + { case ObjectOrigin: + ui.objectPoint->setChecked (true); + break; + + case WorldOrigin: + ui.worldPoint->setChecked (true); + break; + + case CustomPoint: + ui.customPoint->setChecked (true); + break; + } + + ui.customX->setValue (edit_rotpoint_x); + ui.customY->setValue (edit_rotpoint_y); + ui.customZ->setValue (edit_rotpoint_z); + + if (!dlg->exec()) + return; + + edit_rotpoint = + (ui.objectPoint->isChecked()) ? ObjectOrigin : + (ui.worldPoint->isChecked()) ? WorldOrigin : + CustomPoint; + + edit_rotpoint_x = ui.customX->value(); + edit_rotpoint_y = ui.customY->value(); + edit_rotpoint_z = ui.customZ->value(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str join (initlist<StringFormatArg> vals, str delim) +{ QStringList list; + + for (const StringFormatArg& arg : vals) + list << arg.value(); + + return list.join (delim); +} + +// ============================================================================= +// This is the main algorithm of the ring finder. It tries to use math to find +// the one ring between r0 and r1. If it fails (the ring number is non-integral), +// it finds an intermediate radius (ceil of the ring number times scale) and +// splits the radius at this point, calling this function again to try find the +// rings between r0 - r and r - r1. +// +// This does not always yield into usable results. If at some point r == r0 or +// r == r1, there is no hope of finding the rings, at least with this algorithm, +// as it would fall into an infinite recursion. +// ----------------------------------------------------------------------------- +bool RingFinder::findRingsRecursor (double r0, double r1, Solution& currentSolution) +{ // Don't recurse too deep. + if (m_stack >= 5) + return false; + + // Find the scale and number of a ring between r1 and r0. + assert (r1 >= r0); + double scale = r1 - r0; + double num = r0 / scale; + + // If the ring number is integral, we have found a fitting ring to r0 -> r1! + if (isInteger (num)) + { Component cmp; + cmp.scale = scale; + cmp.num = (int) round (num); + currentSolution.addComponent (cmp); + + // If we're still at the first recursion, this is the only + // ring and there's nothing left to do. Guess we found the winner. + if (m_stack == 0) + { m_solutions.push_back (currentSolution); + return true; + } + } + else + { // Try find solutions by splitting the ring in various positions. + if (isZero (r1 - r0)) + return false; + + double interval; + + // Determine interval. The smaller delta between radii, the more precise + // interval should be used. We can't really use a 0.5 increment when + // calculating rings to 10 -> 105... that would take ages to process! + if (r1 - r0 < 0.5) + interval = 0.1; + else if (r1 - r0 < 10) + interval = 0.5; + else if (r1 - r0 < 50) + interval = 1; + else + interval = 5; + + // Now go through possible splits and try find rings for both segments. + for (double r = r0 + interval; r < r1; r += interval) + { Solution sol = currentSolution; + + m_stack++; + bool res = findRingsRecursor (r0, r, sol) && findRingsRecursor (r, r1, sol); + m_stack--; + + if (res) + { // We succeeded in finding radii for this segment. If the stack is 0, this + // is the first recursion to this function. Thus there are no more ring segments + // to process and we can add the solution. + // + // If not, when this function ends, it will be called again with more arguments. + // Accept the solution to this segment by setting currentSolution to sol, and + // return true to continue processing. + if (m_stack == 0) + m_solutions.push_back (sol); + else + { currentSolution = sol; + return true; + } + } + } + + return false; + } + + return true; +} + +// ============================================================================= +// Main function. Call this with r0 and r1. If this returns true, use bestSolution +// for the solution that was presented. +// ----------------------------------------------------------------------------- +bool RingFinder::findRings (double r0, double r1) +{ m_solutions.clear(); + Solution sol; + + // Recurse in and try find solutions. + findRingsRecursor (r0, r1, sol); + + // Compare the solutions and find the best one. The solution class has an operator> + // overload to compare two solutions. + m_bestSolution = null; + + for (QVector<Solution>::iterator solp = m_solutions.begin(); solp != m_solutions.end(); ++solp) + { const Solution& sol = *solp; + + if (m_bestSolution == null || sol > *m_bestSolution) + m_bestSolution = / + } + + return (m_bestSolution != null); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool RingFinder::Solution::operator> (const RingFinder::Solution& other) const +{ // If this solution has less components than the other one, this one + // is definitely better. + if (getComponents().size() < other.getComponents().size()) + return true; + + // vice versa + if (other.getComponents().size() < getComponents().size()) + return false; + + // Calculate the maximum ring number. Since the solutions have equal + // ring counts, the solutions with lesser maximum rings should result + // in cleaner code and less new primitives, right? + int maxA = 0, + maxB = 0; + + for (int i = 0; i < getComponents().size(); ++i) + { if (getComponents()[i].num > maxA) + maxA = getComponents()[i].num; + + if (other.getComponents()[i].num > maxB) + maxB = other.getComponents()[i].num; + } + + if (maxA < maxB) + return true; + + if (maxB < maxA) + return false; + + // Solutions have equal rings and equal maximum ring numbers. Let's + // just say this one is better, at this point it does not matter which + // one is chosen. + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void roundToDecimals (double& a, int decimals) +{ assert (decimals >= 0 && decimals < (signed) (sizeof g_e10 / sizeof *g_e10)); + a = round (a * g_e10[decimals]) / g_e10[decimals]; +} \ No newline at end of file
--- a/src/misc.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,428 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <math.h> -#include <locale.h> -#include <QColor> -#include "main.h" -#include "misc.h" -#include "gui.h" -#include "dialogs.h" -#include "ui_rotpoint.h" - -RingFinder g_RingFinder; - -// Prime number table. -const int g_primes[NUM_PRIMES] = -{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, - 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, - 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, - 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, - 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, - 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, - 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, - 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, - 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, - 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, - 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, - 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, - 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, - 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, - 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, - 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, - 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, - 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, - 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, - 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, - 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, - 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, - 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, - 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, - 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, - 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, - 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, - 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, - 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, - 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, - 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, - 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, - 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, - 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, - 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, - 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, - 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, - 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, - 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, - 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, - 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, - 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, - 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, - 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, - 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, - 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, - 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, - 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, - 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, - 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, -}; - -static const int32 g_e10[] = -{ 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, -}; - -// ============================================================================= -// ----------------------------------------------------------------------------- -// Grid stuff -cfg (Int, grid, Grid::Medium); - -cfg (Float, grid_coarse_x, 5.0f); -cfg (Float, grid_coarse_y, 5.0f); -cfg (Float, grid_coarse_z, 5.0f); -cfg (Float, grid_coarse_angle, 45.0f); -cfg (Float, grid_medium_x, 1.0f); -cfg (Float, grid_medium_y, 1.0f); -cfg (Float, grid_medium_z, 1.0f); -cfg (Float, grid_medium_angle, 22.5f); -cfg (Float, grid_fine_x, 0.1f); -cfg (Float, grid_fine_y, 0.1f); -cfg (Float, grid_fine_z, 0.1f); -cfg (Float, grid_fine_angle, 7.5f); -cfg (Int, edit_rotpoint, 0); -cfg (Float, edit_rotpoint_x, 0.0f); // TODO: make a VertexConfig and use it here -cfg (Float, edit_rotpoint_y, 0.0f); -cfg (Float, edit_rotpoint_z, 0.0f); - -const gridinfo g_GridInfo[3] = -{ { "Coarse", { &grid_coarse_x, &grid_coarse_y, &grid_coarse_z, &grid_coarse_angle }}, - { "Medium", { &grid_medium_x, &grid_medium_y, &grid_medium_z, &grid_medium_angle }}, - { "Fine", { &grid_fine_x, &grid_fine_y, &grid_fine_z, &grid_fine_angle }} -}; - -// ============================================================================= -// Snap the given coordinate value on the current grid's given axis. -// ----------------------------------------------------------------------------- -double Grid::snap (double in, const Grid::Config axis) -{ const double gridval = currentGrid().confs[axis]->value; - const long mult = abs (in / gridval); - const bool neg = (in < 0); - double out = mult * gridval; - - if (abs<double> (in) - (mult * gridval) > gridval / 2) - out += gridval; - - if (neg && out != 0) - out *= -1; - - return out; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool numeric (const str& tok) -{ bool gotDot = false; - - for (int i = 0; i < tok.length(); ++i) - { const QChar c = tok[i]; - - // Allow leading hyphen for negatives - if (i == 0 && c == '-') - continue; - - // Check for decimal point - if (!gotDot && c == '.') - { gotDot = true; - continue; - } - - if (c >= '0' && c <= '9') - continue; // Digit - - // If the above cases didn't catch this character, it was - // illegal and this is therefore not a number. - return false; - } - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void simplify (int& numer, int& denom) -{ bool repeat; - - do - { repeat = false; - - for (int x = 0; x < NUM_PRIMES; x++) - { const int prime = g_primes[NUM_PRIMES - x - 1]; - - if (numer <= prime || denom <= prime) - continue; - - if ( (numer % prime == 0) && (denom % prime == 0)) - { numer /= prime; - denom /= prime; - repeat = true; - break; - } - } - } - while (repeat); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex rotPoint (const QList<LDObject*>& objs) -{ LDBoundingBox box; - - switch (edit_rotpoint) - { case ObjectOrigin: - { // Calculate center vertex - for (LDObject* obj : objs) - if (obj->hasMatrix()) - box << dynamic_cast<LDMatrixObject*> (obj)->getPosition(); - else - box << obj; - - return box.center(); - } - - case WorldOrigin: - { return g_origin; - } - - case CustomPoint: - { return vertex (edit_rotpoint_x, edit_rotpoint_y, edit_rotpoint_z); - } - } - - return vertex(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void configRotationPoint() -{ QDialog* dlg = new QDialog; - Ui::RotPointUI ui; - ui.setupUi (dlg); - - switch (edit_rotpoint) - { case ObjectOrigin: - ui.objectPoint->setChecked (true); - break; - - case WorldOrigin: - ui.worldPoint->setChecked (true); - break; - - case CustomPoint: - ui.customPoint->setChecked (true); - break; - } - - ui.customX->setValue (edit_rotpoint_x); - ui.customY->setValue (edit_rotpoint_y); - ui.customZ->setValue (edit_rotpoint_z); - - if (!dlg->exec()) - return; - - edit_rotpoint = - (ui.objectPoint->isChecked()) ? ObjectOrigin : - (ui.worldPoint->isChecked()) ? WorldOrigin : - CustomPoint; - - edit_rotpoint_x = ui.customX->value(); - edit_rotpoint_y = ui.customY->value(); - edit_rotpoint_z = ui.customZ->value(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str join (initlist<StringFormatArg> vals, str delim) -{ QStringList list; - - for (const StringFormatArg& arg : vals) - list << arg.value(); - - return list.join (delim); -} - -// ============================================================================= -// This is the main algorithm of the ring finder. It tries to use math to find -// the one ring between r0 and r1. If it fails (the ring number is non-integral), -// it finds an intermediate radius (ceil of the ring number times scale) and -// splits the radius at this point, calling this function again to try find the -// rings between r0 - r and r - r1. -// -// This does not always yield into usable results. If at some point r == r0 or -// r == r1, there is no hope of finding the rings, at least with this algorithm, -// as it would fall into an infinite recursion. -// ----------------------------------------------------------------------------- -bool RingFinder::findRingsRecursor (double r0, double r1, Solution& currentSolution) -{ // Don't recurse too deep. - if (m_stack >= 5) - return false; - - // Find the scale and number of a ring between r1 and r0. - assert (r1 >= r0); - double scale = r1 - r0; - double num = r0 / scale; - - // If the ring number is integral, we have found a fitting ring to r0 -> r1! - if (isInteger (num)) - { Component cmp; - cmp.scale = scale; - cmp.num = (int) round (num); - currentSolution.addComponent (cmp); - - // If we're still at the first recursion, this is the only - // ring and there's nothing left to do. Guess we found the winner. - if (m_stack == 0) - { m_solutions.push_back (currentSolution); - return true; - } - } - else - { // Try find solutions by splitting the ring in various positions. - if (isZero (r1 - r0)) - return false; - - double interval; - - // Determine interval. The smaller delta between radii, the more precise - // interval should be used. We can't really use a 0.5 increment when - // calculating rings to 10 -> 105... that would take ages to process! - if (r1 - r0 < 0.5) - interval = 0.1; - else if (r1 - r0 < 10) - interval = 0.5; - else if (r1 - r0 < 50) - interval = 1; - else - interval = 5; - - // Now go through possible splits and try find rings for both segments. - for (double r = r0 + interval; r < r1; r += interval) - { Solution sol = currentSolution; - - m_stack++; - bool res = findRingsRecursor (r0, r, sol) && findRingsRecursor (r, r1, sol); - m_stack--; - - if (res) - { // We succeeded in finding radii for this segment. If the stack is 0, this - // is the first recursion to this function. Thus there are no more ring segments - // to process and we can add the solution. - // - // If not, when this function ends, it will be called again with more arguments. - // Accept the solution to this segment by setting currentSolution to sol, and - // return true to continue processing. - if (m_stack == 0) - m_solutions.push_back (sol); - else - { currentSolution = sol; - return true; - } - } - } - - return false; - } - - return true; -} - -// ============================================================================= -// Main function. Call this with r0 and r1. If this returns true, use bestSolution -// for the solution that was presented. -// ----------------------------------------------------------------------------- -bool RingFinder::findRings (double r0, double r1) -{ m_solutions.clear(); - Solution sol; - - // Recurse in and try find solutions. - findRingsRecursor (r0, r1, sol); - - // Compare the solutions and find the best one. The solution class has an operator> - // overload to compare two solutions. - m_bestSolution = null; - - for (QVector<Solution>::iterator solp = m_solutions.begin(); solp != m_solutions.end(); ++solp) - { const Solution& sol = *solp; - - if (m_bestSolution == null || sol > *m_bestSolution) - m_bestSolution = / - } - - return (m_bestSolution != null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool RingFinder::Solution::operator> (const RingFinder::Solution& other) const -{ // If this solution has less components than the other one, this one - // is definitely better. - if (getComponents().size() < other.getComponents().size()) - return true; - - // vice versa - if (other.getComponents().size() < getComponents().size()) - return false; - - // Calculate the maximum ring number. Since the solutions have equal - // ring counts, the solutions with lesser maximum rings should result - // in cleaner code and less new primitives, right? - int maxA = 0, - maxB = 0; - - for (int i = 0; i < getComponents().size(); ++i) - { if (getComponents()[i].num > maxA) - maxA = getComponents()[i].num; - - if (other.getComponents()[i].num > maxB) - maxB = other.getComponents()[i].num; - } - - if (maxA < maxB) - return true; - - if (maxB < maxA) - return false; - - // Solutions have equal rings and equal maximum ring numbers. Let's - // just say this one is better, at this point it does not matter which - // one is chosen. - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void roundToDecimals (double& a, int decimals) -{ assert (decimals >= 0 && decimals < (signed) (sizeof g_e10 / sizeof *g_e10)); - a = round (a * g_e10[decimals]) / g_e10[decimals]; -} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/primitives.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,631 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QDir> +#include <QRegExp> +#include <QFileDialog> +#include "document.h" +#include "gui.h" +#include "primitives.h" +#include "ui_makeprim.h" +#include "misc.h" +#include "colors.h" +#include "moc_primitives.cpp" + +QList<PrimitiveCategory*> g_PrimitiveCategories; +QList<Primitive> g_primitives; +static PrimitiveLister* g_activePrimLister = null; +PrimitiveCategory* g_unmatched = null; + +extern_cfg (String, ld_defaultname); +extern_cfg (String, ld_defaultuser); +extern_cfg (Int, ld_defaultlicense); + +static const str g_radialNameRoots[] = +{ "edge", + "cyli", + "disc", + "ndis", + "ring", + "con" +}; + +PrimitiveLister* getPrimitiveLister() +{ return g_activePrimLister; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void loadPrimitives() +{ log ("Loading primitives...\n"); + PrimitiveCategory::loadCategories(); + + // Try to load prims.cfg + File conf (Config::filepath ("prims.cfg"), File::Read); + + if (!conf) + { // No prims.cfg, build it + PrimitiveLister::start(); + } + else + { // Read primitives from prims.cfg + for (str line : conf) + { int space = line.indexOf (" "); + + if (space == -1) + continue; + + Primitive info; + info.name = line.left (space); + info.title = line.mid (space + 1); + g_primitives << info; + } + + PrimitiveCategory::populateCategories(); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void recursiveGetFilenames (QDir dir, QList<str>& fnames) +{ QFileInfoList flist = dir.entryInfoList(); + + for (const QFileInfo & info : flist) + { if (info.fileName() == "." || info.fileName() == "..") + continue; // skip . and .. + + if (info.isDir()) + recursiveGetFilenames (QDir (info.absoluteFilePath()), fnames); + else + fnames << info.absoluteFilePath(); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PrimitiveLister::PrimitiveLister (QObject* parent) : + QObject (parent), + m_i (0) +{ g_activePrimLister = this; + QDir dir (LDPaths::prims()); + assert (dir.exists()); + m_baselen = dir.absolutePath().length(); + recursiveGetFilenames (dir, m_files); + emit starting (m_files.size()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PrimitiveLister::~PrimitiveLister() +{ g_activePrimLister = null; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PrimitiveLister::work() +{ int j = min (m_i + 300, m_files.size()); + log ("PrimitiveLister::work: %1 -> %2\n", m_i, j); + + for (; m_i < j; ++m_i) + { str fname = m_files[m_i]; + File f (fname, File::Read); + Primitive info; + info.name = fname.mid (m_baselen + 1); // make full path relative + info.name.replace ('/', '\\'); // use DOS backslashes, they're expected + info.cat = null; + + if (!f.readLine (info.title)) + info.title = ""; + + info.title = info.title.simplified(); + + if (info.title[0] == '0') + { info.title.remove (0, 1); // remove 0 + info.title = info.title.simplified(); + } + + m_prims << info; + } + + if (m_i == m_files.size()) + { // Done with primitives, now save to a config file + File conf (Config::filepath ("prims.cfg"), File::Write); + + for (Primitive& info : m_prims) + fprint (conf, "%1 %2\n", info.name, info.title); + + conf.close(); + + g_primitives = m_prims; + PrimitiveCategory::populateCategories(); + log ("%1 primitives listed", g_primitives.size()); + g_activePrimLister = null; + emit workDone(); + deleteLater(); + } + else + { // Defer to event loop, pick up the work later + emit update (m_i); + QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PrimitiveLister::start() +{ if (g_activePrimLister) + return; + + PrimitiveLister* lister = new PrimitiveLister; + /* + connect (lister, SIGNAL (starting (int)), g_win, SLOT (primitiveLoaderStart (int))); + connect (lister, SIGNAL (update (int)), g_win, SLOT (primitiveLoaderUpdate (int))); + connect (lister, SIGNAL (workDone()), g_win, SLOT (primitiveLoaderEnd())); + */ + lister->work(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PrimitiveCategory::PrimitiveCategory (str name, QObject* parent) : + QObject (parent), + m_Name (name) {} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PrimitiveCategory::populateCategories() +{ for (PrimitiveCategory* cat : g_PrimitiveCategories) + cat->prims.clear(); + + + for (Primitive& prim : g_primitives) + { bool matched = false; + prim.cat = null; + + // Go over the categories and their regexes, if and when there's a match, + // the primitive's category is set to the category the regex beloings to. + for (PrimitiveCategory* cat : g_PrimitiveCategories) + { for (RegexEntry& entry : cat->regexes) + { switch (entry.type) + { case EFilenameRegex: + { // f-regex, check against filename + matched = entry.regex.exactMatch (prim.name); + } break; + + case ETitleRegex: + { // t-regex, check against title + matched = entry.regex.exactMatch (prim.title); + } break; + } + + if (matched) + { prim.cat = cat; + break; + } + } + + // Drop out if a category was decided on. + if (prim.cat != null) + break; + } + + // If there was a match, add the primitive to the category. + // Otherwise, add it to the list of unmatched primitives. + if (prim.cat != null) + prim.cat->prims << prim; + else + g_unmatched->prims << prim; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PrimitiveCategory::loadCategories() +{ for (PrimitiveCategory* cat : g_PrimitiveCategories) + delete cat; + + g_PrimitiveCategories.clear(); + File f (Config::dirpath() + "primregexps.cfg", File::Read); + + if (!f) + f.open (":/data/primitive-categories.cfg", File::Read); + + if (!f) + { critical (QObject::tr ("Failed to open primitive categories!")); + return; + } + + if (f) + { PrimitiveCategory* cat = null; + + for (str line : f) + { int colon; + + if (line.length() == 0 || line[0] == '#') + continue; + + if ((colon = line.indexOf (":")) == -1) + { if (cat && cat->isValidToInclude()) + g_PrimitiveCategories << cat; + + cat = new PrimitiveCategory (line); + } + elif (cat != null) + { str cmd = line.left (colon); + ERegexType type = EFilenameRegex; + + if (cmd == "f") + type = EFilenameRegex; + elif (cmd == "t") + type = ETitleRegex; + else + { log (tr ("Warning: unknown command \"%1\" on line \"%2\""), cmd, line); + continue; + } + + QRegExp regex (line.mid (colon + 1)); + RegexEntry entry = { regex, type }; + cat->regexes << entry; + } + else + log ("Warning: Rules given before the first category name"); + } + + if (cat->isValidToInclude()) + g_PrimitiveCategories << cat; + } + + // Add a category for unmatched primitives. + // Note: if this function is called the second time, g_unmatched has been + // deleted at the beginning of the function and is dangling at this point. + g_unmatched = new PrimitiveCategory (tr ("Other")); + g_PrimitiveCategories << g_unmatched; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool PrimitiveCategory::isValidToInclude() +{ if (regexes.size() == 0) + { log (tr ("Warning: category \"%1\" left without patterns"), getName()); + deleteLater(); + return false; + } + + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool isPrimitiveLoaderBusy() +{ return g_activePrimLister != null; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static double radialPoint (int i, int divs, double (*func) (double)) +{ return (*func) ((i * 2 * pi) / divs); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines) +{ for (int i = 0; i < segs; ++i) + { double x0 = radius * radialPoint (i, divs, cos), + x1 = radius * radialPoint (i + 1, divs, cos), + z0 = radius * radialPoint (i, divs, sin), + z1 = radius * radialPoint (i + 1, divs, sin); + + lines << QLineF (QPointF (x0, z0), QPointF (x1, z1)); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QList<LDObject*> makePrimitive (PrimitiveType type, int segs, int divs, int num) +{ QList<LDObject*> objs; + QList<int> condLineSegs; + QList<QLineF> circle; + + makeCircle (segs, divs, 1, circle); + + for (int i = 0; i < segs; ++i) + { double x0 = circle[i].x1(), + x1 = circle[i].x2(), + z0 = circle[i].y1(), + z1 = circle[i].y2(); + + switch (type) + { case Circle: + { vertex v0 (x0, 0.0f, z0), + v1 (x1, 0.0f, z1); + + LDLine* line = new LDLine; + line->setVertex (0, v0); + line->setVertex (1, v1); + line->setColor (edgecolor); + objs << line; + } break; + + case Cylinder: + case Ring: + case Cone: + { double x2, x3, z2, z3; + double y0, y1, y2, y3; + + if (type == Cylinder) + { x2 = x1; + x3 = x0; + z2 = z1; + z3 = z0; + + y0 = y1 = 0.0f; + y2 = y3 = 1.0f; + } + else + { x2 = x1 * (num + 1); + x3 = x0 * (num + 1); + z2 = z1 * (num + 1); + z3 = z0 * (num + 1); + + x0 *= num; + x1 *= num; + z0 *= num; + z1 *= num; + + if (type == Ring) + y0 = y1 = y2 = y3 = 0.0f; + else + { y0 = y1 = 1.0f; + y2 = y3 = 0.0f; + } + } + + vertex v0 (x0, y0, z0), + v1 (x1, y1, z1), + v2 (x2, y2, z2), + v3 (x3, y3, z3); + + LDQuad* quad = new LDQuad; + quad->setColor (maincolor); + quad->setVertex (0, v0); + quad->setVertex (1, v1); + quad->setVertex (2, v2); + quad->setVertex (3, v3); + + if (type == Cylinder) + quad->invert(); + + objs << quad; + + if (type == Cylinder || type == Cone) + condLineSegs << i; + } break; + + case Disc: + case DiscNeg: + { double x2, z2; + + if (type == Disc) + x2 = z2 = 0.0f; + else + { x2 = (x0 >= 0.0f) ? 1.0f : -1.0f; + z2 = (z0 >= 0.0f) ? 1.0f : -1.0f; + } + + vertex v0 (x0, 0.0f, z0), + v1 (x1, 0.0f, z1), + v2 (x2, 0.0f, z2); + + // Disc negatives need to go the other way around, otherwise + // they'll end up upside-down. + LDTriangle* seg = new LDTriangle; + seg->setColor (maincolor); + seg->setVertex (type == Disc ? 0 : 2, v0); + seg->setVertex (1, v1); + seg->setVertex (type == Disc ? 2 : 0, v2); + objs << seg; + } break; + } + } + + // If this is not a full circle, we need a conditional line at the other + // end, too. + if (segs < divs && condLineSegs.size() != 0) + condLineSegs << segs; + + for (int i : condLineSegs) + { vertex v0 (radialPoint (i, divs, cos), 0.0f, radialPoint (i, divs, sin)), + v1, + v2 (radialPoint (i + 1, divs, cos), 0.0f, radialPoint (i + 1, divs, sin)), + v3 (radialPoint (i - 1, divs, cos), 0.0f, radialPoint (i - 1, divs, sin)); + + if (type == Cylinder) + v1 = vertex (v0[X], 1.0f, v0[Z]); + elif (type == Cone) + { v1 = vertex (v0[X] * (num + 1), 0.0f, v0[Z] * (num + 1)); + v0[X] *= num; + v0[Y] = 1.0f; + v0[Z] *= num; + } + + LDCondLine* line = new LDCondLine; + line->setColor (edgecolor); + line->setVertex (0, v0); + line->setVertex (1, v1); + line->setVertex (2, v2); + line->setVertex (3, v3); + objs << line; + } + + return objs; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static str primitiveTypeName (PrimitiveType type) +{ // Not translated as primitives are in English. + return type == Circle ? "Circle" : + type == Cylinder ? "Cylinder" : + type == Disc ? "Disc" : + type == DiscNeg ? "Disc Negative" : + type == Ring ? "Ring" : "Cone"; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str radialFileName (PrimitiveType type, int segs, int divs, int num) +{ int numer = segs, + denom = divs; + + // Simplify the fractional part, but the denominator must be at least 4. + simplify (numer, denom); + + if (denom < 4) + { const int factor = 4 / denom; + numer *= factor; + denom *= factor; + } + + // Compose some general information: prefix, fraction, root, ring number + str prefix = (divs == lores) ? "" : fmt ("%1/", divs); + str frac = fmt ("%1-%2", numer, denom); + str root = g_radialNameRoots[type]; + str numstr = (type == Ring || type == Cone) ? fmt ("%1", num) : ""; + + // Truncate the root if necessary (7-16rin4.dat for instance). + // However, always keep the root at least 2 characters. + int extra = (frac.length() + numstr.length() + root.length()) - 8; + root.chop (clamp (extra, 0, 2)); + + // Stick them all together and return the result. + return prefix + frac + root + numstr + ".dat"; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocument* generatePrimitive (PrimitiveType type, int segs, int divs, int num) +{ // Make the description + str frac = str::number ((float) segs / divs); + str name = radialFileName (type, segs, divs, num); + str descr; + + // Ensure that there's decimals, even if they're 0. + if (frac.indexOf (".") == -1) + frac += ".0"; + + if (type == Ring || type == Cone) + { str spacing = + (num < 10) ? " " : + (num < 100) ? " " : ""; + + descr = fmt ("%1 %2%3 x %4", primitiveTypeName (type), spacing, num, frac); + } + else + descr = fmt ("%1 %2", primitiveTypeName (type), frac); + + // Prepend "Hi-Res" if 48/ primitive. + if (divs == hires) + descr.insert (0, "Hi-Res "); + + LDDocument* f = new LDDocument; + f->setDefaultName (name); + + str author = APPNAME; + str license = ""; + + if (ld_defaultname.value.isEmpty() == false) + { license = getLicenseText (ld_defaultlicense); + author = fmt ("%1 [%2]", ld_defaultname, ld_defaultuser); + } + + f->addObjects ( + { new LDComment (descr), + new LDComment (fmt ("Name: %1", name)), + new LDComment (fmt ("Author: %1", author)), + new LDComment (fmt ("!LDRAW_ORG Unofficial_%1Primitive", divs == hires ? "48_" : "")), + new LDComment (license), + new LDEmpty, + new LDBFC (LDBFC::CertifyCCW), + new LDEmpty, + }); + + f->addObjects (makePrimitive (type, segs, divs, num)); + return f; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num) +{ str name = radialFileName (type, segs, divs, num); + LDDocument* f = getDocument (name); + + if (f != null) + return f; + + return generatePrimitive (type, segs, divs, num); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PrimitivePrompt::PrimitivePrompt (QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f) +{ ui = new Ui_MakePrimUI; + ui->setupUi (this); + connect (ui->cb_hires, SIGNAL (toggled (bool)), this, SLOT (hiResToggled (bool))); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PrimitivePrompt::~PrimitivePrompt() +{ delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PrimitivePrompt::hiResToggled (bool on) +{ ui->sb_segs->setMaximum (on ? hires : lores); + + // If the current value is 16 and we switch to hi-res, default the + // spinbox to 48. + if (on && ui->sb_segs->value() == lores) + ui->sb_segs->setValue (hires); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (MakePrimitive, 0) +{ PrimitivePrompt* dlg = new PrimitivePrompt (g_win); + + if (!dlg->exec()) + return; + + int segs = dlg->ui->sb_segs->value(); + int divs = dlg->ui->cb_hires->isChecked() ? hires : lores; + int num = dlg->ui->sb_ringnum->value(); + PrimitiveType type = + dlg->ui->rb_circle->isChecked() ? Circle : + dlg->ui->rb_cylinder->isChecked() ? Cylinder : + dlg->ui->rb_disc->isChecked() ? Disc : + dlg->ui->rb_ndisc->isChecked() ? DiscNeg : + dlg->ui->rb_ring->isChecked() ? Ring : Cone; + + LDDocument* f = generatePrimitive (type, segs, divs, num); + + g_win->save (f, false); + delete f; +}
--- a/src/primitives.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,631 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QDir> -#include <QRegExp> -#include <QFileDialog> -#include "document.h" -#include "gui.h" -#include "primitives.h" -#include "ui_makeprim.h" -#include "misc.h" -#include "colors.h" -#include "moc_primitives.cpp" - -QList<PrimitiveCategory*> g_PrimitiveCategories; -QList<Primitive> g_primitives; -static PrimitiveLister* g_activePrimLister = null; -PrimitiveCategory* g_unmatched = null; - -extern_cfg (String, ld_defaultname); -extern_cfg (String, ld_defaultuser); -extern_cfg (Int, ld_defaultlicense); - -static const str g_radialNameRoots[] = -{ "edge", - "cyli", - "disc", - "ndis", - "ring", - "con" -}; - -PrimitiveLister* getPrimitiveLister() -{ return g_activePrimLister; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void loadPrimitives() -{ log ("Loading primitives...\n"); - PrimitiveCategory::loadCategories(); - - // Try to load prims.cfg - File conf (Config::filepath ("prims.cfg"), File::Read); - - if (!conf) - { // No prims.cfg, build it - PrimitiveLister::start(); - } - else - { // Read primitives from prims.cfg - for (str line : conf) - { int space = line.indexOf (" "); - - if (space == -1) - continue; - - Primitive info; - info.name = line.left (space); - info.title = line.mid (space + 1); - g_primitives << info; - } - - PrimitiveCategory::populateCategories(); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void recursiveGetFilenames (QDir dir, QList<str>& fnames) -{ QFileInfoList flist = dir.entryInfoList(); - - for (const QFileInfo & info : flist) - { if (info.fileName() == "." || info.fileName() == "..") - continue; // skip . and .. - - if (info.isDir()) - recursiveGetFilenames (QDir (info.absoluteFilePath()), fnames); - else - fnames << info.absoluteFilePath(); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PrimitiveLister::PrimitiveLister (QObject* parent) : - QObject (parent), - m_i (0) -{ g_activePrimLister = this; - QDir dir (LDPaths::prims()); - assert (dir.exists()); - m_baselen = dir.absolutePath().length(); - recursiveGetFilenames (dir, m_files); - emit starting (m_files.size()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PrimitiveLister::~PrimitiveLister() -{ g_activePrimLister = null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PrimitiveLister::work() -{ int j = min (m_i + 300, m_files.size()); - log ("PrimitiveLister::work: %1 -> %2\n", m_i, j); - - for (; m_i < j; ++m_i) - { str fname = m_files[m_i]; - File f (fname, File::Read); - Primitive info; - info.name = fname.mid (m_baselen + 1); // make full path relative - info.name.replace ('/', '\\'); // use DOS backslashes, they're expected - info.cat = null; - - if (!f.readLine (info.title)) - info.title = ""; - - info.title = info.title.simplified(); - - if (info.title[0] == '0') - { info.title.remove (0, 1); // remove 0 - info.title = info.title.simplified(); - } - - m_prims << info; - } - - if (m_i == m_files.size()) - { // Done with primitives, now save to a config file - File conf (Config::filepath ("prims.cfg"), File::Write); - - for (Primitive& info : m_prims) - fprint (conf, "%1 %2\n", info.name, info.title); - - conf.close(); - - g_primitives = m_prims; - PrimitiveCategory::populateCategories(); - log ("%1 primitives listed", g_primitives.size()); - g_activePrimLister = null; - emit workDone(); - deleteLater(); - } - else - { // Defer to event loop, pick up the work later - emit update (m_i); - QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PrimitiveLister::start() -{ if (g_activePrimLister) - return; - - PrimitiveLister* lister = new PrimitiveLister; - /* - connect (lister, SIGNAL (starting (int)), g_win, SLOT (primitiveLoaderStart (int))); - connect (lister, SIGNAL (update (int)), g_win, SLOT (primitiveLoaderUpdate (int))); - connect (lister, SIGNAL (workDone()), g_win, SLOT (primitiveLoaderEnd())); - */ - lister->work(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PrimitiveCategory::PrimitiveCategory (str name, QObject* parent) : - QObject (parent), - m_Name (name) {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PrimitiveCategory::populateCategories() -{ for (PrimitiveCategory* cat : g_PrimitiveCategories) - cat->prims.clear(); - - - for (Primitive& prim : g_primitives) - { bool matched = false; - prim.cat = null; - - // Go over the categories and their regexes, if and when there's a match, - // the primitive's category is set to the category the regex beloings to. - for (PrimitiveCategory* cat : g_PrimitiveCategories) - { for (RegexEntry& entry : cat->regexes) - { switch (entry.type) - { case EFilenameRegex: - { // f-regex, check against filename - matched = entry.regex.exactMatch (prim.name); - } break; - - case ETitleRegex: - { // t-regex, check against title - matched = entry.regex.exactMatch (prim.title); - } break; - } - - if (matched) - { prim.cat = cat; - break; - } - } - - // Drop out if a category was decided on. - if (prim.cat != null) - break; - } - - // If there was a match, add the primitive to the category. - // Otherwise, add it to the list of unmatched primitives. - if (prim.cat != null) - prim.cat->prims << prim; - else - g_unmatched->prims << prim; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PrimitiveCategory::loadCategories() -{ for (PrimitiveCategory* cat : g_PrimitiveCategories) - delete cat; - - g_PrimitiveCategories.clear(); - File f (Config::dirpath() + "primregexps.cfg", File::Read); - - if (!f) - f.open (":/data/primitive-categories.cfg", File::Read); - - if (!f) - { critical (QObject::tr ("Failed to open primitive categories!")); - return; - } - - if (f) - { PrimitiveCategory* cat = null; - - for (str line : f) - { int colon; - - if (line.length() == 0 || line[0] == '#') - continue; - - if ((colon = line.indexOf (":")) == -1) - { if (cat && cat->isValidToInclude()) - g_PrimitiveCategories << cat; - - cat = new PrimitiveCategory (line); - } - elif (cat != null) - { str cmd = line.left (colon); - ERegexType type = EFilenameRegex; - - if (cmd == "f") - type = EFilenameRegex; - elif (cmd == "t") - type = ETitleRegex; - else - { log (tr ("Warning: unknown command \"%1\" on line \"%2\""), cmd, line); - continue; - } - - QRegExp regex (line.mid (colon + 1)); - RegexEntry entry = { regex, type }; - cat->regexes << entry; - } - else - log ("Warning: Rules given before the first category name"); - } - - if (cat->isValidToInclude()) - g_PrimitiveCategories << cat; - } - - // Add a category for unmatched primitives. - // Note: if this function is called the second time, g_unmatched has been - // deleted at the beginning of the function and is dangling at this point. - g_unmatched = new PrimitiveCategory (tr ("Other")); - g_PrimitiveCategories << g_unmatched; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool PrimitiveCategory::isValidToInclude() -{ if (regexes.size() == 0) - { log (tr ("Warning: category \"%1\" left without patterns"), getName()); - deleteLater(); - return false; - } - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool isPrimitiveLoaderBusy() -{ return g_activePrimLister != null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static double radialPoint (int i, int divs, double (*func) (double)) -{ return (*func) ((i * 2 * pi) / divs); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines) -{ for (int i = 0; i < segs; ++i) - { double x0 = radius * radialPoint (i, divs, cos), - x1 = radius * radialPoint (i + 1, divs, cos), - z0 = radius * radialPoint (i, divs, sin), - z1 = radius * radialPoint (i + 1, divs, sin); - - lines << QLineF (QPointF (x0, z0), QPointF (x1, z1)); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDObject*> makePrimitive (PrimitiveType type, int segs, int divs, int num) -{ QList<LDObject*> objs; - QList<int> condLineSegs; - QList<QLineF> circle; - - makeCircle (segs, divs, 1, circle); - - for (int i = 0; i < segs; ++i) - { double x0 = circle[i].x1(), - x1 = circle[i].x2(), - z0 = circle[i].y1(), - z1 = circle[i].y2(); - - switch (type) - { case Circle: - { vertex v0 (x0, 0.0f, z0), - v1 (x1, 0.0f, z1); - - LDLine* line = new LDLine; - line->setVertex (0, v0); - line->setVertex (1, v1); - line->setColor (edgecolor); - objs << line; - } break; - - case Cylinder: - case Ring: - case Cone: - { double x2, x3, z2, z3; - double y0, y1, y2, y3; - - if (type == Cylinder) - { x2 = x1; - x3 = x0; - z2 = z1; - z3 = z0; - - y0 = y1 = 0.0f; - y2 = y3 = 1.0f; - } - else - { x2 = x1 * (num + 1); - x3 = x0 * (num + 1); - z2 = z1 * (num + 1); - z3 = z0 * (num + 1); - - x0 *= num; - x1 *= num; - z0 *= num; - z1 *= num; - - if (type == Ring) - y0 = y1 = y2 = y3 = 0.0f; - else - { y0 = y1 = 1.0f; - y2 = y3 = 0.0f; - } - } - - vertex v0 (x0, y0, z0), - v1 (x1, y1, z1), - v2 (x2, y2, z2), - v3 (x3, y3, z3); - - LDQuad* quad = new LDQuad; - quad->setColor (maincolor); - quad->setVertex (0, v0); - quad->setVertex (1, v1); - quad->setVertex (2, v2); - quad->setVertex (3, v3); - - if (type == Cylinder) - quad->invert(); - - objs << quad; - - if (type == Cylinder || type == Cone) - condLineSegs << i; - } break; - - case Disc: - case DiscNeg: - { double x2, z2; - - if (type == Disc) - x2 = z2 = 0.0f; - else - { x2 = (x0 >= 0.0f) ? 1.0f : -1.0f; - z2 = (z0 >= 0.0f) ? 1.0f : -1.0f; - } - - vertex v0 (x0, 0.0f, z0), - v1 (x1, 0.0f, z1), - v2 (x2, 0.0f, z2); - - // Disc negatives need to go the other way around, otherwise - // they'll end up upside-down. - LDTriangle* seg = new LDTriangle; - seg->setColor (maincolor); - seg->setVertex (type == Disc ? 0 : 2, v0); - seg->setVertex (1, v1); - seg->setVertex (type == Disc ? 2 : 0, v2); - objs << seg; - } break; - } - } - - // If this is not a full circle, we need a conditional line at the other - // end, too. - if (segs < divs && condLineSegs.size() != 0) - condLineSegs << segs; - - for (int i : condLineSegs) - { vertex v0 (radialPoint (i, divs, cos), 0.0f, radialPoint (i, divs, sin)), - v1, - v2 (radialPoint (i + 1, divs, cos), 0.0f, radialPoint (i + 1, divs, sin)), - v3 (radialPoint (i - 1, divs, cos), 0.0f, radialPoint (i - 1, divs, sin)); - - if (type == Cylinder) - v1 = vertex (v0[X], 1.0f, v0[Z]); - elif (type == Cone) - { v1 = vertex (v0[X] * (num + 1), 0.0f, v0[Z] * (num + 1)); - v0[X] *= num; - v0[Y] = 1.0f; - v0[Z] *= num; - } - - LDCondLine* line = new LDCondLine; - line->setColor (edgecolor); - line->setVertex (0, v0); - line->setVertex (1, v1); - line->setVertex (2, v2); - line->setVertex (3, v3); - objs << line; - } - - return objs; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static str primitiveTypeName (PrimitiveType type) -{ // Not translated as primitives are in English. - return type == Circle ? "Circle" : - type == Cylinder ? "Cylinder" : - type == Disc ? "Disc" : - type == DiscNeg ? "Disc Negative" : - type == Ring ? "Ring" : "Cone"; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str radialFileName (PrimitiveType type, int segs, int divs, int num) -{ int numer = segs, - denom = divs; - - // Simplify the fractional part, but the denominator must be at least 4. - simplify (numer, denom); - - if (denom < 4) - { const int factor = 4 / denom; - numer *= factor; - denom *= factor; - } - - // Compose some general information: prefix, fraction, root, ring number - str prefix = (divs == lores) ? "" : fmt ("%1/", divs); - str frac = fmt ("%1-%2", numer, denom); - str root = g_radialNameRoots[type]; - str numstr = (type == Ring || type == Cone) ? fmt ("%1", num) : ""; - - // Truncate the root if necessary (7-16rin4.dat for instance). - // However, always keep the root at least 2 characters. - int extra = (frac.length() + numstr.length() + root.length()) - 8; - root.chop (clamp (extra, 0, 2)); - - // Stick them all together and return the result. - return prefix + frac + root + numstr + ".dat"; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument* generatePrimitive (PrimitiveType type, int segs, int divs, int num) -{ // Make the description - str frac = str::number ((float) segs / divs); - str name = radialFileName (type, segs, divs, num); - str descr; - - // Ensure that there's decimals, even if they're 0. - if (frac.indexOf (".") == -1) - frac += ".0"; - - if (type == Ring || type == Cone) - { str spacing = - (num < 10) ? " " : - (num < 100) ? " " : ""; - - descr = fmt ("%1 %2%3 x %4", primitiveTypeName (type), spacing, num, frac); - } - else - descr = fmt ("%1 %2", primitiveTypeName (type), frac); - - // Prepend "Hi-Res" if 48/ primitive. - if (divs == hires) - descr.insert (0, "Hi-Res "); - - LDDocument* f = new LDDocument; - f->setDefaultName (name); - - str author = APPNAME; - str license = ""; - - if (ld_defaultname.value.isEmpty() == false) - { license = getLicenseText (ld_defaultlicense); - author = fmt ("%1 [%2]", ld_defaultname, ld_defaultuser); - } - - f->addObjects ( - { new LDComment (descr), - new LDComment (fmt ("Name: %1", name)), - new LDComment (fmt ("Author: %1", author)), - new LDComment (fmt ("!LDRAW_ORG Unofficial_%1Primitive", divs == hires ? "48_" : "")), - new LDComment (license), - new LDEmpty, - new LDBFC (LDBFC::CertifyCCW), - new LDEmpty, - }); - - f->addObjects (makePrimitive (type, segs, divs, num)); - return f; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num) -{ str name = radialFileName (type, segs, divs, num); - LDDocument* f = getDocument (name); - - if (f != null) - return f; - - return generatePrimitive (type, segs, divs, num); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PrimitivePrompt::PrimitivePrompt (QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f) -{ ui = new Ui_MakePrimUI; - ui->setupUi (this); - connect (ui->cb_hires, SIGNAL (toggled (bool)), this, SLOT (hiResToggled (bool))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PrimitivePrompt::~PrimitivePrompt() -{ delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PrimitivePrompt::hiResToggled (bool on) -{ ui->sb_segs->setMaximum (on ? hires : lores); - - // If the current value is 16 and we switch to hi-res, default the - // spinbox to 48. - if (on && ui->sb_segs->value() == lores) - ui->sb_segs->setValue (hires); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (MakePrimitive, 0) -{ PrimitivePrompt* dlg = new PrimitivePrompt (g_win); - - if (!dlg->exec()) - return; - - int segs = dlg->ui->sb_segs->value(); - int divs = dlg->ui->cb_hires->isChecked() ? hires : lores; - int num = dlg->ui->sb_ringnum->value(); - PrimitiveType type = - dlg->ui->rb_circle->isChecked() ? Circle : - dlg->ui->rb_cylinder->isChecked() ? Cylinder : - dlg->ui->rb_disc->isChecked() ? Disc : - dlg->ui->rb_ndisc->isChecked() ? DiscNeg : - dlg->ui->rb_ring->isChecked() ? Ring : Cone; - - LDDocument* f = generatePrimitive (type, segs, divs, num); - - g_win->save (f, false); - delete f; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/types.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,613 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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 <QObject> +#include <QStringList> +#include <QTextStream> +#include <QFile> +#include <assert.h> +#include "main.h" +#include "types.h" +#include "misc.h" +#include "ldtypes.h" +#include "document.h" + +// ============================================================================= +// ----------------------------------------------------------------------------- +str DoFormat (QList<StringFormatArg> args) +{ assert (args.size() >= 1); + str text = args[0].value(); + + for (uchar i = 1; i < args.size(); ++i) + text = text.arg (args[i].value()); + + return text; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +vertex::vertex (double x, double y, double z) +{ m_coords[X] = x; + m_coords[Y] = y; + m_coords[Z] = z; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void vertex::move (const vertex& other) +{ for_axes (ax) + m_coords[ax] += other[ax]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +vertex vertex::midpoint (const vertex& other) +{ vertex mid; + + for_axes (ax) + mid[ax] = (m_coords[ax] + other[ax]) / 2; + + return mid; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str vertex::stringRep (bool mangled) const +{ str fmtstr = "%1 %2 %3"; + + if (mangled) + fmtstr = "(%1, %2, %3)"; + + return fmt (fmtstr, coord (X), coord (Y), coord (Z)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void vertex::transform (matrix matr, vertex pos) +{ double x2 = (matr[0] * x()) + (matr[1] * y()) + (matr[2] * z()) + pos[X]; + double y2 = (matr[3] * x()) + (matr[4] * y()) + (matr[5] * z()) + pos[Y]; + double z2 = (matr[6] * x()) + (matr[7] * y()) + (matr[8] * z()) + pos[Z]; + + x() = x2; + y() = y2; + z() = z2; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +vertex vertex::operator-() const +{ return vertex (-m_coords[X], -m_coords[Y], -m_coords[Z]); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool vertex::operator!= (const vertex& other) const +{ return !operator== (other); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +double& vertex::operator[] (const Axis ax) +{ return coord ( (int) ax); +} + +const double& vertex::operator[] (const Axis ax) const +{ return coord ( (int) ax); +} + +double& vertex::operator[] (const int ax) +{ return coord (ax); +} + +const double& vertex::operator[] (const int ax) const +{ return coord (ax); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool vertex::operator== (const vertex& other) const +{ return coord (X) == other[X] && + coord (Y) == other[Y] && + coord (Z) == other[Z]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +vertex& vertex::operator/= (const double d) +{ for_axes (ax) + m_coords[ax] /= d; + + return *this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +vertex vertex::operator/ (const double d) const +{ vertex other (*this); + return other /= d; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +vertex& vertex::operator+= (const vertex& other) +{ move (other); + return *this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +vertex vertex::operator+ (const vertex& other) const +{ vertex newvert (*this); + newvert.move (other); + return newvert; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int vertex::operator< (const vertex& other) const +{ if (operator== (other)) + return false; + + if (coord (X) < other[X]) + return true; + + if (coord (X) > other[X]) + return false; + + if (coord (Y) < other[Y]) + return true; + + if (coord (Y) > other[Y]) + return false; + + return coord (Z) < other[Z]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +matrix::matrix (double vals[]) +{ for (int i = 0; i < 9; ++i) + m_vals[i] = vals[i]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +matrix::matrix (double fillval) +{ for (int i = 0; i < 9; ++i) + m_vals[i] = fillval; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +matrix::matrix (initlist<double> vals) +{ assert (vals.size() == 9); + memcpy (&m_vals[0], & (*vals.begin()), sizeof m_vals); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void matrix::puts() const +{ for (int i = 0; i < 3; ++i) + { for (int j = 0; j < 3; ++j) + log ("%1\t", m_vals[ (i * 3) + j]); + + log ("\n"); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str matrix::stringRep() const +{ str val; + + for (int i = 0; i < 9; ++i) + { if (i > 0) + val += ' '; + + val += str::number (m_vals[i]); + } + + return val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void matrix::zero() +{ memset (&m_vals[0], 0, sizeof m_vals); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +matrix matrix::mult (matrix other) const +{ matrix val; + val.zero(); + + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + for (int k = 0; k < 3; ++k) + val[(i * 3) + j] += m_vals[(i * 3) + k] * other[(k * 3) + j]; + + return val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +matrix& matrix::operator= (matrix other) +{ memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals); + return *this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +double matrix::getDeterminant() const +{ return (val (0) * val (4) * val (8)) + + (val (1) * val (5) * val (6)) + + (val (2) * val (3) * val (7)) - + (val (2) * val (4) * val (6)) - + (val (1) * val (3) * val (8)) - + (val (0) * val (5) * val (7)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool matrix::operator== (const matrix& other) const +{ for (int i = 0; i < 9; ++i) + if (val (i) != other[i]) + return false; + + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +StringFormatArg::StringFormatArg (const str& v) +{ m_val = v; +} + +StringFormatArg::StringFormatArg (const char& v) +{ m_val = v; +} + +StringFormatArg::StringFormatArg (const uchar& v) +{ m_val = v; +} + +StringFormatArg::StringFormatArg (const QChar& v) +{ m_val = v; +} + +StringFormatArg::StringFormatArg (const float& v) +{ m_val = str::number (v); +} + +StringFormatArg::StringFormatArg (const double& v) +{ m_val = str::number (v); +} + +StringFormatArg::StringFormatArg (const vertex& v) +{ m_val = v.stringRep (false); +} + +StringFormatArg::StringFormatArg (const matrix& v) +{ m_val = v.stringRep(); +} + +StringFormatArg::StringFormatArg (const char* v) +{ m_val = v; +} + +StringFormatArg::StringFormatArg (const StringConfig& v) +{ m_val = v.value; +} + +StringFormatArg::StringFormatArg (const IntConfig& v) +{ m_val.number (v.value); +} + +StringFormatArg::StringFormatArg (const FloatConfig& v) +{ m_val.number (v.value); +} + +StringFormatArg::StringFormatArg (const void* v) +{ m_val.sprintf ("%p", v); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +File::File() +{ // Make a null file + m_file = null; + m_textstream = null; +} + +File::File (str path, OpenType rtype) +{ m_file = null; + open (path, rtype); +} + +File::File (FILE* fp, OpenType rtype) +{ m_file = null; + open (fp, rtype); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +File::~File() +{ if (m_file) + { m_file->close(); + delete m_file; + + if (m_textstream) + delete m_textstream; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool File::open (FILE* fp, OpenType rtype) +{ return open ("", rtype, fp); +} + +bool File::open (str path, OpenType rtype, FILE* fp) +{ close(); + + if (!m_file) + m_file = new QFile; + + m_file->setFileName (path); + + bool result; + + QIODevice::OpenMode mode = + (rtype == Read) ? QIODevice::ReadOnly : + (rtype == Write) ? QIODevice::WriteOnly : QIODevice::Append; + + if (fp) + result = m_file->open (fp, mode); + else + result = m_file->open (mode); + + if (result) + { m_textstream = new QTextStream (m_file); + return true; + } + + delete m_file; + m_file = null; + return false; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +File::iterator File::begin() +{ return iterator (this); +} + +File::iterator& File::end() +{ return m_endIterator; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void File::write (str msg) +{ m_file->write (msg.toUtf8(), msg.length()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool File::readLine (str& line) +{ if (!m_textstream || m_textstream->atEnd()) + return false; + + line = m_textstream->readLine(); + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool File::atEnd() const +{ assert (m_textstream != null); + return m_textstream->atEnd(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool File::isNull() const +{ return m_file == null; +} + +bool File::operator!() const +{ return isNull(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void File::close() +{ if (!m_file) + return; + + delete m_file; + m_file = null; + + if (m_textstream) + { delete m_textstream; + m_textstream = null; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool File::flush() +{ return m_file->flush(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +File::operator bool() const +{ return !isNull(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void File::rewind() +{ m_file->seek (0); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +File::iterator::iterator (File* f) : m_file (f) +{ operator++(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void File::iterator::operator++() +{ m_gotdata = m_file->readLine (m_text); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +str File::iterator::operator*() +{ return m_text; +} + +// ============================================================================= +// The prime contestant for the weirdest operator== 2013 award? +// ----------------------------------------------------------------------------- +bool File::iterator::operator== (File::iterator& other) +{ return (other.m_file == null && !m_gotdata); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool File::iterator::operator!= (File::iterator& other) +{ return !operator== (other); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDBoundingBox::LDBoundingBox() +{ reset(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDBoundingBox::calculate() +{ reset(); + + if (!getCurrentDocument()) + return; + + for (LDObject* obj : getCurrentDocument()->getObjects()) + calcObject (obj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDBoundingBox::calcObject (LDObject* obj) +{ switch (obj->getType()) + { case LDObject::Line: + case LDObject::Triangle: + case LDObject::Quad: + case LDObject::CondLine: + { for (int i = 0; i < obj->vertices(); ++i) + calcVertex (obj->getVertex (i)); + } break; + + case LDObject::Subfile: + { LDSubfile* ref = static_cast<LDSubfile*> (obj); + QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepCacheInline); + + for (LDObject * obj : objs) + { calcObject (obj); + delete obj; + } + } + break; + + default: + break; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDBoundingBox& LDBoundingBox::operator<< (const vertex& v) +{ calcVertex (v); + return *this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDBoundingBox& LDBoundingBox::operator<< (LDObject* obj) +{ calcObject (obj); + return *this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDBoundingBox::calcVertex (const vertex& v) +{ for_axes (ax) + { if (v[ax] < m_Vertex0[ax]) + m_Vertex0[ax] = v[ax]; + + if (v[ax] > m_Vertex1[ax]) + m_Vertex1[ax] = v[ax]; + } + + setEmpty (false); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDBoundingBox::reset() +{ m_Vertex0[X] = m_Vertex0[Y] = m_Vertex0[Z] = 0x7FFFFFFF; + m_Vertex1[X] = m_Vertex1[Y] = m_Vertex1[Z] = 0xFFFFFFFF; + + setEmpty (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +double LDBoundingBox::size() const +{ double xscale = (m_Vertex0[X] - m_Vertex1[X]); + double yscale = (m_Vertex0[Y] - m_Vertex1[Y]); + double zscale = (m_Vertex0[Z] - m_Vertex1[Z]); + double size = zscale; + + if (xscale > yscale) + { if (xscale > zscale) + size = xscale; + } + elif (yscale > zscale) + size = yscale; + + if (abs (size) >= 2.0f) + return abs (size / 2); + + return 1.0f; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +vertex LDBoundingBox::center() const +{ return vertex ( + (m_Vertex0[X] + m_Vertex1[X]) / 2, + (m_Vertex0[Y] + m_Vertex1[Y]) / 2, + (m_Vertex0[Z] + m_Vertex1[Z]) / 2); +}
--- a/src/types.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,613 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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 <QObject> -#include <QStringList> -#include <QTextStream> -#include <QFile> -#include <assert.h> -#include "main.h" -#include "types.h" -#include "misc.h" -#include "ldtypes.h" -#include "document.h" - -// ============================================================================= -// ----------------------------------------------------------------------------- -str DoFormat (QList<StringFormatArg> args) -{ assert (args.size() >= 1); - str text = args[0].value(); - - for (uchar i = 1; i < args.size(); ++i) - text = text.arg (args[i].value()); - - return text; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex::vertex (double x, double y, double z) -{ m_coords[X] = x; - m_coords[Y] = y; - m_coords[Z] = z; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void vertex::move (const vertex& other) -{ for (const Axis ax : g_Axes) - m_coords[ax] += other[ax]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex vertex::midpoint (const vertex& other) -{ vertex mid; - - for (const Axis ax : g_Axes) - mid[ax] = (m_coords[ax] + other[ax]) / 2; - - return mid; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str vertex::stringRep (bool mangled) const -{ str fmtstr = "%1 %2 %3"; - - if (mangled) - fmtstr = "(%1, %2, %3)"; - - return fmt (fmtstr, coord (X), coord (Y), coord (Z)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void vertex::transform (matrix matr, vertex pos) -{ double x2 = (matr[0] * x()) + (matr[1] * y()) + (matr[2] * z()) + pos[X]; - double y2 = (matr[3] * x()) + (matr[4] * y()) + (matr[5] * z()) + pos[Y]; - double z2 = (matr[6] * x()) + (matr[7] * y()) + (matr[8] * z()) + pos[Z]; - - x() = x2; - y() = y2; - z() = z2; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex vertex::operator-() const -{ return vertex (-m_coords[X], -m_coords[Y], -m_coords[Z]); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool vertex::operator!= (const vertex& other) const -{ return !operator== (other); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -double& vertex::operator[] (const Axis ax) -{ return coord ( (int) ax); -} - -const double& vertex::operator[] (const Axis ax) const -{ return coord ( (int) ax); -} - -double& vertex::operator[] (const int ax) -{ return coord (ax); -} - -const double& vertex::operator[] (const int ax) const -{ return coord (ax); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool vertex::operator== (const vertex& other) const -{ return coord (X) == other[X] && - coord (Y) == other[Y] && - coord (Z) == other[Z]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex& vertex::operator/= (const double d) -{ for (const Axis ax : g_Axes) - m_coords[ax] /= d; - - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex vertex::operator/ (const double d) const -{ vertex other (*this); - return other /= d; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex& vertex::operator+= (const vertex& other) -{ move (other); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex vertex::operator+ (const vertex& other) const -{ vertex newvert (*this); - newvert.move (other); - return newvert; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int vertex::operator< (const vertex& other) const -{ if (operator== (other)) - return false; - - if (coord (X) < other[X]) - return true; - - if (coord (X) > other[X]) - return false; - - if (coord (Y) < other[Y]) - return true; - - if (coord (Y) > other[Y]) - return false; - - return coord (Z) < other[Z]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -matrix::matrix (double vals[]) -{ for (int i = 0; i < 9; ++i) - m_vals[i] = vals[i]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -matrix::matrix (double fillval) -{ for (int i = 0; i < 9; ++i) - m_vals[i] = fillval; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -matrix::matrix (initlist<double> vals) -{ assert (vals.size() == 9); - memcpy (&m_vals[0], & (*vals.begin()), sizeof m_vals); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void matrix::puts() const -{ for (int i = 0; i < 3; ++i) - { for (int j = 0; j < 3; ++j) - log ("%1\t", m_vals[ (i * 3) + j]); - - log ("\n"); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str matrix::stringRep() const -{ str val; - - for (int i = 0; i < 9; ++i) - { if (i > 0) - val += ' '; - - val += str::number (m_vals[i]); - } - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void matrix::zero() -{ memset (&m_vals[0], 0, sizeof m_vals); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -matrix matrix::mult (matrix other) const -{ matrix val; - val.zero(); - - for (int i = 0; i < 3; ++i) - for (int j = 0; j < 3; ++j) - for (int k = 0; k < 3; ++k) - val[(i * 3) + j] += m_vals[(i * 3) + k] * other[(k * 3) + j]; - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -matrix& matrix::operator= (matrix other) -{ memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -double matrix::getDeterminant() const -{ return (val (0) * val (4) * val (8)) + - (val (1) * val (5) * val (6)) + - (val (2) * val (3) * val (7)) - - (val (2) * val (4) * val (6)) - - (val (1) * val (3) * val (8)) - - (val (0) * val (5) * val (7)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool matrix::operator== (const matrix& other) const -{ for (int i = 0; i < 9; ++i) - if (val (i) != other[i]) - return false; - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -StringFormatArg::StringFormatArg (const str& v) -{ m_val = v; -} - -StringFormatArg::StringFormatArg (const char& v) -{ m_val = v; -} - -StringFormatArg::StringFormatArg (const uchar& v) -{ m_val = v; -} - -StringFormatArg::StringFormatArg (const QChar& v) -{ m_val = v; -} - -StringFormatArg::StringFormatArg (const float& v) -{ m_val = str::number (v); -} - -StringFormatArg::StringFormatArg (const double& v) -{ m_val = str::number (v); -} - -StringFormatArg::StringFormatArg (const vertex& v) -{ m_val = v.stringRep (false); -} - -StringFormatArg::StringFormatArg (const matrix& v) -{ m_val = v.stringRep(); -} - -StringFormatArg::StringFormatArg (const char* v) -{ m_val = v; -} - -StringFormatArg::StringFormatArg (const StringConfig& v) -{ m_val = v.value; -} - -StringFormatArg::StringFormatArg (const IntConfig& v) -{ m_val.number (v.value); -} - -StringFormatArg::StringFormatArg (const FloatConfig& v) -{ m_val.number (v.value); -} - -StringFormatArg::StringFormatArg (const void* v) -{ m_val.sprintf ("%p", v); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File::File() -{ // Make a null file - m_file = null; - m_textstream = null; -} - -File::File (str path, OpenType rtype) -{ m_file = null; - open (path, rtype); -} - -File::File (FILE* fp, OpenType rtype) -{ m_file = null; - open (fp, rtype); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File::~File() -{ if (m_file) - { m_file->close(); - delete m_file; - - if (m_textstream) - delete m_textstream; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool File::open (FILE* fp, OpenType rtype) -{ return open ("", rtype, fp); -} - -bool File::open (str path, OpenType rtype, FILE* fp) -{ close(); - - if (!m_file) - m_file = new QFile; - - m_file->setFileName (path); - - bool result; - - QIODevice::OpenMode mode = - (rtype == Read) ? QIODevice::ReadOnly : - (rtype == Write) ? QIODevice::WriteOnly : QIODevice::Append; - - if (fp) - result = m_file->open (fp, mode); - else - result = m_file->open (mode); - - if (result) - { m_textstream = new QTextStream (m_file); - return true; - } - - delete m_file; - m_file = null; - return false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File::iterator File::begin() -{ return iterator (this); -} - -File::iterator& File::end() -{ return m_endIterator; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void File::write (str msg) -{ m_file->write (msg.toUtf8(), msg.length()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool File::readLine (str& line) -{ if (!m_textstream || m_textstream->atEnd()) - return false; - - line = m_textstream->readLine(); - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool File::atEnd() const -{ assert (m_textstream != null); - return m_textstream->atEnd(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool File::isNull() const -{ return m_file == null; -} - -bool File::operator!() const -{ return isNull(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void File::close() -{ if (!m_file) - return; - - delete m_file; - m_file = null; - - if (m_textstream) - { delete m_textstream; - m_textstream = null; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool File::flush() -{ return m_file->flush(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File::operator bool() const -{ return !isNull(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void File::rewind() -{ m_file->seek (0); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File::iterator::iterator (File* f) : m_file (f) -{ operator++(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void File::iterator::operator++() -{ m_gotdata = m_file->readLine (m_text); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str File::iterator::operator*() -{ return m_text; -} - -// ============================================================================= -// The prime contestant for the weirdest operator== 2013 award? -// ----------------------------------------------------------------------------- -bool File::iterator::operator== (File::iterator& other) -{ return (other.m_file == null && !m_gotdata); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool File::iterator::operator!= (File::iterator& other) -{ return !operator== (other); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDBoundingBox::LDBoundingBox() -{ reset(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDBoundingBox::calculate() -{ reset(); - - if (!getCurrentDocument()) - return; - - for (LDObject* obj : getCurrentDocument()->getObjects()) - calcObject (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDBoundingBox::calcObject (LDObject* obj) -{ switch (obj->getType()) - { case LDObject::Line: - case LDObject::Triangle: - case LDObject::Quad: - case LDObject::CondLine: - { for (int i = 0; i < obj->vertices(); ++i) - calcVertex (obj->getVertex (i)); - } break; - - case LDObject::Subfile: - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepCacheInline); - - for (LDObject * obj : objs) - { calcObject (obj); - delete obj; - } - } - break; - - default: - break; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDBoundingBox& LDBoundingBox::operator<< (const vertex& v) -{ calcVertex (v); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDBoundingBox& LDBoundingBox::operator<< (LDObject* obj) -{ calcObject (obj); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDBoundingBox::calcVertex (const vertex& v) -{ for (const Axis ax : g_Axes) - { if (v[ax] < m_Vertex0[ax]) - m_Vertex0[ax] = v[ax]; - - if (v[ax] > m_Vertex1[ax]) - m_Vertex1[ax] = v[ax]; - } - - setEmpty (false); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDBoundingBox::reset() -{ m_Vertex0[X] = m_Vertex0[Y] = m_Vertex0[Z] = 0x7FFFFFFF; - m_Vertex1[X] = m_Vertex1[Y] = m_Vertex1[Z] = 0xFFFFFFFF; - - setEmpty (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -double LDBoundingBox::size() const -{ double xscale = (m_Vertex0[X] - m_Vertex1[X]); - double yscale = (m_Vertex0[Y] - m_Vertex1[Y]); - double zscale = (m_Vertex0[Z] - m_Vertex1[Z]); - double size = zscale; - - if (xscale > yscale) - { if (xscale > zscale) - size = xscale; - } elif (yscale > zscale) - - size = yscale; - - if (abs (size) >= 2.0f) - return abs (size / 2); - - return 1.0f; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex LDBoundingBox::center() const -{ return vertex ( - (m_Vertex0[X] + m_Vertex1[X]) / 2, - (m_Vertex0[Y] + m_Vertex1[Y]) / 2, - (m_Vertex0[Z] + m_Vertex1[Z]) / 2); -}
--- a/src/types.h Fri Dec 13 00:39:49 2013 +0200 +++ b/src/types.h Fri Dec 13 20:01:49 2013 +0200 @@ -49,8 +49,11 @@ template<class T, class R> using pair = std::pair<T, R>; -enum Axis { X, Y, Z }; -static const Axis g_Axes[3] = { X, Y, Z }; +enum Axis +{ X, + Y, + Z +}; // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets.cc Fri Dec 13 20:01:49 2013 +0200 @@ -0,0 +1,177 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri 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/>. + */ + +// I still find the radio group useful... find a way to use this in Designer. +// I probably need to look into how to make Designer plugins. +// TODO: try make this usable in Designer + +#include <QBoxLayout> +#include <QRadioButton> +#include <QButtonGroup> +#include <QCheckBox> +#include <map> + +#include "widgets.h" +#include "moc_widgets.cpp" + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup::RadioGroup (const QString& title, QWidget* parent) : QGroupBox (title, parent) +{ init (Qt::Vertical); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false) +{ return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool RadioGroup::isChecked (int n) const +{ return m_buttonGroup->checkedId() == n; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::init (Qt::Orientation orient) +{ m_vert = orient == Qt::Vertical; + + m_buttonGroup = new QButtonGroup; + m_oldId = m_curId = 0; + m_coreLayout = null; + + m_coreLayout = new QBoxLayout ( (orient == Qt::Vertical) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom); + setLayout (m_coreLayout); + + // Init the first row with a break + rowBreak(); + + connect (m_buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int))); + connect (m_buttonGroup, SIGNAL (buttonReleased (int)), this, SLOT (slot_buttonReleased (int))); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup::RadioGroup (const QString& title, initlist<char const*> entries, int const defaultId, const Qt::Orientation orient, QWidget* parent) : + QGroupBox (title, parent), + m_defId (defaultId) +{ init (orient); + m_oldId = m_defId; + + for (const char* entry : entries) + addButton (entry); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::rowBreak() +{ QBoxLayout* newLayout = new QBoxLayout (m_vert ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); + m_currentLayout = newLayout; + m_layouts << newLayout; + + m_coreLayout->addLayout (newLayout); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::addButton (const char* entry) +{ QRadioButton* button = new QRadioButton (entry); + addButton (button); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::addButton (QRadioButton* button) +{ bool const selectThis = (m_curId == m_defId); + + m_objects << button; + m_buttonGroup->addButton (button, m_curId++); + m_currentLayout->addWidget (button); + + if (selectThis) + button->setChecked (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup& RadioGroup::operator<< (QRadioButton* button) +{ addButton (button); + return *this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup& RadioGroup::operator<< (const char* entry) +{ addButton (entry); + return *this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::setCurrentRow (int row) +{ m_currentLayout = m_layouts[row]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int RadioGroup::value() const +{ return m_buttonGroup->checkedId(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::setValue (int val) +{ m_buttonGroup->button (val)->setChecked (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QRadioButton* RadioGroup::operator[] (int n) const +{ return m_objects[n]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::slot_buttonPressed (int btn) +{ emit buttonPressed (btn); + + m_oldId = m_buttonGroup->checkedId(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::slot_buttonReleased (int btn) +{ emit buttonReleased (btn); + int newid = m_buttonGroup->checkedId(); + + if (m_oldId != newid) + emit valueChanged (newid); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup::Iterator RadioGroup::begin() +{ return m_objects.begin(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup::Iterator RadioGroup::end() +{ return m_objects.end(); +}
--- a/src/widgets.cpp Fri Dec 13 00:39:49 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,177 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri 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/>. - */ - -// I still find the radio group useful... find a way to use this in Designer. -// I probably need to look into how to make Designer plugins. -// TODO: try make this usable in Designer - -#include <QBoxLayout> -#include <QRadioButton> -#include <QButtonGroup> -#include <QCheckBox> -#include <map> - -#include "widgets.h" -#include "moc_widgets.cpp" - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup::RadioGroup (const QString& title, QWidget* parent) : QGroupBox (title, parent) -{ init (Qt::Vertical); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false) -{ return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool RadioGroup::isChecked (int n) const -{ return m_buttonGroup->checkedId() == n; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::init (Qt::Orientation orient) -{ m_vert = orient == Qt::Vertical; - - m_buttonGroup = new QButtonGroup; - m_oldId = m_curId = 0; - m_coreLayout = null; - - m_coreLayout = new QBoxLayout ( (orient == Qt::Vertical) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom); - setLayout (m_coreLayout); - - // Init the first row with a break - rowBreak(); - - connect (m_buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int))); - connect (m_buttonGroup, SIGNAL (buttonReleased (int)), this, SLOT (slot_buttonReleased (int))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup::RadioGroup (const QString& title, initlist<char const*> entries, int const defaultId, const Qt::Orientation orient, QWidget* parent) : - QGroupBox (title, parent), - m_defId (defaultId) -{ init (orient); - m_oldId = m_defId; - - for (const char* entry : entries) - addButton (entry); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::rowBreak() -{ QBoxLayout* newLayout = new QBoxLayout (m_vert ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); - m_currentLayout = newLayout; - m_layouts << newLayout; - - m_coreLayout->addLayout (newLayout); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::addButton (const char* entry) -{ QRadioButton* button = new QRadioButton (entry); - addButton (button); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::addButton (QRadioButton* button) -{ bool const selectThis = (m_curId == m_defId); - - m_objects << button; - m_buttonGroup->addButton (button, m_curId++); - m_currentLayout->addWidget (button); - - if (selectThis) - button->setChecked (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup& RadioGroup::operator<< (QRadioButton* button) -{ addButton (button); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup& RadioGroup::operator<< (const char* entry) -{ addButton (entry); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::setCurrentRow (int row) -{ m_currentLayout = m_layouts[row]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int RadioGroup::value() const -{ return m_buttonGroup->checkedId(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::setValue (int val) -{ m_buttonGroup->button (val)->setChecked (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QRadioButton* RadioGroup::operator[] (int n) const -{ return m_objects[n]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::slot_buttonPressed (int btn) -{ emit buttonPressed (btn); - - m_oldId = m_buttonGroup->checkedId(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::slot_buttonReleased (int btn) -{ emit buttonReleased (btn); - int newid = m_buttonGroup->checkedId(); - - if (m_oldId != newid) - emit valueChanged (newid); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup::Iterator RadioGroup::begin() -{ return m_objects.begin(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup::Iterator RadioGroup::end() -{ return m_objects.end(); -}