Sat, 29 Mar 2014 05:26:10 +0200
- renamed files to camelCase
--- a/CMakeLists.txt Wed Mar 12 16:20:40 2014 +0200 +++ b/CMakeLists.txt Sat Mar 29 05:26:10 2014 +0200 @@ -12,67 +12,67 @@ get_target_property (UPDATEREVISION_EXE updaterevision LOCATION) add_custom_target (revision_check ALL - COMMAND ${UPDATEREVISION_EXE} src/Git.h + COMMAND ${UPDATEREVISION_EXE} src/gitinfo.h WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} DEPENDS updaterevision) include_directories (${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR}) set (LDForgeSources - src/AddObjectDialog.cc - src/Colors.cc - src/ColorSelector.cc - src/Configuration.cc - src/ConfigurationDialog.cc - src/CrashCatcher.cc - src/Dialogs.cc - src/Documentation.cc - src/Document.cc - src/EditHistory.cc - src/ExternalPrograms.cc - src/GLRenderer.cc - src/LDConfig.cc - src/LDObject.cc - src/Main.cc - src/MainWindow.cc - src/MessageLog.cc - src/Misc.cc - src/PartDownloader.cc - src/Primitives.cc - src/Types.cc - src/Version.cc - src/Widgets.cc - src/actions/MainActions.cc - src/actions/EditActions.cc + src/actions.cc + src/actionsEdit.cc + src/addObjectDialog.cc + src/basics.cc + src/colors.cc + src/colorSelector.cc + src/configuration.cc + src/configDialog.cc + src/crashCatcher.cc + src/dialogs.cc + src/documentation.cc + src/editHistory.cc + src/extPrograms.cc + src/glRenderer.cc + src/ldConfig.cc + src/ldDocument.cc + src/ldObject.cc + src/main.cc + src/mainWindow.cc + src/messageLog.cc + src/miscallenous.cc + src/partDownloader.cc + src/primitives.cc + src/radioGroup.cc + src/version.cc ) set (LDForgeHeaders - src/Macros.h - src/CrashCatcher.h - src/Colors.h - src/misc/DocumentPointer.h - src/misc/InvokationDeferer.h - src/misc/RingFinder.h - src/Document.h - src/AddObjectDialog.h - src/LDConfig.h - src/PartDownloader.h - src/LDObject.h - src/Primitives.h - src/Misc.h - src/MessageLog.h - src/Dialogs.h - src/Widgets.h - src/Documentation.h - src/Main.h - src/Types.h - src/ColorSelector.h - src/ConfigurationDialog.h - src/GLRenderer.h - src/Configuration.h - src/MainWindow.h - src/EditHistory.h - src/Format.h + src/macros.h + src/crashCatcher.h + src/colors.h + src/misc/documentPointer.h + src/misc/invokeLater.h + src/misc/ringFinder.h + src/ldDocument.h + src/addObjectDialog.h + src/ldConfig.h + src/partDownloader.h + src/ldObject.h + src/primitives.h + src/miscallenous.h + src/messageLog.h + src/dialogs.h + src/radioGroup.h + src/documentation.h + src/main.h + src/basics.h + src/colorSelector.h + src/configDialog.h + src/glRenderer.h + src/configuration.h + src/mainWindow.h + src/editHistory.h + src/format.h ) set (LDForgeForms
--- a/src/AddObjectDialog.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,443 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "MainWindow.h" -#include "AddObjectDialog.h" -#include "Document.h" -#include "Colors.h" -#include "ColorSelector.h" -#include "EditHistory.h" -#include "Widgets.h" -#include "Misc.h" -#include "Primitives.h" - -// ============================================================================= -// -class SubfileListItem : public QTreeWidgetItem -{ - PROPERTY (public, Primitive*, primitive, setPrimitive, STOCK_WRITE) - - public: - SubfileListItem (QTreeWidgetItem* parent, Primitive* info) : - QTreeWidgetItem (parent), - m_primitive (info) {} - - SubfileListItem (QTreeWidget* parent, Primitive* info) : - QTreeWidgetItem (parent), - m_primitive (info) {} -}; - -// ============================================================================= -// -AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) : - QDialog (parent) -{ - setlocale (LC_ALL, "C"); - - int coordCount = 0; - QString typeName = LDObject::typeName (type); - - switch (type) - { - case LDObject::EComment: - { - le_comment = new QLineEdit; - - if (obj) - le_comment->setText (static_cast<LDComment*> (obj)->text()); - - le_comment->setMinimumWidth (384); - } break; - - case LDObject::ELine: - { - coordCount = 6; - } break; - - case LDObject::ETriangle: - { - coordCount = 9; - } break; - - case LDObject::EQuad: - case LDObject::ECondLine: - { - coordCount = 12; - } break; - - case LDObject::EVertex: - { - coordCount = 3; - } break; - - case LDObject::EBFC: - { - 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::k_statementStrings[i]); - } - - if (obj) - rb_bfcType->setValue ( (int) static_cast<LDBFC*> (obj)->statement()); - } break; - - case LDObject::ESubfile: - { - 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->name()); - QList<QTreeWidgetItem*> subfileItems; - - for (Primitive& prim : cat->prims) - { - SubfileListItem* item = new SubfileListItem (parentItem, &prim); - item->setText (0, format ("%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)->fileInfo()->name() == 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->fileInfo()->name()); - } - } break; - - default: - { - critical (format ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName)); - } return; - } - - QPixmap icon = getIcon (format ("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->color(); - else - colnum = (type == LDObject::ECondLine || type == LDObject::ELine) ? 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::ELine: - case LDObject::ECondLine: - case LDObject::ETriangle: - case LDObject::EQuad: - - // 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->vertex (i).getCoordinate (j)); - } - - break; - - case LDObject::EComment: - layout->addWidget (le_comment, 0, 1); - break; - - case LDObject::EBFC: - layout->addWidget (rb_bfcType, 0, 1); - break; - - case LDObject::ESubfile: - 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->position()[ax]); - - defaultMatrix = mo->transform(); - } - - le_matrix->setText (defaultMatrix.toString()); - 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 (format (tr ("Edit %1"), typeName)); - - setWindowIcon (icon); - defaults->destroy(); -} - -// ============================================================================= -// ============================================================================= -void AddObjectDialog::setButtonBackground (QPushButton* button, int colnum) -{ - LDColor* col = ::getColor (colnum); - - button->setIcon (getIcon ("palette")); - button->setAutoFillBackground (true); - - if (col) - button->setStyleSheet (format ("background-color: %1", col->hexcode)); -} - -// ============================================================================= -// ============================================================================= -QString AddObjectDialog::currentSubfileName() -{ - SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem()); - - if (item->primitive() == null) - return ""; // selected a heading - - return item->primitive()->name; -} - -// ============================================================================= -// ============================================================================= -void AddObjectDialog::slot_colorButtonClicked() -{ - ColorSelector::selectColor (colnum, colnum, this); - setButtonBackground (pb_color, colnum); -} - -// ============================================================================= -// ============================================================================= -void AddObjectDialog::slot_subfileTypeChanged() -{ - QString 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->type() == LDObject::EError) - return; - - if (type == LDObject::EEmpty) - return; // Nothing to edit with empties - - const bool newObject = (obj == null); - Matrix transform = g_identity; - AddObjectDialog dlg (type, obj); - - assert (obj == null || obj->type() == type); - - if (dlg.exec() == false) - return; - - if (type == LDObject::ESubfile) - { - QStringList matrixstrvals = dlg.le_matrix->text().split (" ", QString::SkipEmptyParts); - - if (matrixstrvals.size() == 9) - { - double matrixvals[9]; - int i = 0; - - for (QString val : matrixstrvals) - matrixvals[i++] = val.toFloat(); - - transform = Matrix (matrixvals); - } - } - - switch (type) - { - case LDObject::EComment: - { - LDComment* comm = initObj<LDComment> (obj); - comm->setText (dlg.le_comment->text()); - } - break; - - case LDObject::ELine: - case LDObject::ETriangle: - case LDObject::EQuad: - case LDObject::ECondLine: - { - 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::EBFC: - { - LDBFC* bfc = initObj<LDBFC> (obj); - bfc->setStatement ((LDBFC::Statement) dlg.rb_bfcType->value()); - } break; - - case LDObject::EVertex: - { - LDVertex* vert = initObj<LDVertex> (obj); - - for_axes (ax) - vert->pos[ax] = dlg.dsb_coords[ax]->value(); - } - break; - - case LDObject::ESubfile: - { - QString name = dlg.le_subfileName->text(); - - if (name.length() == 0) - return; // no subfile filename - - LDDocument* file = getDocument (name); - - if (!file) - { - critical (format ("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->refresh(); -}
--- a/src/AddObjectDialog.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QDialog> -#include "LDObject.h" - -class QTreeWidgetItem; -class QLineEdit; -class RadioGroup; -class QCheckBox; -class QSpinBox; -class QLabel; -class QTreeWidget; -class QDoubleSpinBox; - -class AddObjectDialog : public QDialog -{ - Q_OBJECT - - public: - AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent = null); - static void staticDialog (const LDObject::Type type, LDObject* obj); - - QLabel* lb_typeIcon; - - // Comment line edit - QLineEdit* le_comment; - - // Coordinate edits for.. anything with coordinates, really. - QDoubleSpinBox* dsb_coords[12]; - - // Color selection dialog button - QPushButton* pb_color; - - // BFC-related widgets - RadioGroup* rb_bfcType; - - // Subfile stuff - QTreeWidget* tw_subfileList; - QLineEdit* le_subfileName; - QLabel* lb_subfileName; - QLineEdit* le_matrix; - - private: - void setButtonBackground (QPushButton* button, int color); - QString currentSubfileName(); - - int colnum; - - private slots: - void slot_colorButtonClicked(); - void slot_subfileTypeChanged(); -};
--- a/src/ColorSelector.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,210 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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.cxx: Color selector box. - */ - -#include <QGraphicsScene> -#include <QGraphicsItem> -#include <QMouseEvent> -#include <QScrollBar> - -#include "Main.h" -#include "MainWindow.h" -#include "ColorSelector.h" -#include "Colors.h" -#include "Configuration.h" -#include "Misc.h" -#include "ui_colorsel.h" - -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 (format ("%1", i)); - numtext->setDefaultTextColor ( (luma (col) < 80) ? Qt::white : Qt::black); - numtext->setPos (x, y); - - if (selection() && i == selection()->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 (!selection()) - { - ui->colorLabel->setText ("---"); - return; - } - - ui->colorLabel->setText (format ("%1 - %2", selection()->index, selection()->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 (selection() && selection()->index >= visibleColors) - { - int y = (selection()->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.selection() != null) - { - val = dlg.selection()->index; - return true; - } - - return false; -} \ No newline at end of file
--- a/src/ColorSelector.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QDialog> -#include "Main.h" - -class LDColor; -class Ui_ColorSelUI; -class QGraphicsScene; - -class ColorSelector : public QDialog -{ - Q_OBJECT - PROPERTY (private, LDColor*, selection, setSelection, STOCK_WRITE) - - public: - explicit ColorSelector (int defval = -1, QWidget* parent = null); - virtual ~ColorSelector(); - static bool selectColor (int& val, int defval = -1, QWidget* parent = null); - - protected: - void mousePressEvent (QMouseEvent* event); - void resizeEvent (QResizeEvent* ev); - - private: - Ui_ColorSelUI* ui; - QGraphicsScene* m_scene; - bool m_firstResize; - - int numRows() const; - int viewportWidth() const; - void drawScene(); - void drawColorInfo(); -};
--- a/src/Colors.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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.cxx: 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 "MainWindow.h" -#include "LDConfig.h" -#include <QColor> - -static LDColor* g_LDColors[MAX_COLORS]; - -// ============================================================================= -// ============================================================================= -void initColors() -{ - LDColor* col; - print ("Initializing color information.\n"); - - // 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.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QColor> -#include "Main.h" - -#define MAX_COLORS 512 - -class LDColor -{ - public: - QString name, hexcode; - QColor faceColor, edgeColor; - int index; -}; - -void initColors(); -int luma (QColor& col); - -// Safely gets a color with the given number or null if no such color. -LDColor* getColor (int colnum); -void setColor (int colnum, LDColor* col); - -// Main and edge color identifiers -static const int maincolor = 16; -static const int edgecolor = 24;
--- a/src/Configuration.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,179 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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.cxx: 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 "Configuration.h" -#include "Misc.h" -#include "MainWindow.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; -static QMap<QString, Config*> g_configsByName; -static QList<Config*> g_configs; - -// ============================================================================= -// Get the QSettings object. -// ============================================================================= -static QSettings* getSettingsObject() -{ - QString path = qApp->applicationDirPath() + "/" UNIXNAME EXTENSION; - return new QSettings (path, QSettings::IniFormat); -} - -Config::Config (QString name) : - m_name (name) {} - -// ============================================================================= -// Load the configuration from file -// ============================================================================= -bool Config::load() -{ - QSettings* settings = getSettingsObject(); - print ("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->getDefaultAsVariant()); - cfg->loadFromVariant (val); - g_configsByName[cfg->name()] = cfg; - g_configs << cfg; - } - - settings->deleteLater(); - return true; -} - -// ============================================================================= -// -// Save the configuration to disk -// -bool Config::save() -{ - QSettings* settings = getSettingsObject(); - - for (Config* cfg : g_configs) - { - if (!cfg->isDefault()) - settings->setValue (cfg->name(), cfg->toVariant()); - else - settings->remove (cfg->name()); - } - - settings->sync(); - print ("Configuration saved to %1.\n", settings->fileName()); - settings->deleteLater(); - return true; -} - -// ============================================================================= -// Reset configuration to defaults. -// ============================================================================= -void Config::reset() -{ - for (Config* cfg : g_configs) - cfg->resetValue(); -} - -// ============================================================================= -// Where is the configuration file located at? -// ============================================================================= -QString Config::filepath (QString file) -{ - return Config::dirpath() + DIRSLASH + file; -} - -// ============================================================================= -// Directory of the configuration file. -// ============================================================================= -QString 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; -} - -// ============================================================================= -// ============================================================================= -template<class T> T* getConfigByName (QString name, Config::Type type) -{ - auto it = g_configsByName.find (name); - - if (it == g_configsByName.end()) - return null; - - Config* cfg = it.value(); - - if (cfg->getType() != type) - { - fprint (stderr, "type of %1 is %2, not %3\n", name, cfg->getType(), type); - abort(); - } - - return reinterpret_cast<T*> (cfg); -} - -// ============================================================================= -// ============================================================================= -#undef IMPLEMENT_CONFIG - -#define IMPLEMENT_CONFIG(NAME) \ - NAME##Config* NAME##Config::getByName (QString name) \ - { \ - return getConfigByName<NAME##Config> (name, E##NAME##Type); \ - } - -IMPLEMENT_CONFIG (Int) -IMPLEMENT_CONFIG (String) -IMPLEMENT_CONFIG (Bool) -IMPLEMENT_CONFIG (Float) -IMPLEMENT_CONFIG (List) -IMPLEMENT_CONFIG (KeySequence) -IMPLEMENT_CONFIG (Vertex)
--- a/src/Configuration.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QString> -#include <QVariant> -#include <QKeySequence> -#include "Macros.h" -#include "Types.h" - -class QSettings; - -#define MAX_INI_LINE 512 -#define MAX_CONFIG 512 - -#define cfg(T, NAME, DEFAULT) \ - Config::T##Type NAME; \ - T##Config config_##NAME (&NAME, #NAME, DEFAULT); - -#define extern_cfg(T, NAME) extern Config::T##Type NAME; - -// ========================================================= -class Config -{ - PROPERTY (private, QString, name, setName, STOCK_WRITE) - - public: - enum Type - { - EIntType, - EStringType, - EFloatType, - EBoolType, - EKeySequenceType, - EListType, - EVertexType, - }; - - using IntType = int; - using StringType = QString; - using FloatType = float; - using BoolType = bool; - using KeySequenceType = QKeySequence; - using ListType = QList<QVariant>; - using VertexType = Vertex; - - Config (QString name); - - virtual QVariant getDefaultAsVariant() const = 0; - virtual Type getType() const = 0; - virtual bool isDefault() const = 0; - virtual void loadFromVariant (const QVariant& val) = 0; - virtual void resetValue() = 0; - virtual QVariant toVariant() const = 0; - - // ------------------------------------------ - static bool load(); - static bool save(); - static void reset(); - static QString dirpath(); - static QString filepath (QString file); - - protected: - static void addToArray (Config* ptr); -}; - -// ============================================================================= -#define IMPLEMENT_CONFIG(NAME) \ -public: \ - using ValueType = Config::NAME##Type; \ - \ - NAME##Config (ValueType* valueptr, QString name, ValueType def) : \ - Config (name), \ - m_valueptr (valueptr), \ - m_default (def) \ - { \ - Config::addToArray (this); \ - *m_valueptr = def; \ - } \ - \ - inline ValueType getValue() const \ - { \ - return *m_valueptr; \ - } \ - \ - inline void setValue (ValueType val) \ - { \ - *m_valueptr = val; \ - } \ - \ - virtual Config::Type getType() const \ - { \ - return Config::E##NAME##Type; \ - } \ - \ - virtual void resetValue() \ - { \ - *m_valueptr = m_default; \ - } \ - \ - virtual const ValueType& getDefault() const \ - { \ - return m_default; \ - } \ - \ - virtual bool isDefault() const \ - { \ - return *m_valueptr == m_default; \ - } \ - \ - virtual void loadFromVariant (const QVariant& val) \ - { \ - *m_valueptr = val.value<ValueType>(); \ - } \ - \ - virtual QVariant toVariant() const \ - { \ - return QVariant::fromValue<ValueType> (*m_valueptr); \ - } \ - \ - virtual QVariant getDefaultAsVariant() const \ - { \ - return QVariant::fromValue<ValueType> (m_default); \ - } \ - \ - static NAME##Config* getByName (QString name); \ - \ -private: \ - ValueType* m_valueptr; \ - ValueType m_default; - -// ============================================================================= -// -class IntConfig : public Config -{ - IMPLEMENT_CONFIG (Int) -}; - -// ============================================================================= -// -class StringConfig : public Config -{ - IMPLEMENT_CONFIG (String) -}; - -// ============================================================================= -// -class FloatConfig : public Config -{ - IMPLEMENT_CONFIG (Float) -}; - -// ============================================================================= -// -class BoolConfig : public Config -{ - IMPLEMENT_CONFIG (Bool) -}; - -// ============================================================================= -// -class KeySequenceConfig : public Config -{ - IMPLEMENT_CONFIG (KeySequence) -}; - -// ============================================================================= -// -class ListConfig : public Config -{ - IMPLEMENT_CONFIG (List) -}; - -// ============================================================================= -// -class VertexConfig : public Config -{ - IMPLEMENT_CONFIG (Vertex) -};
--- a/src/ConfigurationDialog.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,803 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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.cxx: Settings dialog and everything related to it. - * Actual configuration core is in config.cxx. - */ - -#include <QGridLayout> -#include <QFileDialog> -#include <QColorDialog> -#include <QBoxLayout> -#include <QKeyEvent> -#include <QGroupBox> -#include <QDoubleSpinBox> -#include <QLineEdit> -#include <QCheckBox> -#include "Main.h" -#include "ConfigurationDialog.h" -#include "Document.h" -#include "Configuration.h" -#include "Misc.h" -#include "Colors.h" -#include "ColorSelector.h" -#include "GLRenderer.h" -#include "ui_config.h" - -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 (Bool, gl_linelengths); -extern_cfg (String, ld_defaultname); -extern_cfg (String, ld_defaultuser); -extern_cfg (Int, ld_defaultlicense); -extern_cfg (String, gl_selectcolor); -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); - -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())); - - setButtonBackground (ui->selColorButton, gl_selectcolor); - connect (ui->selColorButton, SIGNAL (clicked()), - this, SLOT (slot_setGLSelectColor())); - - 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); - ui->linelengths->setChecked (gl_linelengths); - - int i = 0; - - for (QAction* act : g_win->findChildren<QAction*>()) - { - KeySequenceConfig* cfg = g_win->shortcutForAction (act); - - if (cfg) - addShortcut (*cfg, act, i); - } - - 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 : QList<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 (format ("grid-%1", QString (g_GridInfo[i].name).toLower()))); - - // Text label - lb_gridLabels[i] = new QLabel (format ("%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; - - // Set the maximum angle - if (j == 3) - dsb_gridData[i][j]->setMaximum (360); - - dsb_gridData[i][j]->setValue (*g_GridInfo[i].confs[j]); - gridlayout->addWidget (dsb_gridData[i][j], i + 1, j + 1); - } - } - - ui->grids->setLayout (gridlayout); -} - -// ============================================================================= -// ============================================================================= -static struct LDExtProgInfo -{ - const QString name, - iconname; - QString* const path; - QLineEdit* input; - QPushButton* setPathButton; -#ifndef _WIN32 - bool* const wine; - 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 (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); - 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(); - gl_linelengths = ui->linelengths->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] = dsb_gridData[i][j]->value(); - - // Apply key shortcuts - g_win->updateActionShortcuts(); - - // 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->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.color(); - - 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->color()->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 (QString& conf, QPushButton* button) -{ - QColor col = QColorDialog::getColor (QColor (conf)); - - if (col.isValid()) - { - int r = col.red(), - g = col.green(), - b = col.blue(); - - QString colname; - colname.sprintf ("#%.2X%.2X%.2X", r, g, b); - conf = colname; - setButtonBackground (button, colname); - } -} - -// ============================================================================= -// ============================================================================= -void ConfigDialog::slot_setGLBackground() -{ - pickColor (gl_bgcolor, ui->backgroundColorButton); -} - -// ============================================================================= -// ============================================================================= -void ConfigDialog::slot_setGLForeground() -{ - pickColor (gl_maincolor, ui->mainColorButton); -} - -// ============================================================================= -// ============================================================================= -void ConfigDialog::slot_setGLSelectColor() -{ - pickColor (gl_selectcolor, ui->selColorButton); -} - -// ============================================================================= -// Sets background color of a given button. -// ============================================================================= -void ConfigDialog::setButtonBackground (QPushButton* button, QString value) -{ - button->setIcon (getIcon ("colorselect")); - button->setAutoFillBackground (true); - button->setStyleSheet (format ("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->keyConfig(), this)) - setShortcutText (item); -} - -// ============================================================================= -// Reset a shortcut to defaults -// ============================================================================= -void ConfigDialog::slot_resetShortcut() -{ - QList<ShortcutListItem*> sel = getShortcutSelection(); - - for (ShortcutListItem* item : sel) - { - item->keyConfig()->reset(); - setShortcutText (item); - } -} - -// ============================================================================= -// Remove the shortcut of an action. -// ============================================================================= -void ConfigDialog::slot_clearShortcut() -{ - QList<ShortcutListItem*> sel = getShortcutSelection(); - - for (ShortcutListItem* item : sel) - { - item->keyConfig()->setValue (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); - QString fpath = QFileDialog::getOpenFileName (this, format ("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() -{ - QString 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->action(); - QString label = act->iconText(); - QString keybind = item->keyConfig()->getValue().toString(); - item->setText (format ("%1 (%2)", label, keybind)); -} - -// ============================================================================= -// Gets the configuration string of the quick color toolbar -// ============================================================================= -QString ConfigDialog::quickColorString() -{ - QString val; - - for (const LDQuickColor& entry : quickColors) - { - if (val.length() > 0) - val += ':'; - - if (entry.isSeparator()) - val += '|'; - else - val += format ("%1", entry.color()->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->getValue(), parent); - - if (dlg.exec() == false) - return false; - - cfg->setValue (dlg.seq); - return true; -} - -// ============================================================================= -// ============================================================================= -void KeySequenceDialog::updateOutput() -{ - QString shortcut = seq.toString(); - - if (seq == QKeySequence()) - shortcut = "<empty>"; - - QString text = format ("<center><b>%1</b></center>", shortcut); - lb_output->setText (text); -} - -// ============================================================================= -// ============================================================================= -void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) -{ - seq = ev->key() + ev->modifiers(); - updateOutput(); -}
--- a/src/ConfigurationDialog.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include "MainWindow.h" -#include <QDialog> - -class Ui_ConfigUI; -class QLabel; -class QDoubleSpinBox; - -// ============================================================================= -class ShortcutListItem : public QListWidgetItem -{ - PROPERTY (public, KeySequenceConfig*, keyConfig, setKeyConfig, STOCK_WRITE) - PROPERTY (public, QAction*, action, setAction, STOCK_WRITE) - - public: - explicit ShortcutListItem (QListWidget* view = null, int type = Type) : - QListWidgetItem (view, type) {} -}; - -// ============================================================================= -class ConfigDialog : public QDialog -{ - Q_OBJECT - - public: - enum Tab - { - InterfaceTab, - ProfileTab, - ShortcutsTab, - QuickColorsTab, - GridsTab, - ExtProgsTab, - DownloadTab - }; - - explicit ConfigDialog (Tab deftab = InterfaceTab, QWidget* parent = null, Qt::WindowFlags f = 0); - virtual ~ConfigDialog(); - float getGridValue (int i, int j) const; - - QList<LDQuickColor> quickColors; - QDoubleSpinBox* dsb_gridData[3][4]; - - private: - Ui_ConfigUI* ui; - QLabel* lb_gridLabels[3]; - QLabel* lb_gridIcons[3]; - QList<QListWidgetItem*> quickColorItems; - - void applySettings(); - void addShortcut (KeySequenceConfig& cfg, QAction* act, int& i); - void setButtonBackground (QPushButton* button, QString value); - void pickColor (QString& conf, QPushButton* button); - void updateQuickColorList (LDQuickColor* sel = null); - void setShortcutText (ShortcutListItem* item); - int getItemRow (QListWidgetItem* item, QList<QListWidgetItem*>& haystack); - QString quickColorString(); - QListWidgetItem* getSelectedQuickColor(); - QList<ShortcutListItem*> getShortcutSelection(); - void initGrids(); - void initExtProgs(); - - private slots: - void slot_setGLBackground(); - void slot_setGLForeground(); - void slot_setGLSelectColor(); - void slot_setShortcut(); - void slot_resetShortcut(); - void slot_clearShortcut(); - void slot_setColor(); - void slot_delColor(); - void slot_addColorSeparator(); - void slot_moveColor(); - void slot_clearColors(); - void slot_setExtProgPath(); - void slot_findDownloadFolder(); - void buttonClicked (QAbstractButton* button); - void selectPage (int row); -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class KeySequenceDialog : public QDialog -{ - Q_OBJECT - - public: - explicit KeySequenceDialog (QKeySequence seq, QWidget* parent = null, Qt::WindowFlags f = 0); - static bool staticDialog (KeySequenceConfig* cfg, QWidget* parent = null); - - QLabel* lb_output; - QDialogButtonBox* bbx_buttons; - QKeySequence seq; - - private: - void updateOutput(); - - private slots: - virtual void keyPressEvent (QKeyEvent* ev) override; -};
--- a/src/CrashCatcher.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,139 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#ifdef __unix__ - -#include <QString> -#include <QProcess> -#include <QTemporaryFile> -#include <QMessageBox> -#include <unistd.h> -#include <signal.h> - -#ifdef Q_OS_LINUX -# include <sys/prctl.h> -#endif - -#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 QString 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 (format ("attach %1\n", pid).toLocal8Bit()); - commandsFile.write (QString ("backtrace full\n").toLocal8Bit()); - commandsFile.write (QString ("detach\n").toLocal8Bit()); - commandsFile.write (QString ("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. -#ifdef Q_OS_LINUX - prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0); -#endif - - proc.waitForFinished (1000); - QString output = QString (proc.readAllStandardOutput()); - QString err = QString (proc.readAllStandardError()); - - bombBox (format ("<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); - - print ("%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) -{ - QString errmsg = format ( - "<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.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#ifdef __unix__ -void initCrashCatcher(); -#else // ifdef __unix__ -# define initCrashCatcher() -#endif // ifdef __unix__
--- a/src/Dialogs.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,378 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "MainWindow.h" -#include "GLRenderer.h" -#include "Documentation.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" - -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); - } -} - -// ============================================================================= -// ============================================================================= -QString 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 (QString path) -{ - ui->path->setText (path); -} - -QString LDrawPathDialog::filename() const -{ - return ui->path->text(); -} - -// ============================================================================= -// ============================================================================= -void LDrawPathDialog::slot_findPath() -{ - QString 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 (format ("<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, numLines()); - updateValues(); -} - -// ============================================================================= -// ============================================================================= -void OpenProgressDialog::updateValues() -{ - ui->progressText->setText (format ("Parsing... %1 / %2", progress(), numLines())); - ui->progressBar->setValue (progress()); -} - -// ============================================================================= -// ============================================================================= -void OpenProgressDialog::updateProgress (int progress) -{ - setProgress (progress); - updateValues(); -} - -// ============================================================================= -// ============================================================================= -ExtProgPathPrompt::ExtProgPathPrompt (QString progName, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f), - ui (new Ui_ExtProgPath) -{ - ui->setupUi (this); - QString 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() -{ - QString path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter); - - if (!path.isEmpty()) - ui->m_path->setText (path); -} - -// ============================================================================= -// ============================================================================= -QString 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 (APPNAME " " + QString (fullVersionString())); - - QPushButton* mailButton = new QPushButton; - mailButton->setText (tr ("Contact")); - mailButton->setIcon (getIcon ("mail")); - ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole); - connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail())); - - setWindowTitle (format (tr ("About %1"), APPNAME)); -} - -// ============================================================================= -// ============================================================================= -void AboutDialog::slot_mail() -{ - QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <arezey@gmail.com>?subject=LDForge")); -} - -// ============================================================================= -// ============================================================================= -void bombBox (const QString& 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.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QDialog> -#include "Main.h" -#include "Types.h" - -class Ui_ExtProgPath; -class QRadioButton; -class QCheckBox; -class QProgressBar; -class QGroupBox; -class QDialogButtonBox; -class QDoubleSpinBox; -class QPushButton; -class QLineEdit; -class QSpinBox; -class RadioGroup; -class QLabel; -class QAbstractButton; -class Ui_OverlayUI; -class Ui_LDPathUI; -class Ui_OpenProgressUI; - -class OverlayDialog : public QDialog -{ - Q_OBJECT - - public: - explicit OverlayDialog (QWidget* parent = null, Qt::WindowFlags f = 0); - virtual ~OverlayDialog(); - - QString fpath() const; - int ofsx() const; - int ofsy() const; - double lwidth() const; - double lheight() const; - int camera() const; - - private: - Ui_OverlayUI* ui; - QList<Pair<QRadioButton*, int>> m_cameraArgs; - - private slots: - void slot_fpath(); - void slot_help(); - void slot_dimensionsChanged(); - void fillDefaults (int newcam); -}; - -// ============================================================================= -class LDrawPathDialog : public QDialog -{ - Q_OBJECT - - public: - explicit LDrawPathDialog (const bool validDefault, QWidget* parent = null, Qt::WindowFlags f = 0); - virtual ~LDrawPathDialog(); - QString filename() const; - void setPath (QString path); - - private: - Q_DISABLE_COPY (LDrawPathDialog) - const bool m_validDefault; - Ui_LDPathUI* ui; - QPushButton* okButton(); - QPushButton* cancelButton(); - - private slots: - void slot_findPath(); - void slot_tryConfigure(); - void slot_exit(); - void slot_accept(); -}; - -// ============================================================================= -class OpenProgressDialog : public QDialog -{ - Q_OBJECT - PROPERTY (public, int, progress, setProgress, STOCK_WRITE) - PROPERTY (public, int, numLines, setNumLines, CUSTOM_WRITE) - - public: - explicit OpenProgressDialog (QWidget* parent = null, Qt::WindowFlags f = 0); - virtual ~OpenProgressDialog(); - - public slots: - void updateProgress (int progress); - - private: - Ui_OpenProgressUI* ui; - - void updateValues(); -}; - -// ============================================================================= -class ExtProgPathPrompt : public QDialog -{ - Q_OBJECT - - public: - explicit ExtProgPathPrompt (QString progName, QWidget* parent = 0, Qt::WindowFlags f = 0); - virtual ~ExtProgPathPrompt(); - QString getPath() const; - - public slots: - void findPath(); - - private: - Ui_ExtProgPath* ui; -}; - -// ============================================================================= -class AboutDialog : public QDialog -{ - Q_OBJECT - - public: - AboutDialog (QWidget* parent = null, Qt::WindowFlags f = 0); - - private slots: - void slot_mail(); -}; - -void bombBox (const QString& message);
--- a/src/Document.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1437 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 <QMessageBox> -#include <QFileDialog> -#include <QDir> -#include <QApplication> -#include "Main.h" -#include "Configuration.h" -#include "Document.h" -#include "Misc.h" -#include "MainWindow.h" -#include "EditHistory.h" -#include "Dialogs.h" -#include "GLRenderer.h" -#include "misc/InvokationDeferer.h" - -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 = 10; -static bool g_aborted = false; -static LDDocumentPointer g_logoedStud = null; -static LDDocumentPointer g_logoedStud2 = null; - -LDDocument* LDDocument::m_curdoc = null; - -const QStringList g_specialSubdirectories ({ "s", "48", "8" }); - -// ============================================================================= -// -namespace LDPaths -{ - static QString pathError; - - struct - { - QString LDConfigPath; - QString partsPath, primsPath; - } pathInfo; - - void initPaths() - { - if (!tryConfigure (io_ldpath)) - { - LDrawPathDialog dlg (false); - - if (!dlg.exec()) - exit (0); - - io_ldpath = dlg.filename(); - } - } - - bool tryConfigure (QString 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 = format ("%1" DIRSLASH "parts", path); - pathInfo.LDConfigPath = format ("%1" DIRSLASH "LDConfig.ldr", path); - pathInfo.primsPath = format ("%1" DIRSLASH "p", path); - - return true; - } - - // Accessors - QString getError() - { - return pathError; - } - - QString ldconfig() - { - return pathInfo.LDConfigPath; - } - - QString prims() - { - return pathInfo.primsPath; - } - - QString parts() - { - return pathInfo.partsPath; - } -} - -// ============================================================================= -// -LDDocument::LDDocument() : - m_gldata (new LDGLData) -{ - setImplicit (true); - setSavePosition (-1); - setTabIndex (-1); - setHistory (new History); - history()->setDocument (this); -} - -// ============================================================================= -// -LDDocument::~LDDocument() -{ - // Remove this file from the list of files. This MUST be done FIRST, otherwise - // a ton of other functions will think this file is still valid when it is not! - g_loadedFiles.removeOne (this); - - m_history->setIgnoring (true); - - // Clear everything from the model - for (LDObject* obj : objects()) - obj->destroy(); - - // Clear the cache as well - for (LDObject* obj : cache()) - obj->destroy(); - - delete m_history; - delete m_gldata; - - // 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(); - } - - if (this == g_logoedStud) - g_logoedStud = null; - elif (this == g_logoedStud2) - g_logoedStud2 = null; - - g_win->updateDocumentList(); - print ("Closed %1", name()); -} - -// ============================================================================= -// -LDDocument* findDocument (QString name) -{ - for (LDDocument * file : g_loadedFiles) - if (!file->name().isEmpty() && file->name() == name) - return file; - - return null; -} - -// ============================================================================= -// -QString dirname (QString 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 ""; -} - -// ============================================================================= -// -QString basename (QString path) -{ - long lastpos = path.lastIndexOf (DIRSLASH); - - if (lastpos != -1) - return path.mid (lastpos + 1); - - return path; -} - -// ============================================================================= -// -static QString findLDrawFilePath (QString relpath, bool subdirs) -{ - QString 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 - - // Try find it relative to other currently open documents. We want a file - // in the immediate vicinity of a current model to override stock LDraw stuff. - QString reltop = basename (dirname (relpath)); - - for (LDDocument* doc : g_loadedFiles) - { - if (doc->fullPath().isEmpty()) - continue; - - QString partpath = format ("%1/%2", dirname (doc->fullPath()), relpath); - QFile f (partpath); - - if (f.exists()) - { - // ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48 - QString proptop = basename (dirname (partpath)); - - bool bogus = false; - - for (QString s : g_specialSubdirectories) - { - if ((proptop == s && reltop != s) || (reltop == s && proptop != s)) - { - bogus = true; - break; - } - } - - if (!bogus) - return partpath; - } - } - - if (QFile::exists (relpath)) - return relpath; - - // Try with just the LDraw path first - fullPath = format ("%1" DIRSLASH "%2", io_ldpath, relpath); - - if (QFile::exists (fullPath)) - return fullPath; - - 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 QString& topdir : QList<QString> ({ io_ldpath, net_downloadpath })) - { - for (const QString& subdir : QList<QString> ({ "parts", "p" })) - { - fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); - - if (QFile::exists (fullPath)) - return fullPath; - } - } - } - - // Did not find the file. - return ""; -} - -QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer) -{ - print ("Opening %1...\n", relpath); - QString path = findLDrawFilePath (relpath, subdirs); - - if (pathpointer != null) - *pathpointer = path; - - if (path.isEmpty()) - return null; - - QFile* fp = new QFile (path); - - if (fp->open (QIODevice::ReadOnly)) - return fp; - - fp->deleteLater(); - 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 (lines().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) - obj->destroy(); - - m_objects.clear(); - setDone (true); - return; - } - - // Parse up to 300 lines per iteration - int max = i + 300; - - for (; i < max && i < (int) lines().size(); ++i) - { - QString line = lines()[i]; - - // Trim the trailing newline - QChar c; - - while (line.endsWith ("\n") || line.endsWith ("\r")) - line.chop (1); - - LDObject* obj = parseLine (line); - - // Check for parse errors and warn about tthem - if (obj->type() == LDObject::EError) - { - print ("Couldn't parse line #%1: %2", progress() + 1, static_cast<LDError*> (obj)->reason()); - - if (warnings() != null) - (*warnings())++; - } - - 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) lines().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); - } -} - -// ============================================================================= -// -void LDFileLoader::abort() -{ - setAborted (true); - - if (isOnForeground()) - g_aborted = true; -} - -// ============================================================================= -// -LDObjectList loadFileContents (QFile* fp, int* numWarnings, bool* ok) -{ - QStringList lines; - LDObjectList objs; - - if (numWarnings) - *numWarnings = 0; - - // Read in the lines - while (fp->atEnd() == false) - lines << QString::fromUtf8 (fp->readLine()); - - 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->objects(); - return objs; -} - -// ============================================================================= -// -LDDocument* openDocument (QString 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.. - QFile* fp; - QString fullpath; - - if (search) - fp = openLDrawFile (path.toLower(), true, &fullpath); - else - { - fp = new QFile (path); - fullpath = path; - - if (!fp->open (QIODevice::ReadOnly)) - { - delete fp; - return null; - } - } - - if (!fp) - return null; - - LDDocument* load = new LDDocument; - load->setFullPath (fullpath); - load->setName (LDDocument::shortenName (load->fullPath())); - dprint ("name: %1 (%2)", load->name(), load->fullPath()); - g_loadedFiles << load; - - // Don't take the file loading as actual edits to the file - load->history()->setIgnoring (true); - - int numWarnings; - bool ok; - LDObjectList objs = loadFileContents (fp, &numWarnings, &ok); - fp->close(); - fp->deleteLater(); - - if (!ok) - { - g_loadedFiles.removeOne (load); - delete load; - return null; - } - - load->addObjects (objs); - - if (g_loadingMainFile) - { - LDDocument::setCurrent (load); - g_win->R()->setDocument (load); - print (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); - } - - load->history()->setIgnoring (false); - 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()) - { - QString message = format (tr ("There are unsaved changes to %1. Should it be saved?"), - (name().length() > 0) ? name() : tr ("<anonymous>")); - - int button = msgbox::question (g_win, tr ("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 (name().length() == 0) - { - QString newpath = QFileDialog::getSaveFileName (g_win, tr ("Save As"), - getCurrentDocument()->name(), tr ("LDraw files (*.dat *.ldr)")); - - if (newpath.length() == 0) - return false; - - setName (newpath); - } - - if (!save()) - { - message = format (tr ("Failed to save %1 (%2)\nDo you still want to close?"), - name(), strerror (errno)); - - if (msgbox::critical (g_win, tr ("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()->setDocument (f); - g_win->doFullRefresh(); - g_win->updateTitle(); - g_win->updateActions(); -} - -// ============================================================================= -// -void addRecentFile (QString path) -{ - auto& rfiles = io_recentfiles; - 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 (QString path) -{ - g_loadingMainFile = true; - - // If there's already a file with the same name, this file must replace it. - LDDocument* documentToReplace = null; - QString shortName = LDDocument::shortenName (path); - - for (LDDocument* doc : g_loadedFiles) - { - if (doc->name() == shortName) - { - documentToReplace = doc; - break; - } - } - - // We cannot open this file if the document this would replace is not - // safe to close. - if (documentToReplace != null && documentToReplace->isSafeToClose() == false) - { - g_loadingMainFile = false; - return; - } - - 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 (format (QObject::tr ("Failed to open %1: %2"), path, strerror (errno))); - } - - g_loadingMainFile = false; - return; - } - - file->setImplicit (false); - - // Replace references to the old file with the new file. - if (documentToReplace != null) - { - for (LDDocumentPointer* ptr : documentToReplace->references()) - { dprint ("ptr: %1 (%2)\n", - ptr, ptr->pointer() ? ptr->pointer()->name() : "<null>"); - - *ptr = file; - } - - assert (documentToReplace->references().isEmpty()); - delete documentToReplace; - } - - // 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 (QString savepath) -{ - if (!savepath.length()) - savepath = fullPath(); - - QFile f (savepath); - - if (!f.open (QIODevice::WriteOnly)) - 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. - LDObject* nameObject = getObject (1); - - if (!isImplicit() && nameObject != null && nameObject->type() == LDObject::EComment) - { - LDComment* nameComment = static_cast<LDComment*> (nameObject); - - if (nameComment->text().left (6) == "Name: ") - { - QString newname = shortenName (savepath); - nameComment->setText (format ("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 : objects()) - f.write ((obj->asText() + "\r\n").toUtf8()); - - // File is saved, now clean up. - f.close(); - - // We have successfully saved, update the save position now. - setSavePosition (history()->position()); - setFullPath (savepath); - setName (shortenName (savepath)); - - g_win->updateDocumentListItem (this); - g_win->updateTitle(); - return true; -} - -// ============================================================================= -// -class LDParseError : public std::exception -{ - PROPERTY (private, QString, error, setError, STOCK_WRITE) - PROPERTY (private, QString, line, setLine, STOCK_WRITE) - - public: - LDParseError (QString line, QString a) : - m_error (a), - m_line (line) {} - - const char* what() const throw() - { - return qPrintable (error()); - } -}; - -// ============================================================================= -// -void checkTokenCount (QString line, const QStringList& tokens, int num) -{ - if (tokens.size() != num) - throw LDParseError (line, format ("Bad amount of tokens, expected %1, got %2", num, tokens.size())); -} - -// ============================================================================= -// -void checkTokenNumbers (QString line, const QStringList& tokens, int min, int max) -{ - bool ok; - - // Check scientific notation, e.g. 7.99361e-15 - QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+"); - - for (int i = min; i <= max; ++i) - { - tokens[i].toDouble (&ok); - - if (!ok && !scient.exactMatch (tokens[i])) - throw LDParseError (line, format ("Token #%1 was `%2`, expected a number (matched length: %3)", (i + 1), tokens[i], scient.matchedLength())); - } -} - -// ============================================================================= -// -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 (QString line) -{ - try - { - QStringList tokens = line.split (" ", QString::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) - throw LDParseError (line, "Illogical line code"); - - int num = tokens[0][0].digitValue(); - - switch (num) - { - case 0: - { - // Comment - QString 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 == format ("BFC %1", LDBFC::k_statementStrings [i])) - return new LDBFC ( (LDBFC::Statement) 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 - { - QString a; - LDBFC::Statement 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) - checkTokenCount (line, tokens, 7); - checkTokenNumbers (line, tokens, 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") - { - checkTokenCount (line, tokens, 9);; - checkTokenNumbers (line, tokens, 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->setText (comm); - return obj; - } - - case 1: - { - // Subfile - checkTokenCount (line, tokens, 15); - checkTokenNumbers (line, tokens, 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. Note we cannot use LDParseError - // here because the error object needs the document reference. - if (!load) - { - LDError* obj = new LDError (line, format ("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: - { - checkTokenCount (line, tokens, 8); - checkTokenNumbers (line, tokens, 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: - { - checkTokenCount (line, tokens, 11); - checkTokenNumbers (line, tokens, 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: - { - checkTokenCount (line, tokens, 14); - checkTokenNumbers (line, tokens, 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 - throw LDError (line, "Unknown line code number"); - } - } - catch (LDParseError& e) - { - return new LDError (e.line(), e.error()); - } -} - -// ============================================================================= -// -LDDocument* getDocument (QString 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()->objects()) - { - if (obj->type() == LDObject::ESubfile) - { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - LDDocument* fileInfo = getDocument (ref->fileInfo()->name()); - - if (fileInfo) - ref->setFileInfo (fileInfo); - else - ref->replace (new LDError (ref->asText(), format ("Could not open %1", ref->fileInfo()->name()))); - } - - // Reparse gibberish files. It could be that they are invalid because - // of loading errors. Circumstances may be different now. - if (obj->type() == LDObject::EError) - obj->replace (parseLine (static_cast<LDError*> (obj)->contents())); - } -} - -// ============================================================================= -// -int LDDocument::addObject (LDObject* obj) -{ - history()->add (new AddHistory (objects().size(), obj)); - m_objects << obj; - - if (obj->type() == LDObject::EVertex) - m_vertices << obj; - -#ifdef DEBUG - if (!isImplicit()) - dprint ("Added object #%1 (%2)\n", obj->id(), obj->typeName()); -#endif - - obj->setDocument (this); - return getObjectCount() - 1; -} - -// ============================================================================= -// -void LDDocument::addObjects (const LDObjectList objs) -{ - for (LDObject* obj : objs) - if (obj) - addObject (obj); -} - -// ============================================================================= -// -void LDDocument::insertObj (int pos, LDObject* obj) -{ - history()->add (new AddHistory (pos, obj)); - m_objects.insert (pos, obj); - obj->setDocument (this); - -#ifdef DEBUG - if (!isImplicit()) - dprint ("Inserted object #%1 (%2) at %3\n", obj->id(), obj->typeName(), pos); -#endif -} - -// ============================================================================= -// -void LDDocument::forgetObject (LDObject* obj) -{ - int idx = obj->lineNumber(); - obj->unselect(); - assert (m_objects[idx] == obj); - - if (!history()->isIgnoring()) - history()->add (new DelHistory (idx, obj)); - - m_objects.removeAt (idx); - obj->setDocument (null); -} - -// ============================================================================= -// -bool safeToCloseAll() -{ - for (LDDocument* f : g_loadedFiles) - if (!f->isSafeToClose()) - return false; - - return true; -} - -// ============================================================================= -// -void LDDocument::setObject (int idx, LDObject* obj) -{ - assert (idx >= 0 && idx < m_objects.size()); - - // Mark this change to history - if (!m_history->isIgnoring()) - { - QString oldcode = getObject (idx)->asText(); - QString newcode = obj->asText(); - *m_history << new EditHistory (idx, oldcode, newcode); - } - - m_objects[idx]->unselect(); - m_objects[idx]->setDocument (null); - obj->setDocument (this); - m_objects[idx] = obj; -} - -// ============================================================================= -// -// Close all documents we don't need anymore -// -void LDDocument::closeUnused() -{ - for (LDDocument* file : g_loadedFiles) - if (file->isImplicit() && file->references().isEmpty()) - delete file; -} - -// ============================================================================= -// -LDObject* LDDocument::getObject (int pos) const -{ - if (m_objects.size() <= pos) - return null; - - return m_objects[pos]; -} - -// ============================================================================= -// -int LDDocument::getObjectCount() const -{ - return objects().size(); -} - -// ============================================================================= -// -bool LDDocument::hasUnsavedChanges() const -{ - return !isImplicit() && history()->position() != savePosition(); -} - -// ============================================================================= -// -QString LDDocument::getDisplayName() -{ - if (!name().isEmpty()) - return name(); - - if (!defaultName().isEmpty()) - return "[" + defaultName() + "]"; - - return tr ("<anonymous>"); -} - -// ============================================================================= -// -LDObjectList 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)) - { - // Ensure logoed studs are loaded first - loadLogoedStuds(); - - if (name() == "stud.dat" && g_logoedStud) - return g_logoedStud->inlineContents (flags); - elif (name() == "stud2.dat" && g_logoedStud2) - return g_logoedStud2->inlineContents (flags); - } - - LDObjectList objs, objcache; - - bool deep = flags & LDSubfile::DeepInline, - doCache = flags & LDSubfile::CacheInline; - - if (m_needsCache) - { - m_cache.clear(); - doCache = true; - } - - // If we have this cached, just create a copy of that - if (deep && cache().isEmpty() == false) - { - for (LDObject* obj : cache()) - objs << obj->createCopy(); - } - else - { - if (!deep) - doCache = false; - - for (LDObject* obj : objects()) - { - // 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->type() == LDObject::ESubfile) - { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - - // We only want to cache immediate subfiles, so shed the caching - // flag when recursing deeper in hierarchy. - LDObjectList otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline)); - - for (LDObject* otherobj : otherobjs) - { - // Cache this object, if desired - if (doCache) - objcache << otherobj->createCopy(); - - objs << otherobj; - } - } - else - { - if (doCache) - objcache << obj->createCopy(); - - objs << obj->createCopy(); - } - } - - 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. ;) -// -// TODO: 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()->setDocument (f); - g_win->R()->repaint(); - print ("Changed file to %1", f->getDisplayName()); - } -} - -// ============================================================================= -// -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]->name().isEmpty() && - g_loadedFiles[1]->name().isEmpty() == false && - !g_loadedFiles[0]->hasUnsavedChanges() - ) - delete g_loadedFiles[0]; -} - -// ============================================================================= -// -void loadLogoedStuds() -{ - if (g_logoedStud && g_logoedStud2) - return; - - delete g_logoedStud; - delete g_logoedStud2; - - g_logoedStud = openDocument ("stud-logo.dat", true); - g_logoedStud2 = openDocument ("stud2-logo.dat", true); - - print (LDDocument::tr ("Logoed studs loaded.\n")); -} - -// ============================================================================= -// -void LDDocument::addToSelection (LDObject* obj) // [protected] -{ - if (obj->isSelected()) - return; - - assert (obj->document() == this); - m_sel << obj; - obj->setSelected (true); -} - -// ============================================================================= -// -void LDDocument::removeFromSelection (LDObject* obj) // [protected] -{ - if (!obj->isSelected()) - return; - - assert (obj->document() == this); - m_sel.removeOne (obj); - obj->setSelected (false); -} - -// ============================================================================= -// -void LDDocument::clearSelection() -{ - for (LDObject* obj : m_sel) - removeFromSelection (obj); - - assert (m_sel.isEmpty()); -} - -// ============================================================================= -// -const LDObjectList& LDDocument::getSelection() const -{ - return m_sel; -} - -// ============================================================================= -// -void LDDocument::swapObjects (LDObject* one, LDObject* other) -{ - int a = m_objects.indexOf (one); - int b = m_objects.indexOf (other); - assert (a != b && a != -1 && b != -1); - m_objects[b] = one; - m_objects[a] = other; - addToHistory (new SwapHistory (one->id(), other->id())); -} - -// ============================================================================= -// -QString LDDocument::shortenName (QString a) // [static] -{ - QString shortname = basename (a); - QString topdirname = basename (dirname (a)); - - if (g_specialSubdirectories.contains (topdirname)) - shortname.prepend (topdirname + "\\"); - - return shortname; -} - -// ============================================================================= -// -void LDDocument::addReference (LDDocumentPointer* ptr) -{ - m_references << ptr; -} - -// ============================================================================= -// -void LDDocument::removeReference (LDDocumentPointer* ptr) -{ - m_references.removeOne (ptr); - - if (references().isEmpty()) - invokeLater (closeUnused); -} \ No newline at end of file
--- a/src/Document.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,240 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QObject> -#include "Main.h" -#include "LDObject.h" -#include "EditHistory.h" - -class History; -class OpenProgressDialog; -class LDDocumentPointer; -struct LDGLData; - -namespace LDPaths -{ - void initPaths(); - bool tryConfigure (QString path); - - QString ldconfig(); - QString prims(); - QString parts(); - QString getError(); -} - -// ============================================================================= -// -// This class stores a document either as a editable file for the user or for -// subfile caching. Its methods handle file input and output. -// -// A file is implicit when they are opened automatically for caching purposes -// and are hidden from the user. User-opened files are explicit (not implicit). -// -// The default name is a placeholder, initially suggested name for a file. The -// primitive generator uses this to give initial names to primitives. -// -class LDDocument : public QObject -{ - public: - using ReferenceList = QList<LDDocumentPointer*>; - - Q_OBJECT - PROPERTY (public, QString, name, setName, STOCK_WRITE) - PROPERTY (private, LDObjectList, objects, setObjects, STOCK_WRITE) - PROPERTY (private, LDObjectList, cache, setCache, STOCK_WRITE) - PROPERTY (private, History*, history, setHistory, STOCK_WRITE) - PROPERTY (private, LDObjectList, vertices, setVertices, STOCK_WRITE) - PROPERTY (private, ReferenceList, references, setReferences, STOCK_WRITE) - PROPERTY (public, QString, fullPath, setFullPath, STOCK_WRITE) - PROPERTY (public, QString, defaultName, setDefaultName, STOCK_WRITE) - PROPERTY (public, bool, isImplicit, setImplicit, STOCK_WRITE) - PROPERTY (public, long, savePosition, setSavePosition, STOCK_WRITE) - PROPERTY (public, int, tabIndex, setTabIndex, STOCK_WRITE) - - public: - LDDocument(); - ~LDDocument(); - - int addObject (LDObject* obj); // Adds an object to this file at the end of the file. - void addObjects (const LDObjectList objs); - void clearSelection(); - void forgetObject (LDObject* obj); // Deletes the given object from the object chain. - QString getDisplayName(); - const LDObjectList& getSelection() const; - bool hasUnsavedChanges() const; // Does this document have unsaved changes? - LDObjectList inlineContents (LDSubfile::InlineFlags flags); - void insertObj (int pos, LDObject* obj); - int getObjectCount() const; - LDObject* getObject (int pos) const; - bool save (QString path = ""); // Saves this file to disk. - void swapObjects (LDObject* one, LDObject* other); - bool isSafeToClose(); // Perform safety checks. Do this before closing any files! - void setObject (int idx, LDObject* obj); - void addReference (LDDocumentPointer* ptr); - void removeReference (LDDocumentPointer* ptr); - - inline LDDocument& operator<< (LDObject* obj) - { - addObject (obj); - return *this; - } - - inline void addHistoryStep() - { - history()->addStep(); - } - - inline void undo() - { - history()->undo(); - } - - inline void redo() - { - history()->redo(); - } - - inline void clearHistory() - { - history()->clear(); - } - - inline void addToHistory (AbstractHistoryEntry* entry) - { - *history() << entry; - } - - static void closeUnused(); - static LDDocument* current(); - static void setCurrent (LDDocument* f); - static void closeInitialFile(); - static int countExplicitFiles(); - - // Turns a full path into a relative path - static QString shortenName (QString a); - - protected: - void addToSelection (LDObject* obj); - void removeFromSelection (LDObject* obj); - - LDGLData* getGLData() - { - return m_gldata; - } - - friend class LDObject; - friend class GLRenderer; - - private: - LDObjectList m_sel; - LDGLData* m_gldata; - - // If set to true, next inline of this document discards the cache and - // re-builds it. - bool m_needsCache; - - static LDDocument* m_curdoc; -}; - -inline LDDocument* getCurrentDocument() -{ - return LDDocument::current(); -} - -// Close all current loaded files and start off blank. -void newFile(); - -// Opens the given file as the main file. Everything is closed first. -void openMainFile (QString path); - -// Finds an OpenFile by name or null if not open -LDDocument* findDocument (QString name); - -// Opens the given file and parses the LDraw code within. Returns a pointer -// to the opened file or null on error. -LDDocument* openDocument (QString path, bool search); - -// Opens the given file and returns a pointer to it, potentially looking in /parts and /p -QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer = null); - -// Close all open files, whether user-opened or subfile caches. -void closeAll(); - -// Parses a string line containing an LDraw object and returns the object parsed. -LDObject* parseLine (QString line); - -// Retrieves the pointer to the given document by file name. Document is loaded -// from file if necessary. Can return null if neither succeeds. -LDDocument* getDocument (QString filename); - -// Re-caches all subfiles. -void reloadAllSubfiles(); - -// Is it safe to close all files? -bool safeToCloseAll(); - -LDObjectList loadFileContents (QFile* f, int* numWarnings, bool* ok = null); - -extern QList<LDDocument*> g_loadedFiles; - -inline const LDObjectList& selection() -{ - return getCurrentDocument()->getSelection(); -} - -void addRecentFile (QString path); -void loadLogoedStuds(); -QString basename (QString path); -QString dirname (QString path); - -extern QList<LDDocument*> g_loadedFiles; // Vector of all currently opened files. - -// ============================================================================= -// -// LDFileLoader -// -// Loads the given file and parses it to LDObjects using parseLine. It's a -// separate class so as to be able to do the work progressively through the -// event loop, allowing the program to maintain responsivity during loading. -// -class LDFileLoader : public QObject -{ - Q_OBJECT - PROPERTY (private, LDObjectList, objects, setObjects, STOCK_WRITE) - PROPERTY (private, bool, isDone, setDone, STOCK_WRITE) - PROPERTY (private, int, progress, setProgress, STOCK_WRITE) - PROPERTY (private, bool, isAborted, setAborted, STOCK_WRITE) - PROPERTY (public, QStringList, lines, setLines, STOCK_WRITE) - PROPERTY (public, int*, warnings, setWarnings, STOCK_WRITE) - PROPERTY (public, bool, isOnForeground, setOnForeground, STOCK_WRITE) - - public slots: - void start(); - void abort(); - - private: - OpenProgressDialog* dlg; - - private slots: - void work (int i); - - signals: - void progressUpdate (int progress); - void workDone(); -};
--- a/src/Documentation.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/Documentation.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -extern const char* g_docs_overlays; -void showDocumentation (const char* text);
--- a/src/EditHistory.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,202 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "EditHistory.h" -#include "LDObject.h" -#include "Document.h" -#include "Misc.h" -#include "MainWindow.h" -#include "GLRenderer.h" - -// ============================================================================= -// -History::History() : - m_position (-1) {} - -// ============================================================================= -// -void History::undo() -{ - if (m_changesets.isEmpty() || position() == -1) - return; - - // Don't take the changes done here as actual edits to the document - setIgnoring (true); - - const Changeset& set = getChangeset (position()); - - // Iterate the list in reverse and undo all actions - for (int i = set.size() - 1; i >= 0; --i) - { - AbstractHistoryEntry* change = set[i]; - change->undo(); - } - - m_position--; - g_win->refresh(); - g_win->updateActions(); - dprint ("Position is now %1", position()); - setIgnoring (false); -} - -// ============================================================================= -// -void History::redo() -{ - if (position() == m_changesets.size()) - return; - - setIgnoring (true); - const Changeset& set = getChangeset (position() + 1); - - // Redo things - in the order as they were done in the first place - for (const AbstractHistoryEntry* change : set) - change->redo(); - - setPosition (position() + 1); - g_win->refresh(); - g_win->updateActions(); - dprint ("Position is now %1", position()); - setIgnoring (false); -} - -// ============================================================================= -// -void History::clear() -{ - for (Changeset set : m_changesets) - for (AbstractHistoryEntry* change : set) - delete change; - - m_changesets.clear(); - dprint ("History: cleared"); -} - -// ============================================================================= -// -void History::addStep() -{ - if (m_currentChangeset.isEmpty()) - return; - - while (position() < getSize() - 1) - { - Changeset last = m_changesets.last(); - - for (AbstractHistoryEntry* entry : last) - delete entry; - - m_changesets.removeLast(); - } - - dprint ("History: step added (%1 changes)", m_currentChangeset.size()); - m_changesets << m_currentChangeset; - m_currentChangeset.clear(); - setPosition (position() + 1); - g_win->updateActions(); -} - -// ============================================================================= -// -void History::add (AbstractHistoryEntry* entry) -{ - if (isIgnoring()) - { - delete entry; - return; - } - - entry->setParent (this); - m_currentChangeset << entry; - dprint ("History: added entry of type %1", entry->getTypeName()); -} - -// ============================================================================= -// -void AddHistory::undo() const -{ - LDObject* obj = parent()->document()->getObject (index()); - obj->destroy(); -} - -// ============================================================================= -// -void AddHistory::redo() const -{ - LDObject* obj = parseLine (code()); - parent()->document()->insertObj (index(), obj); - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// -DelHistory::DelHistory (int idx, LDObject* obj) : - m_index (idx), - m_code (obj->asText()) {} - -// ============================================================================= -// heh -// -void DelHistory::undo() const -{ - LDObject* obj = parseLine (code()); - parent()->document()->insertObj (index(), obj); - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// -void DelHistory::redo() const -{ - LDObject* obj = parent()->document()->getObject (index()); - obj->destroy(); -} - -// ============================================================================= -// -void EditHistory::undo() const -{ - LDObject* obj = getCurrentDocument()->getObject (index()); - LDObject* newobj = parseLine (oldCode()); - obj->replace (newobj); - g_win->R()->compileObject (newobj); -} - -// ============================================================================= -// -void EditHistory::redo() const -{ - LDObject* obj = getCurrentDocument()->getObject (index()); - LDObject* newobj = parseLine (newCode()); - obj->replace (newobj); - g_win->R()->compileObject (newobj); -} - -// ============================================================================= -// -void SwapHistory::undo() const -{ - LDObject::fromID (a)->swap (LDObject::fromID (b)); -} - -// ============================================================================= -// -void SwapHistory::redo() const -{ - undo(); -} \ No newline at end of file
--- a/src/EditHistory.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,175 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include "Main.h" -#include "LDObject.h" - -#define IMPLEMENT_HISTORY_TYPE(N) \ - virtual ~N##History() {} \ - virtual void undo() const override; \ - virtual void redo() const override; \ - \ - virtual History::EHistoryType getType() const override \ - { \ - return History::E##N##History; \ - } \ - \ - virtual QString getTypeName() const \ - { \ - return #N; \ - } - -class AbstractHistoryEntry; - -// ============================================================================= -class History -{ - PROPERTY (private, int, position, setPosition, STOCK_WRITE) - PROPERTY (public, LDDocument*, document, setDocument, STOCK_WRITE) - PROPERTY (public, bool, isIgnoring, setIgnoring, STOCK_WRITE) - - public: - typedef QList<AbstractHistoryEntry*> Changeset; - - enum EHistoryType - { - EDelHistory, - EEditHistory, - EAddHistory, - EMoveHistory, - ESwapHistory, - }; - - History(); - void undo(); - void redo(); - void clear(); - - void addStep(); - void add (AbstractHistoryEntry* entry); - - inline long getSize() const - { - return m_changesets.size(); - } - - inline History& operator<< (AbstractHistoryEntry* entry) - { - add (entry); - return *this; - } - - inline const Changeset& getChangeset (long pos) const - { - return m_changesets[pos]; - } - - private: - Changeset m_currentChangeset; - QList<Changeset> m_changesets; -}; - -// ============================================================================= -// -class AbstractHistoryEntry -{ - PROPERTY (public, History*, parent, setParent, STOCK_WRITE) - - public: - virtual ~AbstractHistoryEntry() {} - virtual void undo() const = 0; - virtual void redo() const = 0; - virtual History::EHistoryType getType() const = 0; - virtual QString getTypeName() const = 0; -}; - -// ============================================================================= -// -class DelHistory : public AbstractHistoryEntry -{ - PROPERTY (private, int, index, setIndex, STOCK_WRITE) - PROPERTY (private, QString, code, setCode, STOCK_WRITE) - - public: - IMPLEMENT_HISTORY_TYPE (Del) - DelHistory (int idx, LDObject* obj); -}; - -// ============================================================================= -// -class EditHistory : public AbstractHistoryEntry -{ - PROPERTY (private, int, index, setIndex, STOCK_WRITE) - PROPERTY (private, QString, oldCode, setOldCode, STOCK_WRITE) - PROPERTY (private, QString, newCode, setNewCode, STOCK_WRITE) - - public: - IMPLEMENT_HISTORY_TYPE (Edit) - - EditHistory (int idx, QString oldCode, QString newCode) : - m_index (idx), - m_oldCode (oldCode), - m_newCode (newCode) {} -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class AddHistory : public AbstractHistoryEntry -{ - PROPERTY (private, int, index, setIndex, STOCK_WRITE) - PROPERTY (private, QString, code, setCode, STOCK_WRITE) - - public: - IMPLEMENT_HISTORY_TYPE (Add) - - AddHistory (int idx, LDObject* obj) : - m_index (idx), - m_code (obj->asText()) {} -}; - -// ============================================================================= -// -class MoveHistory : public AbstractHistoryEntry -{ - public: - IMPLEMENT_HISTORY_TYPE (Move) - - QList<int> indices; - Vertex dest; - - MoveHistory (QList<int> indices, Vertex dest) : - indices (indices), - dest (dest) {} -}; - -// ============================================================================= -// -class SwapHistory : public AbstractHistoryEntry -{ - public: - IMPLEMENT_HISTORY_TYPE (Swap) - - SwapHistory (int a, int b) : - a (a), - b (b) {} - - private: - int a, b; -};
--- a/src/ExternalPrograms.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,696 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "Configuration.h" -#include "Misc.h" -#include "MainWindow.h" -#include "Document.h" -#include "Widgets.h" -#include "EditHistory.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, ""); - -QString* 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); - -bool* 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 mkTempFile (QTemporaryFile& tmp, QString& fname) -{ - if (!tmp.open()) - return false; - - fname = tmp.fileName(); - tmp.close(); - return true; -} - -// ============================================================================= -// -static bool checkProgPath (const extprog prog) -{ - QString& path = *g_extProgPaths[prog]; - - 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 QString processErrorString (extprog prog, QProcess& proc) -{ - switch (proc.error()) - { - case QProcess::FailedToStart: - { - QString wineblurb; - -#ifndef _WIN32 - if (*g_extProgWine[prog]) - wineblurb = "make sure Wine is installed and "; -#endif - - return format ("Program failed to start, %1check your permissions", wineblurb); - } break; - - 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 format ("Timed out (30 seconds)"); - } - - return ""; -} - -// ============================================================================= -// -static void writeObjects (const LDObjectList& objects, QFile& f) -{ - for (LDObject* obj : objects) - { - if (obj->type() == LDObject::ESubfile) - { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - LDObjectList objs = ref->inlineContents (LDSubfile::DeepInline); - - writeObjects (objs, f); - - for (LDObject* obj : objs) - obj->destroy(); - } - else - f.write ((obj->asText() + "\r\n").toUtf8()); - } -} - -// ============================================================================= -// -static void writeObjects (const LDObjectList& objects, QString fname) -{ - // Write the input file - QFile f (fname); - - if (!f.open (QIODevice::WriteOnly | QIODevice::Text)) - { - critical (format ("Couldn't open temporary file %1 for writing: %2\n", fname, f.errorString())); - return; - } - - writeObjects (objects, f); - f.close(); - -#ifdef DEBUG - QFile::copy (fname, "debug_lastInput"); -#endif -} - -// ============================================================================= -// -void writeSelection (QString fname) -{ - writeObjects (selection(), fname); -} - -// ============================================================================= -// -void writeColorGroup (const int colnum, QString fname) -{ - LDObjectList objects; - - for (LDObject* obj : getCurrentDocument()->objects()) - { - if (obj->isColored() == false || obj->color() != colnum) - continue; - - objects << obj; - } - - writeObjects (objects, fname); -} - -// ============================================================================= -// -bool runUtilityProcess (extprog prog, QString path, QString argvstr) -{ - QTemporaryFile input; - QStringList argv = argvstr.split (" ", QString::SkipEmptyParts); - -#ifndef _WIN32 - if (*g_extProgWine[prog]) - { - argv.insert (0, path); - path = "wine"; - } -#endif // _WIN32 - - print ("Running command: %1 %2\n", path, argv.join (" ")); - - if (!input.open()) - return false; - - QProcess proc; - - // Begin! - proc.setStandardInputFile (input.fileName()); - proc.start (path, argv); - - if (!proc.waitForStarted()) - { - critical (format ("Couldn't start %1: %2\n", g_extProgNames[prog], processErrorString (prog, proc))); - return false; - } - - // Write an enter, the utility tools all expect one - input.write ("\n"); - - // Wait while it runs - proc.waitForFinished(); - - QString err = ""; - - if (proc.exitStatus() != QProcess::NormalExit) - err = processErrorString (prog, proc); - - // Check the return code - if (proc.exitCode() != 0) - err = format ("Program exited abnormally (return code %1).", proc.exitCode()); - - if (!err.isEmpty()) - { - critical (format ("%1 failed: %2\n", g_extProgNames[prog], err)); - return false; - } - - return true; -} - -// ============================================================================= -// -static void insertOutput (QString fname, bool replace, QList<int> colorsToReplace) -{ -#ifdef DEBUG - QFile::copy (fname, "./debug_lastOutput"); -#endif // RELEASE - - // Read the output file - QFile f (fname); - - if (!f.open (QIODevice::ReadOnly)) - { - critical (format ("Couldn't open temporary file %1 for reading.\n", fname)); - return; - } - - LDObjectList objs = loadFileContents (&f, null); - - // If we replace the objects, delete the selection now. - if (replace) - g_win->deleteSelection(); - - for (int colnum : colorsToReplace) - g_win->deleteByColor (colnum); - - // Insert the new objects - getCurrentDocument()->clearSelection(); - - for (LDObject* obj : objs) - { - if (!obj->isScemantic()) - { - obj->destroy(); - 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; - QString inDATName, outDATName; - - // Make temp files for the input and output files - if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) - return; - - // Compose the command-line arguments - QString 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; - QString inDATName, outDATName; - - // Make temp files for the input and output files - if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) - return; - - // Compose arguments - QString 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; - QString inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName; - - if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) || - !mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) || - !mkTempFile (edgesdat, edgesDATName)) - { - return; - } - - QString parms = join ( - { - (ui.cb_colorize->isChecked()) ? "-c" : "", - (ui.cb_nocondense->isChecked()) ? "-t" : "", - "-s", - ui.dsb_prescale->value() - }); - - QString argv_normal = join ( - { - parms, - inDATName, - cutDATName, - outDATName - }); - - QString 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; - QString in1DATName, in2DATName, outDATName; - - if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName)) - return; - - QString argv = join ( - { - (ui.cb_oldsweep->isChecked() ? "-s" : ""), - (ui.cb_reverse->isChecked() ? "-r" : ""), - (ui.dsb_segsplit->value() != 0 ? format ("-l %1", ui.dsb_segsplit->value()) : ""), - (ui.sb_bias->value() != 0 ? format ("-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; - QString in1DATName, in2DATName, outDATName; - - if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName)) - return; - - QString 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; - QString inName, outName; - - if (!mkTempFile (in, inName) || !mkTempFile (out, outName)) - return; - - int unmatched = ui.unmatched->currentIndex(); - - QString argv = join ( - { - format ("-p %1", ui.precision->value()), - format ("-af %1", ui.flatAngle->value()), - format ("-ac %1", ui.condAngle->value()), - format ("-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/Format.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,172 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QString> -#include "Types.h" - -//! \file Format.h -//! Contains string formatting-related functions and classes. - -//! -//! Converts a given value into a string that can be retrieved with text(). -//! Used as the argument type to the formatting functions, hence its name. -//! -class StringFormatArg -{ - public: - StringFormatArg (const QString& a) : m_text (a) {} - StringFormatArg (const char& a) : m_text (a) {} - StringFormatArg (const uchar& a) : m_text (a) {} - StringFormatArg (const QChar& a) : m_text (a) {} - StringFormatArg (int a) : m_text (QString::number (a)) {} - StringFormatArg (long a) : m_text (QString::number (a)) {} - StringFormatArg (const float& a) : m_text (QString::number (a)) {} - StringFormatArg (const double& a) : m_text (QString::number (a)) {} - StringFormatArg (const Vertex& a) : m_text (a.toString (false)) {} - StringFormatArg (const Matrix& a) : m_text (a.toString()) {} - StringFormatArg (const char* a) : m_text (a) {} - - StringFormatArg (const void* a) - { - m_text.sprintf ("%p", a); - } - - template<typename T> - StringFormatArg (const QList<T>& a) - { - m_text = "{"; - - for (const T& it : a) - { - if (&it != &a.first()) - m_text += ", "; - - StringFormatArg arg (it); - m_text += arg.text(); - } - - m_text += "}"; - } - - inline QString text() const - { - return m_text; - } - - private: - QString m_text; -}; - -//! -//! Helper function for \c format -//! -template<typename Arg1, typename... Rest> -void formatHelper (QString& str, Arg1 arg1, Rest... rest) -{ - str = str.arg (StringFormatArg (arg1).text()); - formatHelper (str, rest...); -} - -//! -//! Overload of \c formatHelper() with no template args -//! -static void formatHelper (QString& str) __attribute__ ((unused)); -static void formatHelper (QString& str) -{ - (void) str; -} - -//! -//! @brief Format the message with the given args. -//! -//! The formatting ultimately uses QString's arg() method to actually format -//! the args so the format string should be prepared accordingly, with %1 -//! referring to the first arg, %2 to the second, etc. -//! -//! \param fmtstr The string to format -//! \param args The args to format with -//! \return The formatted string -//! -template<typename... Args> -QString format (QString fmtstr, Args... args) -{ - formatHelper (fmtstr, args...); - return fmtstr; -} - -//! -//! From MessageLog.cc - declared here so that I don't need to include -//! MessageLog.h here. Prints the given message to log. -//! -void printToLog (const QString& msg); - -//! -//! Format and print the given args to the message log. -//! \param fmtstr The string to format -//! \param args The args to format with -//! -template<typename... Args> -void print (QString fmtstr, Args... args) -{ - formatHelper (fmtstr, args...); - printToLog (fmtstr); -} - -//! -//! Format and print the given args to the given file descriptor -//! \param fp The file descriptor to print to -//! \param fmtstr The string to format -//! \param args The args to format with -//! -template<typename... Args> -void fprint (FILE* fp, QString fmtstr, Args... args) -{ - formatHelper (fmtstr, args...); - fprintf (fp, "%s", qPrintable (fmtstr)); -} - -//! -//! Overload of \c fprint with a QIODevice -//! \param dev The IO device to print to -//! \param fmtstr The string to format -//! \param args The args to format with -//! -template<typename... Args> -void fprint (QIODevice& dev, QString fmtstr, Args... args) -{ - formatHelper (fmtstr, args...); - dev.write (fmtstr.toUtf8()); -} - -//! -//! Exactly like print() except no-op in release builds. -//! \param fmtstr The string to format -//! \param args The args to format with -//! -template<typename... Args> -void dprint (QString fmtstr, Args... args) -{ -#ifndef RELEASE - formatHelper (fmtstr, args...); - printToLog (fmtstr); -#else - (void) fmtstr; - (void) args; -#endif -}
--- a/src/GLRenderer.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2225 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "Configuration.h" -#include "Document.h" -#include "GLRenderer.h" -#include "Colors.h" -#include "MainWindow.h" -#include "Misc.h" -#include "EditHistory.h" -#include "Dialogs.h" -#include "AddObjectDialog.h" -#include "MessageLog.h" -#include "Primitives.h" -#include "misc/RingFinder.h" - -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 }, -}; - -// Matrix templates for circle drawing. 2 is substituted with -// the scale value, 1 is inverted to -1 if needed. -static const Matrix g_circleDrawMatrixTemplates[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, "#FFFFFF") -cfg (String, gl_maincolor, "#A0A0A0") -cfg (Float, gl_maincolor_alpha, 1.0) -cfg (String, gl_selectcolor, "#0080FF") -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) -cfg (Bool, gl_linelengths, true) -cfg (Bool, gl_drawangles, false) - -// 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 -}; - -// Definitions for visual axes, drawn on the screen -const struct LDGLAxis -{ - const QColor col; - const Vertex vert; -} g_GLAxes[3] = -{ - { QColor (255, 0, 0), Vertex (10000, 0, 0) }, // X - { QColor (80, 192, 0), Vertex (0, 10000, 0) }, // Y - { QColor (0, 160, 192), Vertex (0, 0, 10000) }, // Z -}; - -static bool g_glInvert = false; -static QList<int> g_warnedColors; - -// ============================================================================= -// -GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) -{ - m_isPicking = m_rangepick = false; - m_camera = (GL::EFixedCamera) gl_camera; - m_drawToolTip = false; - m_editMode = ESelectMode; - m_rectdraw = false; - m_panning = false; - setDocument (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) - { - QString iconname = format ("camera-%1", tr (g_CameraNames[cam]).toLower()); - - CameraIcon* info = &m_cameraIcons[cam]; - info->img = new QPixmap (getIcon (iconname)); - info->cam = cam; - } - - calcCameraIcons(); -} - -// ============================================================================= -// -GLRenderer::~GLRenderer() -{ - for (int i = 0; i < 6; ++i) - delete currentDocumentData().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()->id(); - - // 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 / 0x10000) % 0x100, - g = (i / 0x100) % 0x100, - b = i % 0x100; - - qglColor (QColor (r, g, b)); - return; - } - - if ((list == BFCFrontList || list == BFCBackList) && - obj->type() != LDObject::ELine && - obj->type() != LDObject::ECondLine) - { - if (list == GL::BFCFrontList) - qcol = QColor (40, 192, 0); - else - qcol = QColor (224, 0, 0); - } - else - { - if (obj->color() == maincolor) - qcol = getMainColor(); - else - { - LDColor* col = ::getColor (obj->color()); - - if (col) - qcol = col->faceColor; - } - - if (obj->color() == edgecolor) - { - LDColor* col; - - if (!gl_blackedges && obj->parent() && (col = ::getColor (obj->parent()->color()))) - qcol = col->edgeColor; - else - qcol = (m_darkbg == false) ? Qt::black : Qt::white; - } - - if (qcol.isValid() == false) - { - // The color was unknown. Use main color to make the object at least - // not appear pitch-black. - if (obj->color() != edgecolor) - qcol = getMainColor(); - - // Warn about the unknown colors, but only once. - for (int i : g_warnedColors) - if (obj->color() == i) - return; - - print ("%1: Unknown color %2!\n", __func__, obj->color()); - g_warnedColors << obj->color(); - return; - } - } - - int r = qcol.red(), - g = qcol.green(), - b = qcol.blue(), - a = qcol.alpha(); - - if (obj->topLevelParent()->isSelected()) - { - // Brighten it up for the select list. - QColor selcolor (gl_selectcolor); - r = (r + selcolor.red()) / 2; - g = (g + selcolor.green()) / 2; - b = (b + selcolor.blue()) / 2; - } - - 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 (document() == 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 : document()->objects()) - { - 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 : document()->objects()) - { - 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; - - roundToDecimals (cx, 4); - roundToDecimals (cy, 4); - - // Create the vertex from the coordinates - pos3d[axisX] = cx; - pos3d[axisY] = cy; - pos3d[3 - axisX - axisY] = getDepthValue(); - return pos3d; -} - -// ============================================================================= -// -// Inverse operation for the above - convert a 3D position to a 2D screen -// position. Don't ask me how this code manages to work, I don't even know. -// -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 (m_darkbg ? Qt::white : Qt::black); - 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 = currentDocumentData().overlays[m_camera]; - - if (overlay.img != null) - { - QPoint v0 = coordconv3_2 (currentDocumentData().overlays[m_camera].v0), - v1 = coordconv3_2 (currentDocumentData().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. - QString text = format (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 (textpen); - 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); - - // Mode-specific rendering - if (editMode() == EDrawMode) - { - QPoint poly[4]; - Vertex poly3d[4]; - int numverts = 4; - - // Calculate polygon data - if (!m_rectdraw) - { - numverts = m_drawedVerts.size() + 1; - int i = 0; - - for (Vertex& vert : m_drawedVerts) - poly3d[i++] = vert; - - // Draw the cursor vertex as the last one in the list. - if (numverts <= 4) - poly3d[i] = m_hoverpos; - else - numverts = 4; - } - else - { - // Get vertex information from m_rectverts - if (m_drawedVerts.size() > 0) - for (int i = 0; i < numverts; ++i) - poly3d[i] = m_rectverts[i]; - else - poly3d[0] = m_hoverpos; - } - - // Convert to 2D - for (int i = 0; i < numverts; ++i) - poly[i] = coordconv3_2 (poly3d[i]); - - if (numverts > 0) - { - // Draw the polygon-to-be - paint.setBrush (polybrush); - paint.drawPolygon (poly, numverts); - - // Draw vertex blips - for (int i = 0; i < numverts; ++i) - { - QPoint& blip = poly[i]; - paint.setPen (linepen); - drawBlip (paint, blip); - - // Draw their coordinates - paint.setPen (textpen); - paint.drawText (blip.x(), blip.y() - 8, poly3d[i].toString (true)); - } - - // Draw line lenghts and angle info if appropriate - if (numverts >= 2) - { - int numlines = (m_drawedVerts.size() == 1) ? 1 : m_drawedVerts.size() + 1; - paint.setPen (textpen); - - for (int i = 0; i < numlines; ++i) - { - const int j = (i + 1 < numverts) ? i + 1 : 0; - const int h = (i - 1 >= 0) ? i - 1 : numverts - 1; - - if (gl_linelengths) - { - const QString label = QString::number (poly3d[i].distanceTo (poly3d[j])); - QPoint origin = QLineF (poly[i], poly[j]).pointAt (0.5).toPoint(); - paint.drawText (origin, label); - } - - if (gl_drawangles) - { - QLineF l0 (poly[h], poly[i]), - l1 (poly[i], poly[j]); - - double angle = 180 - l0.angleTo (l1); - - if (angle < 0) - angle = 180 - l1.angleTo (l0); - - QString label = QString::number (angle) + QString::fromUtf8 (QByteArray ("\302\260")); - QPoint pos = poly[i]; - pos.setY (pos.y() + metrics.height()); - - paint.drawText (pos, label); - } - } - } - } - } - elif (editMode() == 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 = g_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]); - QString label = QString::number (dist0); - paint.setPen (textpen); - paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label); - - if (m_drawedVerts.size() >= 2) - { - label = QString::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] && editMode() != ESelectMode) - continue; - - paint.drawPixmap (info.destRect, *info.img, info.srcRect); - } - - QString formatstr = tr ("%1 Camera"); - - // Draw a label for the current camera in the bottom left corner - { - const int margin = 4; - - QString label; - label = format (formatstr, 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 - { - QString label = format (formatstr, tr (g_CameraNames[m_toolTipCamera])); - QToolTip::showText (m_globalpos, label); - } - } - } - - // Message log - if (messageLog()) - { - int y = 0; - const int margin = 2; - QColor penColor = textpen.color(); - - for (const MessageManager::Line& line : messageLog()->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); -} - -// ============================================================================= -// -void GLRenderer::compileAllObjects() -{ - if (!document()) - return; - - // Compiling all is a big job, use a busy cursor - setCursor (Qt::BusyCursor); - - m_knownVerts.clear(); - - for (LDObject* obj : document()->objects()) - 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->type() != LDObject::ECondLine) ? obj->vertices() : 2; - - if (g_glInvert == false) - for (int i = 0; i < numverts; ++i) - compileVertex (obj->vertex (i)); - else - for (int i = numverts - 1; i >= 0; --i) - compileVertex (obj->vertex (i)); - - glEnd(); -} - -// ============================================================================= -// -void GLRenderer::compileList (LDObject* obj, const GLRenderer::ListType list) -{ - setObjectColor (obj, list); - - switch (obj->type()) - { - case LDObject::ELine: - { - compileSubObject (obj, GL_LINES); - } break; - - case LDObject::ECondLine: - { - // 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::ETriangle: - { - compileSubObject (obj, GL_TRIANGLES); - } break; - - case LDObject::EQuad: - { - compileSubObject (obj, GL_QUADS); - } break; - - case LDObject::ESubfile: - { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - LDObjectList objs; - - objs = ref->inlineContents (LDSubfile::DeepCacheInline | LDSubfile::RendererInline); - bool oldinvert = g_glInvert; - - if (ref->transform().getDeterminant() < 0) - g_glInvert = !g_glInvert; - - LDObject* prev = ref->previous(); - - if (prev && prev->type() == LDObject::EBFC && static_cast<LDBFC*> (prev)->statement() == LDBFC::InvertNext) - g_glInvert = !g_glInvert; - - for (LDObject* obj : objs) - { - compileList (obj, list); - obj->destroy(); - } - - 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 (editMode() == EDrawMode) - { - 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 (editMode()) - { - 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 && editMode() != 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) -{ - 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) - { - LDObjectList 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(); - - 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]; - - // Read pixels from the color buffer. - glReadPixels (x0, m_height - 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); - assert (obj != null); - - // 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. - LDObjectList priorsel = selection(); - getCurrentDocument()->clearSelection(); - - for (LDObject* obj : priorsel) - compileObject (obj); - - g_win->updateSelection(); - m_drawedVerts.clear(); - } break; - } - - g_win->updateEditModeActions(); - update(); -} - -// ============================================================================= -// -void GLRenderer::setDocument (LDDocument* const& a) -{ - m_document = a; - - if (a != null) - { - initOverlaysFromObjects(); - - if (currentDocumentData().init == false) - { - resetAllAngles(); - currentDocumentData().init = true; - } - } -} - -// ============================================================================= -// -Matrix GLRenderer::getCircleDrawMatrix (double scale) -{ - Matrix transform = g_circleDrawMatrixTemplates[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; - LDObjectList objs; - - switch (editMode()) - { - 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 = g_lores, divs = g_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.findRings (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, g_lores, g_lores, cmp.num))) == null) - { - refFile = generatePrimitive (::Ring, g_lores, g_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) - { - for (LDObject* obj : objs) - { - document()->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->vertex (i); - } elif (obj->type() == LDObject::ESubfile) - { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline); - - for (LDObject* obj : objs) - { - verts << getVertices (obj); - obj->destroy(); - } - } - - 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->setGLInit (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->isGLInit()) - return; - - for (const GL::ListType listType : g_glListTypes) - glDeleteLists (obj->glLists[listType], 1); - - obj->setGLInit (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, QString file, int x, int y, int w, int h) -{ - QImage* img = new QImage (QImage (file).convertToFormat (QImage::Format_ARGB32)); - 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 = currentDocumentData().overlays[camera()]; - delete info.img; - info.img = null; - - updateOverlayObjects(); -} - -// ============================================================================= -// -void GLRenderer::setDepthValue (double depth) -{ - assert (camera() < EFreeCamera); - currentDocumentData().depthValues[camera()] = depth; -} - -// ============================================================================= -// -double GLRenderer::getDepthValue() const -{ - assert (camera() < EFreeCamera); - return currentDocumentData().depthValues[camera()]; -} - -// ============================================================================= -// -const char* GLRenderer::getCameraName() const -{ - return g_CameraNames[camera()]; -} - -// ============================================================================= -// -LDGLOverlay& GLRenderer::getOverlay (int newcam) -{ - return currentDocumentData().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 (document() == 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; - int runaway = 50; - - 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); - - while (--runaway) - { - 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; ++i) - { - if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white) - { - filled = true; - goto endOfLoop; - } - } - - // Left and right edges - for (int i = 0; i < h; ++i) - { - if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white) - { - filled = true; - goto endOfLoop; - } - } - -endOfLoop: - - 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) || editMode() != ESelectMode) - return; - - pick (ev->x(), ev->y()); - - if (selection().isEmpty()) - return; - - LDObject* obj = selection().first(); - AddObjectDialog::staticDialog (obj->type(), obj); - g_win->endAction(); - ev->accept(); -} - -// ============================================================================= -// -LDOverlay* GLRenderer::findOverlayObject (EFixedCamera cam) -{ - LDOverlay* ovlobj = null; - - for (LDObject* obj : document()->objects()) - { - if (obj->type() == LDObject::EOverlay && static_cast<LDOverlay*> (obj)->camera() == 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 = currentDocumentData().overlays[cam]; - LDOverlay* ovlobj = findOverlayObject (cam); - - if (!ovlobj && meta.img) - { - delete meta.img; - meta.img = null; - } - elif (ovlobj && (!meta.img || meta.fname != ovlobj->fileName())) - setupOverlay (cam, ovlobj->fileName(), ovlobj->x(), - ovlobj->y(), ovlobj->width(), ovlobj->height()); - } -} - -// ============================================================================= -// -void GLRenderer::updateOverlayObjects() -{ - for (EFixedCamera cam : g_Cameras) - { - if (cam == EFreeCamera) - continue; - - LDGLOverlay& meta = currentDocumentData().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->type() == LDObject::EEmpty) - nextobj->destroy(); - - // If the overlay object was there and the overlay itself is - // not, remove the object. - ovlobj->destroy(); - } 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 < document()->getObjectCount(); ++i) - { - LDObject* obj = document()->getObject (i); - - if (obj->isScemantic()) - { - found = true; - break; - } - - if (obj->type() == LDObject::EOverlay) - lastOverlay = i; - } - - if (lastOverlay != -1) - document()->insertObj (lastOverlay + 1, ovlobj); - else - { - document()->insertObj (i, ovlobj); - - if (found) - document()->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/GLRenderer.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,311 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QGLWidget> -#include "Main.h" -#include "Macros.h" -#include "LDObject.h" -#include "Document.h" - -class MessageManager; -class QDialogButtonBox; -class RadioGroup; -class QDoubleSpinBox; -class QSpinBox; -class QLineEdit; -class QTimer; - -enum EditMode -{ - ESelectMode, - EDrawMode, - ECircleMode, -}; - -// Meta for overlays -struct LDGLOverlay -{ - Vertex v0, - v1; - int ox, - oy; - double lw, - lh; - QString fname; - QImage* img; -}; - -struct LDFixedCameraInfo -{ - const char glrotate[3]; - const Axis axisX, - axisY; - const bool negX, - negY; -}; - -// ============================================================================= -// Document-specific data -// -struct LDGLData -{ - double rotX, - rotY, - rotZ, - panX[7], - panY[7], - zoom[7]; - double depthValues[6]; - LDGLOverlay overlays[6]; - bool init; - - LDGLData() - { - for (int i = 0; i < 6; ++i) - { - overlays[i].img = null; - depthValues[i] = 0.0f; - } - - init = false; - } -}; - -// ============================================================================= -// The main renderer object, draws the brick on the screen, manages the camera -// and selection picking. The instance of GLRenderer is accessible as -// g_win->R() -// -class GLRenderer : public QGLWidget -{ - public: - enum EFixedCamera - { - ETopCamera, - EFrontCamera, - ELeftCamera, - EBottomCamera, - EBackCamera, - ERightCamera, - EFreeCamera - }; - - enum ListType - { - NormalList, - PickList, - BFCFrontList, - BFCBackList - }; - - // CameraIcon::img is a heap-allocated QPixmap because otherwise it gets - // initialized before program gets to main() and constructs a QApplication - // and Qt doesn't like that. - struct CameraIcon - { - QPixmap* img; - QRect srcRect, - destRect, - selRect; - EFixedCamera cam; - }; - - Q_OBJECT - PROPERTY (public, bool, isDrawOnly, setDrawOnly, STOCK_WRITE) - PROPERTY (public, MessageManager*, messageLog, setMessageLog, STOCK_WRITE) - PROPERTY (private, bool, isPicking, setPicking, STOCK_WRITE) - PROPERTY (public, LDDocument*, document, setDocument, CUSTOM_WRITE) - PROPERTY (public, EditMode, editMode, setEditMode, CUSTOM_WRITE) - - public: - GLRenderer (QWidget* parent = null); - ~GLRenderer(); - - inline EFixedCamera camera() const - { - return m_camera; - } - - void clearOverlay(); - void compileObject (LDObject* obj); - void compileAllObjects(); - void drawGLScene(); - void endDraw (bool accept); - Axis getCameraAxis (bool y, EFixedCamera camid = (EFixedCamera) - 1); - const char* getCameraName() const; - double getDepthValue() const; - QColor getMainColor(); - LDGLOverlay& getOverlay (int newcam); - uchar* getScreencap (int& w, int& h); - void hardRefresh(); - void initGLData(); - void initOverlaysFromObjects(); - void refresh(); - void resetAngles(); - void resetAllAngles(); - void setBackground(); - void setCamera (const EFixedCamera cam); - void setDepthValue (double depth); - bool setupOverlay (EFixedCamera cam, QString file, int x, int y, int w, int h); - void updateOverlayObjects(); - void zoomNotch (bool inward); - void zoomToFit(); - void zoomAllToFit(); - - static void deleteLists (LDObject* obj); - - protected: - void contextMenuEvent (QContextMenuEvent* ev); - void initializeGL(); - void keyPressEvent (QKeyEvent* ev); - void keyReleaseEvent (QKeyEvent* ev); - void leaveEvent (QEvent* ev); - void mouseDoubleClickEvent (QMouseEvent* ev); - void mousePressEvent (QMouseEvent* ev); - void mouseMoveEvent (QMouseEvent* ev); - void mouseReleaseEvent (QMouseEvent* ev); - void paintEvent (QPaintEvent* ev); - void resizeGL (int w, int h); - void wheelEvent (QWheelEvent* ev); - - private: - CameraIcon m_cameraIcons[7]; - QTimer* m_toolTipTimer; - Qt::MouseButtons m_lastButtons; - Qt::KeyboardModifiers m_keymods; - Vertex m_hoverpos; - double m_virtWidth, - m_virtHeight; - bool m_darkbg, - m_rangepick, - m_addpick, - m_drawToolTip, - m_screencap, - m_panning; - QPoint m_pos, - m_globalpos, - m_rangeStart; - QPen m_thickBorderPen, - m_thinBorderPen; - EFixedCamera m_camera, - m_toolTipCamera; - GLuint m_axeslist; - int m_width, - m_height, - m_totalmove; - QList<Vertex> m_drawedVerts; - bool m_rectdraw; - Vertex m_rectverts[4]; - QColor m_bgcolor; - QList<Vertex> m_knownVerts; - - void addDrawnVertex (Vertex m_hoverpos); - LDOverlay* findOverlayObject (EFixedCamera cam); - void updateRectVerts(); - void getRelativeAxes (Axis& relX, Axis& relY) const; - Matrix getCircleDrawMatrix (double scale); - void drawBlip (QPainter& paint, QPoint pos) const; - - // Compute geometry for camera icons - void calcCameraIcons(); - - // How large is the circle we're drawing right now? - double getCircleDrawDist (int pos) const; - - // Clamps an angle to [0, 360] - void clampAngle (double& angle) const; - - // Compile one of the lists of an object - void compileList (LDObject* obj, const ListType list); - - // Sub-routine for object compiling - void compileSubObject (LDObject* obj, const GLenum gltype); - - // Compile a single vertex to a list - void compileVertex (const Vertex& vrt); - - // Convert a 2D point to a 3D point - Vertex coordconv2_3 (const QPoint& pos2d, bool snap) const; - - // Convert a 3D point to a 2D point - QPoint coordconv3_2 (const Vertex& pos3d) const; - - // Perform object selection - void pick (int mouseX, int mouseY); - - // Set the color to an object list - void setObjectColor (LDObject* obj, const ListType list); - - LDGLData& currentDocumentData() const - { - return *document()->getGLData(); - } - - // Get a rotation value - inline double& rot (Axis ax) - { - return - (ax == X) ? currentDocumentData().rotX : - (ax == Y) ? currentDocumentData().rotY : - currentDocumentData().rotZ; - } - - // Get a panning value - inline double& pan (Axis ax) - { - return (ax == X) ? currentDocumentData().panX[camera()] : - currentDocumentData().panY[camera()]; - } - - // Same except const (can be used in const methods) - inline const double& pan (Axis ax) const - { - return (ax == X) ? currentDocumentData().panX[camera()] : - currentDocumentData().panY[camera()]; - } - - // Get the zoom value - inline double& zoom() - { - return currentDocumentData().zoom[camera()]; - } - - template<typename... Args> - inline QString format (QString fmtstr, Args... args) - { - return ::format (fmtstr, args...); - } - - private slots: - void slot_toolTipTimer(); -}; - -// Alias for short namespaces -typedef GLRenderer GL; - -static const GLRenderer::ListType g_glListTypes[] = -{ - GL::NormalList, - GL::PickList, - GL::BFCFrontList, - GL::BFCBackList, -}; - -extern const GL::EFixedCamera g_Cameras[7]; -extern const char* g_CameraNames[7];
--- a/src/LDConfig.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,215 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 <QFile> -#include "Document.h" -#include "LDConfig.h" -#include "MainWindow.h" -#include "Misc.h" -#include "Colors.h" - -// ============================================================================= -// -// Helper function for parseLDConfig -// -static bool parseLDConfigTag (LDConfigParser& pars, char const* tag, QString& 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() -{ - QFile* fp = openLDrawFile ("LDConfig.ldr", false); - - if (!fp) - { - critical (QObject::tr ("Unable to open LDConfig.ldr for parsing.")); - return; - } - - // Read in the lines - while (fp->atEnd() == false) - { - QString line = QString::fromUtf8 (fp->readLine()); - - if (line.isEmpty() || line[0] != '0') - continue; // empty or illogical - - line.remove ('\r'); - line.remove ('\n'); - - // Parse the line - LDConfigParser pars (line, ' '); - - int code = 0, alpha = 255; - QString 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); - } - - fp->close(); - fp->deleteLater(); -} - -// ============================================================================= -// -LDConfigParser::LDConfigParser (QString 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 (QString& val, const int pos) -{ - if (pos >= m_tokens.size()) - return false; - - val = m_tokens[pos]; - return true; -} - -// ============================================================================= -// -bool LDConfigParser::getNextToken (QString& val) -{ - return getToken (val, ++m_pos); -} - -// ============================================================================= -// -bool LDConfigParser::peekNextToken (QString& 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) -{ - QString tok; - - if (!getToken (tok, inPos)) - return false; - - return (tok == sOther); -}
--- a/src/LDConfig.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include "Types.h" -#include <QStringList> - -// ============================================================================ -// -// String parsing utility for parsing ldconfig.ldr -// -class LDConfigParser -{ - public: - LDConfigParser (QString inText, char sep); - - bool isAtEnd(); - bool isAtBeginning(); - bool getNextToken (QString& val); - bool peekNextToken (QString& val); - bool getToken (QString& val, const int pos); - bool findToken (int& result, char const* needle, int args); - int getSize(); - void rewind(); - void seek (int amount, bool rel); - bool tokenCompare (int inPos, const char* sOther); - - inline QString operator[] (const int idx) - { - return m_tokens[idx]; - } - - private: - QStringList m_tokens; - int m_pos; -}; - -void parseLDConfig();
--- a/src/LDObject.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,825 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "LDObject.h" -#include "Document.h" -#include "Misc.h" -#include "MainWindow.h" -#include "EditHistory.h" -#include "GLRenderer.h" -#include "Colors.h" - -cfg (String, ld_defaultname, ""); -cfg (String, ld_defaultuser, ""); -cfg (Int, ld_defaultlicense, 0); - -// List of all LDObjects -static LDObjectList g_LDObjects; - -// ============================================================================= -// LDObject constructors -// -LDObject::LDObject() : - m_isHidden (false), - m_isSelected (false), - m_parent (null), - m_document (null), - m_isGLInit (false), - qObjListEntry (null) -{ - memset (m_coords, 0, sizeof m_coords); - chooseID(); - g_LDObjects << this; -} - -// ============================================================================= -// -void LDObject::chooseID() -{ - int32 id = 1; // 0 shalt be null - - for (LDObject* obj : g_LDObjects) - { - assert (obj != this); - - if (obj->id() >= id) - id = obj->id() + 1; - } - - setID (id); -} - -// ============================================================================= -// -void LDObject::setVertexCoord (int i, Axis ax, double value) -{ - Vertex v = vertex (i); - v[ax] = value; - setVertex (i, v); -} - -LDError::LDError() {} - -// ============================================================================= -// -QString LDComment::asText() const -{ - return format ("0 %1", text()); -} - -// ============================================================================= -// -QString LDSubfile::asText() const -{ - QString val = format ("1 %1 %2 ", color(), position()); - val += transform().toString(); - val += ' '; - val += fileInfo()->name(); - return val; -} - -// ============================================================================= -// -QString LDLine::asText() const -{ - QString val = format ("2 %1", color()); - - for (int i = 0; i < 2; ++i) - val += format (" %1", vertex (i)); - - return val; -} - -// ============================================================================= -// -QString LDTriangle::asText() const -{ - QString val = format ("3 %1", color()); - - for (int i = 0; i < 3; ++i) - val += format (" %1", vertex (i)); - - return val; -} - -// ============================================================================= -// -QString LDQuad::asText() const -{ - QString val = format ("4 %1", color()); - - for (int i = 0; i < 4; ++i) - val += format (" %1", vertex (i)); - - return val; -} - -// ============================================================================= -// -QString LDCondLine::asText() const -{ - QString val = format ("5 %1", color()); - - // Add the coordinates - for (int i = 0; i < 4; ++i) - val += format (" %1", vertex (i)); - - return val; -} - -// ============================================================================= -// -QString LDError::asText() const -{ - return contents(); -} - -// ============================================================================= -// -QString LDVertex::asText() const -{ - return format ("0 !LDFORGE VERTEX %1 %2", color(), pos); -} - -// ============================================================================= -// -QString LDEmpty::asText() const -{ - return ""; -} - -// ============================================================================= -// -const char* LDBFC::k_statementStrings[] = -{ - "CERTIFY CCW", - "CCW", - "CERTIFY CW", - "CW", - "NOCERTIFY", - "INVERTNEXT", - "CLIP", - "CLIP CCW", - "CLIP CW", - "NOCLIP", -}; - -QString LDBFC::asText() const -{ - return format ("0 BFC %1", LDBFC::k_statementStrings[m_statement]); -} - -// ============================================================================= -// -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 (vertex (0), vertex (1), vertex (3)); - LDTriangle* tri2 = new LDTriangle (vertex (1), vertex (2), vertex (3)); - - // The triangles also inherit the quad's color - tri1->setColor (color()); - tri2->setColor (color()); - - QList<LDTriangle*> triangles; - triangles << tri1; - triangles << tri2; - return triangles; -} - -// ============================================================================= -// -void LDObject::replace (LDObject* other) -{ - long idx = lineNumber(); - assert (idx != -1); - - // Replace the instance of the old object with the new object - document()->setObject (idx, other); - - // Remove the old object - destroy(); -} - -// ============================================================================= -// -void LDObject::swap (LDObject* other) -{ - assert (document() == other->document()); - document()->swapObjects (this, other); -} - -// ============================================================================= -// -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() {} - -// ============================================================================= -// -LDSubfile::~LDSubfile() {} - -// ============================================================================= -// -void LDObject::destroy() -{ - // 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 (document()) - document()->forgetObject (this); - - // Delete the GL lists - GL::deleteLists (this); - - // Remove this object from the list of LDObjects - g_LDObjects.removeOne (this); - - delete this; -} - -// ============================================================================= -// -static void transformObject (LDObject* obj, Matrix transform, Vertex pos, int parentcolor) -{ - switch (obj->type()) - { - case LDObject::ELine: - case LDObject::ECondLine: - case LDObject::ETriangle: - case LDObject::EQuad: - - for (int i = 0; i < obj->vertices(); ++i) - { - Vertex v = obj->vertex (i); - v.transform (transform, pos); - obj->setVertex (i, v); - } - - break; - - case LDObject::ESubfile: - { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - Matrix newMatrix = transform * ref->transform(); - Vertex newpos = ref->position(); - - newpos.transform (transform, pos); - ref->setPosition (newpos); - ref->setTransform (newMatrix); - } - break; - - default: - break; - } - - if (obj->color() == maincolor) - obj->setColor (parentcolor); -} - -// ============================================================================= -// -LDObjectList LDSubfile::inlineContents (InlineFlags flags) -{ - LDObjectList objs = fileInfo()->inlineContents (flags); - - // Transform the objects - for (LDObject* obj : objs) - { - // Set the parent now so we know what inlined the object. - obj->setParent (this); - transformObject (obj, transform(), position(), color()); - } - - return objs; -} - -// ============================================================================= -// -long LDObject::lineNumber() const -{ - assert (document() != null); - - for (int i = 0; i < document()->getObjectCount(); ++i) - if (document()->getObject (i) == this) - return i; - - return -1; -} - -// ============================================================================= -// -void LDObject::moveObjects (LDObjectList 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; - LDObjectList objsToCompile; - LDDocument* file = objs[0]->document(); - - for (long i = start; i != end; i += incr) - { - LDObject* obj = objs[i]; - - const long idx = obj->lineNumber(), - target = idx + (up ? -1 : 1); - - if ((up && idx == 0) || (!up && idx == (long) (file->objects().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); -} - -// ============================================================================= -// -QString LDObject::typeName (LDObject::Type type) -{ - LDObject* obj = LDObject::getDefault (type); - QString name = obj->typeName(); - obj->destroy(); - return name; -} - -// ============================================================================= -// -QString LDObject::describeObjects (const LDObjectList& objs) -{ - bool firstDetails = true; - QString text = ""; - - if (objs.isEmpty()) - return "nothing"; // :) - - for (long i = 0; i < ENumTypes; ++i) - { - Type objType = (Type) i; - int count = 0; - - for (LDObject * obj : objs) - if (obj->type() == objType) - count++; - - if (count == 0) - continue; - - if (!firstDetails) - text += ", "; - - QString noun = format ("%1%2", typeName (objType), plural (count)); - - // Plural of "vertex" is "vertices", correct that - if (objType == EVertex && count != 1) - noun = "vertices"; - - text += format ("%1 %2", count, noun); - firstDetails = false; - } - - return text; -} - -// ============================================================================= -// -LDObject* LDObject::topLevelParent() -{ - if (parent() == null) - return this; - - LDObject* it = this; - - while (it->parent() != null) - it = it->parent(); - - return it; -} - -// ============================================================================= -// -LDObject* LDObject::next() const -{ - long idx = lineNumber(); - assert (idx != -1); - - if (idx == (long) document()->getObjectCount() - 1) - return null; - - return document()->getObject (idx + 1); -} - -// ============================================================================= -// -LDObject* LDObject::previous() const -{ - long idx = lineNumber(); - assert (idx != -1); - - if (idx == 0) - return null; - - return document()->getObject (idx - 1); -} - -// ============================================================================= -// -void LDObject::move (Vertex vect) -{ - if (hasMatrix()) - { - LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (this); - mo->setPosition (mo->position() + vect); - } - elif (type() == LDObject::EVertex) - { - // ugh - static_cast<LDVertex*> (this)->pos += vect; - } - else - { - for (int i = 0; i < vertices(); ++i) - setVertex (i, vertex (i) + vect); - } -} - -// ============================================================================= -// -#define CHECK_FOR_OBJ(N) \ - if (type == LDObject::E##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 = vertex (1); - setVertex (1, vertex (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 = vertex (1); - setVertex (1, vertex (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 = lineNumber(); - - if (idx > 0) - { - LDBFC* bfc = dynamic_cast<LDBFC*> (previous()); - - if (bfc && bfc->statement() == LDBFC::InvertNext) - { - // This is prefixed with an invertnext, thus remove it. - bfc->destroy(); - return; - } - } - - // Not inverted, thus prefix it with a new invertnext. - LDBFC* bfc = new LDBFC (LDBFC::InvertNext); - document()->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->vertex (0); - line->setVertex (0, line->vertex (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, vertex (i)); - - repl->setColor (color()); - - replace (repl); - return repl; -} - -// ============================================================================= -// -LDObject* LDObject::fromID (int id) -{ - for (LDObject* obj : g_LDObjects) - if (obj->id() == id) - return obj; - - return null; -} - -// ============================================================================= -// -QString LDOverlay::asText() const -{ - return format ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6", - fileName(), camera(), x(), y(), width(), height()); -} - -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->document() && (idx = obj->lineNumber()) != -1) - { - QString before = obj->asText(); - *ptr = val; - QString after = obj->asText(); - - if (before != after) - obj->document()->addToHistory (new EditHistory (idx, before, after)); - } - else - *ptr = val; -} - -// ============================================================================= -// -void LDObject::setColor (const int& val) -{ - changeProperty (this, &m_color, val); -} - -// ============================================================================= -// -const Vertex& LDObject::vertex (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 (linkPointer(), &m_position, LDSharedVertex::getSharedVertex (a)); -} - -// ============================================================================= -// -void LDMatrixObject::setTransform (const Matrix& val) -{ - changeProperty (linkPointer(), &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() -{ - assert (document() != null); - document()->addToSelection (this); -} - -// ============================================================================= -// -void LDObject::unselect() -{ - assert (document() != null); - document()->removeFromSelection (this); -} - -// ============================================================================= -// -QString getLicenseText (int id) -{ - switch (id) - { - case 0: - return g_CALicense; - - case 1: - return g_nonCALicense; - - case 2: - return ""; - } - - assert (false); - return ""; -} - -// ============================================================================= -// -LDObject* LDObject::createCopy() const -{ - /* - LDObject* copy = clone(); - copy->setFile (null); - copy->setGLInit (false); - copy->chooseID(); - copy->setSelected (false); - */ - - /* - LDObject* copy = getDefault (getType()); - copy->setColor (color()); - - if (hasMatrix()) - { - LDMatrixObject* copyMo = static_cast<LDMatrixObject*> (copy); - const LDMatrixObject* mo = static_cast<const LDMatrixObject*> (this); - copyMo->setPosition (mo->getPosition()); - copyMo->setTransform (mo->transform()); - } - else - { - for (int i = 0; i < vertices(); ++i) - copy->setVertex (getVertex (i)); - } - - switch (getType()) - { - case Subfile: - { - LDSubfile* copyRef = static_cast<LDSubfile*> (copy); - const LDSubfile* ref = static_cast<const LDSubfile*> (this); - - copyRef->setFileInfo (ref->fileInfo()); - } - } - */ - - LDObject* copy = parseLine (asText()); - return copy; -} \ No newline at end of file
--- a/src/LDObject.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,553 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include "Main.h" -#include "Types.h" -#include "misc/DocumentPointer.h" - -#define LDOBJ(T) \ -protected: \ - virtual LD##T* clone() override \ - { \ - return new LD##T (*this); \ - } \ - \ -public: \ - virtual LDObject::Type type() const override \ - { \ - return LDObject::E##T; \ - } \ - virtual QString asText() const override; \ - virtual void invert() override; - -#define LDOBJ_NAME(N) virtual QString typeName() const override { return #N; } -#define LDOBJ_VERTICES(V) virtual int vertices() const override { return V; } -#define LDOBJ_SETCOLORED(V) virtual bool isColored() const override { return V; } -#define LDOBJ_COLORED LDOBJ_SETCOLORED (true) -#define LDOBJ_UNCOLORED LDOBJ_SETCOLORED (false) - -#define LDOBJ_CUSTOM_SCEMANTIC virtual bool isScemantic() const override -#define LDOBJ_SCEMANTIC LDOBJ_CUSTOM_SCEMANTIC { return true; } -#define LDOBJ_NON_SCEMANTIC LDOBJ_CUSTOM_SCEMANTIC { return false; } - -#define LDOBJ_SETMATRIX(V) virtual bool hasMatrix() const override { return V; } -#define LDOBJ_HAS_MATRIX LDOBJ_SETMATRIX (true) -#define LDOBJ_NO_MATRIX LDOBJ_SETMATRIX (false) - -class QListWidgetItem; -class LDSubfile; -class LDDocument; -class LDSharedVertex; - -// ============================================================================= -// LDObject -// -// Base class object for all object types. Each LDObject represents a single line -// in the LDraw code file. The virtual method getType returns an enumerator -// which is a token of the object's type. The object can be casted into -// sub-classes based on this enumerator. -// ============================================================================= -class LDObject -{ - PROPERTY (public, bool, isHidden, setHidden, STOCK_WRITE) - PROPERTY (public, bool, isSelected, setSelected, STOCK_WRITE) - PROPERTY (public, LDObject*, parent, setParent, STOCK_WRITE) - PROPERTY (public, LDDocument*, document, setDocument, STOCK_WRITE) - PROPERTY (private, int, id, setID, STOCK_WRITE) - PROPERTY (public, int, color, setColor, CUSTOM_WRITE) - PROPERTY (public, bool, isGLInit, setGLInit, STOCK_WRITE) - - public: - // Object type codes. - enum Type - { - ESubfile, // Object represents a sub-file reference - EQuad, // Object represents a quadrilateral - ETriangle, // Object represents a triangle - ELine, // Object represents a line - ECondLine, // Object represents a conditional line - EVertex, // Object is a vertex, LDForge extension object - EBFC, // Object represents a BFC statement - EOverlay, // Object contains meta-info about an overlay image. - EComment, // Object represents a comment - EError, // Object is the result of failed parsing - EEmpty, // Object represents an empty line - EUnidentified, // Unknown object type (some functions return this; TODO: they probably should not) - ENumTypes // Amount of object types - }; - - LDObject(); - - // Makes a copy of this object - LDObject* createCopy() const; - - // Deletes this object - void destroy(); - - // Index (i.e. line number) of this object - long lineNumber() const; - - // Type enumerator of this object - virtual Type type() const = 0; - - // Get a vertex by index - const Vertex& vertex (int i) const; - - // Type name of this object - virtual QString typeName() const = 0; - - // Does this object have a matrix and position? (see LDMatrixObject) - virtual bool hasMatrix() const = 0; - - // Inverts this object (winding is reversed) - virtual void invert() = 0; - - // Is this object colored? - virtual bool isColored() const = 0; - - // Does this object have meaning in the part model? - virtual bool isScemantic() const = 0; - - // Moves this object using the given vertex as a movement List - void move (Vertex vect); - - // Object after this in the current file - LDObject* next() const; - - // Object prior to this in the current file - LDObject* previous() const; - - // This object as LDraw code - virtual QString asText() const = 0; - - // Replace this LDObject with another LDObject. Object is deleted in the process. - void replace (LDObject* other); - - // Selects this object. - void select(); - - // Set a vertex to the given value - void setVertex (int i, const Vertex& vert); - - // Set a single coordinate of a vertex - void setVertexCoord (int i, Axis ax, double value); - - // Swap this object with another. - void swap (LDObject* other); - - // What object in the current file ultimately references this? - LDObject* topLevelParent(); - - // Removes this object from selection // TODO: rename to deselect? - void unselect(); - - // Number of vertices this object has // TODO: rename to getNumVertices - virtual int vertices() const = 0; - - // Get type name by enumerator - static QString typeName (LDObject::Type type); - - // Returns a default-constructed LDObject by the given type - static LDObject* getDefault (const LDObject::Type type); - - // TODO: move this to LDDocument? - static void moveObjects (LDObjectList objs, const bool up); - - // Get a description of a list of LDObjects - static QString describeObjects (const LDObjectList& objs); - static LDObject* fromID (int id); - - // TODO: make these private! - // OpenGL list for this object - uint glLists[4]; - - // Object list entry for this object - QListWidgetItem* qObjListEntry; - - protected: - // LDObjects are to be deleted with the deleteSelf() method, not with - // operator delete. This is because it seems virtual functions cannot - // be properly called from the destructor, thus a normal method must - // be used instead. The destructor also doesn't seem to be able to - // be private without causing a truckload of problems so it's protected - // instead. - virtual ~LDObject(); - void chooseID(); - - private: - virtual LDObject* clone() = 0; - LDSharedVertex* m_coords[4]; -}; - -// ============================================================================= -// LDSharedVertex -// -// For use as coordinates of LDObjects. Keeps count of references. -// ============================================================================= -class LDSharedVertex -{ - public: - inline const Vertex& data() const - { - return m_data; - } - - inline operator const Vertex&() const - { - return m_data; - } - - void addRef (LDObject* a); - void delRef (LDObject* a); - - static LDSharedVertex* getSharedVertex (const Vertex& a); - - protected: - LDSharedVertex (const Vertex& a) : m_data (a) {} - - private: - LDObjectList m_refs; - Vertex m_data; -}; - -// ============================================================================= -// -// Common code for objects with matrices. This class is multiple-derived in -// and thus not used directly other than as a common storage point for matrices -// and vertices. -// -// The link pointer is a pointer to this object's LDObject self - since this is -// multiple-derived in, static_cast or dynamic_cast won't budge here. -// -// In 0.1-alpha, there was a separate 'radial' type which had a position and -// matrix as well. Even though right now only LDSubfile uses this, I'm keeping -// this class distinct in case I get new extension ideas. :) -// -class LDMatrixObject -{ - PROPERTY (public, LDObject*, linkPointer, setLinkPointer, STOCK_WRITE) - PROPERTY (public, Matrix, transform, setTransform, CUSTOM_WRITE) - - public: - LDMatrixObject() : - m_position (LDSharedVertex::getSharedVertex (g_origin)) {} - - LDMatrixObject (const Matrix& transform, const Vertex& pos) : - m_transform (transform), - m_position (LDSharedVertex::getSharedVertex (pos)) {} - - inline const Vertex& position() const - { - return m_position->data(); - } - - void setCoordinate (const Axis ax, double value) - { - Vertex v = position(); - v[ax] = value; - setPosition (v); - } - - void setPosition (const Vertex& a); - - private: - LDSharedVertex* m_position; -}; - -// ============================================================================= -// -// Represents a line in the LDraw file that could not be properly parsed. It is -// represented by a (!) ERROR in the code view. It exists for the purpose of -// allowing garbage lines be debugged and corrected within LDForge. -// -class LDError : public LDObject -{ - LDOBJ (Error) - LDOBJ_NAME (error) - LDOBJ_VERTICES (0) - LDOBJ_UNCOLORED - LDOBJ_SCEMANTIC - LDOBJ_NO_MATRIX - PROPERTY (public, QString, fileReferenced, setFileReferenced, STOCK_WRITE) - PROPERTY (private, QString, contents, setContents, STOCK_WRITE) - PROPERTY (private, QString, reason, setReason, STOCK_WRITE) - - public: - LDError(); - LDError (QString contents, QString reason) : - m_contents (contents), - m_reason (reason) {} -}; - -// ============================================================================= -// -// Represents an empty line in the LDraw code file. -// -class LDEmpty : public LDObject -{ - LDOBJ (Empty) - LDOBJ_NAME (empty) - LDOBJ_VERTICES (0) - LDOBJ_UNCOLORED - LDOBJ_NON_SCEMANTIC - LDOBJ_NO_MATRIX -}; - -// ============================================================================= -// -// Represents a code-0 comment in the LDraw code file. -// -class LDComment : public LDObject -{ - PROPERTY (public, QString, text, setText, STOCK_WRITE) - LDOBJ (Comment) - LDOBJ_NAME (comment) - LDOBJ_VERTICES (0) - LDOBJ_UNCOLORED - LDOBJ_NON_SCEMANTIC - LDOBJ_NO_MATRIX - - public: - LDComment() {} - LDComment (QString text) : m_text (text) {} -}; - -// ============================================================================= -// -// Represents a 0 BFC statement in the LDraw code. eStatement contains the type -// of this statement. -// -class LDBFC : public LDObject -{ - public: - enum Statement - { - CertifyCCW, - CCW, - CertifyCW, - CW, - NoCertify, - InvertNext, - Clip, - ClipCCW, - ClipCW, - NoClip, - NumStatements - }; - - LDOBJ (BFC) - LDOBJ_NAME (bfc) - LDOBJ_VERTICES (0) - LDOBJ_UNCOLORED - LDOBJ_CUSTOM_SCEMANTIC { return (statement() == InvertNext); } - LDOBJ_NO_MATRIX - PROPERTY (public, Statement, statement, setStatement, STOCK_WRITE) - - public: - LDBFC() {} - LDBFC (const LDBFC::Statement type) : - m_statement (type) {} - - // Statement strings - static const char* k_statementStrings[]; -}; - -// ============================================================================= -// LDSubfile -// -// Represents a single code-1 subfile reference. -// ============================================================================= -class LDSubfile : public LDObject, public LDMatrixObject -{ - LDOBJ (Subfile) - LDOBJ_NAME (subfile) - LDOBJ_VERTICES (0) - LDOBJ_COLORED - LDOBJ_SCEMANTIC - LDOBJ_HAS_MATRIX - PROPERTY (public, LDDocumentPointer, fileInfo, setFileInfo, STOCK_WRITE) - - public: - enum InlineFlag - { - DeepInline = (1 << 0), - CacheInline = (1 << 1), - RendererInline = (1 << 2), - DeepCacheInline = (DeepInline | CacheInline), - }; - - Q_DECLARE_FLAGS (InlineFlags, InlineFlag) - - LDSubfile() - { - setLinkPointer (this); - } - - // Inlines this subfile. Note that return type is an array of heap-allocated - // LDObject copies, they must be deleted manually. - LDObjectList inlineContents (InlineFlags flags); - - protected: - ~LDSubfile(); -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS (LDSubfile::InlineFlags) - -// ============================================================================= -// LDLine -// -// Represents a single code-2 line in the LDraw code file. v0 and v1 are the end -// points of the line. The line is colored with dColor unless uncolored mode is -// set. -// ============================================================================= -class LDLine : public LDObject -{ - LDOBJ (Line) - LDOBJ_NAME (line) - LDOBJ_VERTICES (2) - LDOBJ_COLORED - LDOBJ_SCEMANTIC - LDOBJ_NO_MATRIX - - public: - LDLine() {} - LDLine (Vertex v1, Vertex v2); -}; - -// ============================================================================= -// LDCondLine -// -// Represents a single code-5 conditional line. The end-points v0 and v1 are -// inherited from LDLine, c0 and c1 are the control points of this line. -// ============================================================================= -class LDCondLine : public LDLine -{ - LDOBJ (CondLine) - LDOBJ_NAME (condline) - LDOBJ_VERTICES (4) - LDOBJ_COLORED - LDOBJ_SCEMANTIC - LDOBJ_NO_MATRIX - - public: - LDCondLine() {} - LDLine* demote(); -}; - -// ============================================================================= -// LDTriangle -// -// Represents a single code-3 triangle in the LDraw code file. Vertices v0, v1 -// and v2 contain the end-points of this triangle. dColor is the color the -// triangle is colored with. -// ============================================================================= -class LDTriangle : public LDObject -{ - LDOBJ (Triangle) - LDOBJ_NAME (triangle) - LDOBJ_VERTICES (3) - LDOBJ_COLORED - LDOBJ_SCEMANTIC - LDOBJ_NO_MATRIX - - public: - LDTriangle() {} - LDTriangle (Vertex v0, Vertex v1, Vertex v2) - { - setVertex (0, v0); - setVertex (1, v1); - setVertex (2, v2); - } -}; - -// ============================================================================= -// LDQuad -// -// Represents a single code-4 quadrilateral. v0, v1, v2 and v3 are the end points -// of the quad, dColor is the color used for the quad. -// ============================================================================= -class LDQuad : public LDObject -{ - LDOBJ (Quad) - LDOBJ_NAME (quad) - LDOBJ_VERTICES (4) - LDOBJ_COLORED - LDOBJ_SCEMANTIC - LDOBJ_NO_MATRIX - - public: - LDQuad() {} - LDQuad (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3); - - // Split this quad into two triangles (note: heap-allocated) - QList<LDTriangle*> splitToTriangles(); -}; - -// ============================================================================= -// LDVertex -// -// The vertex is an LDForce-specific extension which represents a single -// vertex which can be used as a parameter to tools or to store coordinates -// with. Vertices are a part authoring tool and they should not appear in -// finished parts. -// ============================================================================= -class LDVertex : public LDObject -{ - LDOBJ (Vertex) - LDOBJ_NAME (vertex) - LDOBJ_VERTICES (0) // TODO: move pos to vaCoords[0] - LDOBJ_COLORED - LDOBJ_NON_SCEMANTIC - LDOBJ_NO_MATRIX - - public: - LDVertex() {} - - Vertex pos; -}; - -// ============================================================================= -// LDOverlay -// -// Overlay image meta, stored in the header of parts so as to preserve overlay -// information. -// ============================================================================= -class LDOverlay : public LDObject -{ - LDOBJ (Overlay) - LDOBJ_NAME (overlay) - LDOBJ_VERTICES (0) - LDOBJ_UNCOLORED - LDOBJ_NON_SCEMANTIC - LDOBJ_NO_MATRIX - PROPERTY (public, int, camera, setCamera, STOCK_WRITE) - PROPERTY (public, int, x, setX, STOCK_WRITE) - PROPERTY (public, int, y, setY, STOCK_WRITE) - PROPERTY (public, int, width, setWidth, STOCK_WRITE) - PROPERTY (public, int, height, setHeight, STOCK_WRITE) - PROPERTY (public, QString, fileName, setFileName, STOCK_WRITE) -}; - -// Other common LDraw stuff -static const QString g_CALicense ("!LICENSE Redistributable under CCAL version 2.0 : see CAreadme.txt"); -static const QString g_nonCALicense ("!LICENSE Not redistributable : see NonCAreadme.txt"); -static const int g_lores = 16; -static const int g_hires = 48; - -QString getLicenseText (int id);
--- a/src/Macros.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once - -#ifndef __GNUC__ -# define __attribute__(X) -#endif - -// ============================================================================= -// -#define PROPERTY(ACCESS, TYPE, READ, WRITE, WRITETYPE) \ -private: \ - TYPE m_##READ; \ - \ -public: \ - inline TYPE const& READ() const \ - { \ - return m_##READ; \ - } \ - \ -ACCESS: \ - void WRITE (TYPE const& a) PROPERTY_##WRITETYPE (READ) \ - -#define PROPERTY_STOCK_WRITE(READ) \ - { \ - m_##READ = a; \ - } - -#define PROPERTY_CUSTOM_WRITE(READ) \ - ; - -// ============================================================================= -// -#define elif(A) else if (A) - -// ============================================================================= -// -#ifdef WIN32 -# define DIRSLASH "\\" -# define DIRSLASH_CHAR '\\' -#else // WIN32 -# define DIRSLASH "/" -# define DIRSLASH_CHAR '/' -#endif // WIN32 - -// ============================================================================= -// -#ifdef __GNUC__ -#define FUNCNAME __PRETTY_FUNCTION__ -#else -#define FUNCNAME __func__ -#endif // __GNUC__ - -// ============================================================================= -// -#define dvalof(A) dprint ("value of '%1' = %2\n", #A, A) - -// ============================================================================= -// -// Replace assert with a version that shows a GUI dialog if possible. -// On Windows I just can't get the actual error messages otherwise. -// -#undef assert - -#ifdef DEBUG -# define assert(N) { ((N) ? (void) 0 : assertionFailure (__FILE__, __LINE__, FUNCNAME, #N)); } -#else -# define assert(N) {} -#endif // DEBUG - -#define for_axes(AX) for (const Axis AX : std::initializer_list<const Axis> ({X, Y, Z})) - -// ============================================================================= -#ifdef IN_IDE_PARSER // KDevelop workarounds: -# error IN_IDE_PARSER is defined (this code is only for KDevelop workarounds) -# define COMPILE_DATE "14-01-10 10:31:09" - -# ifndef va_start -# define va_start(va, arg) -# endif // va_start - -# ifndef va_end -# define va_end(va) -# endif // va_end - -static const char* __func__ = ""; // Current function name -typedef void FILE; // :| -#endif // IN_IDE_PARSER \ No newline at end of file
--- a/src/Main.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 <QDir> -#include "MainWindow.h" -#include "Document.h" -#include "Misc.h" -#include "Configuration.h" -#include "Colors.h" -#include "Types.h" -#include "Primitives.h" -#include "GLRenderer.h" -#include "ConfigurationDialog.h" -#include "Dialogs.h" -#include "CrashCatcher.h" - -QList<LDDocument*> g_loadedFiles; -MainWindow* g_win = null; -static QString 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); - initCrashCatcher(); - LDDocument::setCurrent (null); - - // Load or create the configuration - if (!Config::load()) - { - print ("Creating configuration file...\n"); - - if (Config::save()) - print ("Configuration file successfully created.\n"); - else - critical ("Failed to create configuration file!\n"); - } - - LDPaths::initPaths(); - initColors(); - MainWindow* win = new MainWindow; - 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(); -}
--- a/src/Main.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -// ============================================================================= -// This file is included one way or another in every source file of LDForge. -// Stuff defined and included here is universally included. - -#pragma once -#include <stdio.h> -#include <stdlib.h> -#include <stdint.h> -#include <stdarg.h> -#include <QString> -#include <QTextFormat> -#include "Macros.h" -#include "Version.h" -#include "Configuration.h" -#include "Format.h" - -// Null pointer -static const std::nullptr_t null = nullptr; - -void assertionFailure (const char* file, int line, const char* funcname, const char* expr); - -// Version string identifier. These are defined in Version.cc. -const char* versionString(); -const char* fullVersionString();
--- a/src/MainWindow.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1034 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 <QMetaMethod> -#include "Main.h" -#include "GLRenderer.h" -#include "MainWindow.h" -#include "Document.h" -#include "Configuration.h" -#include "Misc.h" -#include "Colors.h" -#include "EditHistory.h" -#include "Widgets.h" -#include "AddObjectDialog.h" -#include "MessageLog.h" -#include "Configuration.h" -#include "ui_ldforge.h" - -static bool g_isSelectionLocked = false; - -cfg (Bool, lv_colorize, true); -cfg (String, gui_colortoolbar, "16:24:|:4:25:14:27:2:3:11:1:22:|:0:72:71:15"); -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); -extern_cfg (Bool, gl_drawangles); - -// ============================================================================= -// -MainWindow::MainWindow (QWidget* parent, Qt::WindowFlags flags) : - QMainWindow (parent, flags) -{ - g_win = this; - ui = new Ui_LDForgeUI; - ui->setupUi (this); - m_updatingTabs = false; - m_renderer = new GLRenderer (this); - m_tabs = new QTabBar; - ui->verticalLayout->insertWidget (0, m_tabs); - - // 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 (m_tabs, SIGNAL (currentChanged(int)), 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); - - // Make certain actions checkable - ui->actionAxes->setChecked (gl_axes); - ui->actionWireframe->setChecked (gl_wireframe); - ui->actionBFCView->setChecked (gl_colorbfc); - updateGridToolBar(); - updateEditModeActions(); - updateRecentFilesMenu(); - updateColorToolbar(); - updateTitle(); - updateActionShortcuts(); - - setMinimumSize (300, 200); - - connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup())); - - // Connect all actions - for (QAction* act : findChildren<QAction*>()) - if (!act->objectName().isEmpty()) - connect (act, SIGNAL (triggered()), this, SLOT (slot_action())); -} - -// ============================================================================= -// -KeySequenceConfig* MainWindow::shortcutForAction (QAction* action) -{ - QString keycfgname = format ("key_%1", action->objectName()); - return KeySequenceConfig::getByName (keycfgname); -} - -// ============================================================================= -// -void MainWindow::updateActionShortcuts() -{ - for (QAction* act : findChildren<QAction*>()) - { - KeySequenceConfig* cfg = shortcutForAction (act); - - if (cfg) - act->setShortcut (cfg->getValue()); - } -} - -// ============================================================================= -// -void MainWindow::slot_action() -{ - // Get the name of the sender object and use it to compose the slot name, - // then invoke this slot to call the action. - QMetaObject::invokeMethod (this, - qPrintable (format ("slot_%1", sender()->objectName())), Qt::DirectConnection); - endAction(); -} - -// ============================================================================= -// -void MainWindow::endAction() -{ - // Add a step in 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()); -} - -// ============================================================================= -// -void MainWindow::slot_lastSecondCleanup() -{ - delete m_renderer; - delete ui; -} - -// ============================================================================= -// -void MainWindow::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) - { - QString 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 (QString colorname : gui_colortoolbar.split (":")) - { - if (colorname == "|") - colors << LDQuickColor::getSeparator(); - else - { - LDColor* col = getColor (colorname.toLong()); - - if (col != null) - colors << LDQuickColor (col, null); - } - } - - return colors; -} - -// ============================================================================= -// -void MainWindow::updateColorToolbar() -{ - 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.color(), 22)); - colorButton->setIconSize (QSize (22, 22)); - colorButton->setToolTip (entry.color()->name); - - connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor())); - ui->colorToolbar->addWidget (colorButton); - m_colorButtons << colorButton; - - entry.setToolButton (colorButton); - } - } - - updateGridToolBar(); -} - -// ============================================================================= -// -void MainWindow::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 MainWindow::updateTitle() -{ - QString title = format (APPNAME " %1", fullVersionString()); - - // Append our current file if we have one - if (getCurrentDocument()) - { - if (getCurrentDocument()->name().length() > 0) - title += format (": %1", basename (getCurrentDocument()->name())); - else - title += format (": <anonymous>"); - - if (getCurrentDocument()->getObjectCount() > 0 && - getCurrentDocument()->getObject (0)->type() == LDObject::EComment) - { - // Append title - LDComment* comm = static_cast<LDComment*> (getCurrentDocument()->getObject (0)); - title += format (": %1", comm->text()); - } - - if (getCurrentDocument()->hasUnsavedChanges()) - title += '*'; - } - -#ifdef DEBUG - title += " [debug build]"; -#elif BUILD_ID != BUILD_RELEASE - title += " [pre-release build]"; -#endif // DEBUG - -#ifdef COMPILE_DATE - title += " (built " COMPILE_DATE ")"; -#endif // COMPILE_DATE - - setWindowTitle (title); -} - -// ============================================================================= -// -int MainWindow::deleteSelection() -{ - if (selection().isEmpty()) - return 0; - - LDObjectList selCopy = selection(); - - // Delete the objects that were being selected - for (LDObject* obj : selCopy) - obj->destroy(); - - refresh(); - return selCopy.size(); -} - -// ============================================================================= -// -void MainWindow::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()->objects()) - { - QString descr; - - switch (obj->type()) - { - case LDObject::EComment: - { - descr = static_cast<LDComment*> (obj)->text(); - - // Remove leading whitespace - while (descr[0] == ' ') - descr.remove (0, 1); - - break; - } - - case LDObject::EEmpty: - break; // leave it empty - - case LDObject::ELine: - case LDObject::ETriangle: - case LDObject::EQuad: - case LDObject::ECondLine: - { - for (int i = 0; i < obj->vertices(); ++i) - { - if (i != 0) - descr += ", "; - - descr += obj->vertex (i).toString (true); - } - break; - } - - case LDObject::EError: - { - descr = format ("ERROR: %1", obj->asText()); - break; - } - - case LDObject::EVertex: - { - descr = static_cast<LDVertex*> (obj)->pos.toString (true); - break; - } - - case LDObject::ESubfile: - { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - - descr = format ("%1 %2, (", ref->fileInfo()->getDisplayName(), ref->position().toString (true)); - - for (int i = 0; i < 9; ++i) - descr += format ("%1%2", ref->transform()[i], (i != 8) ? " " : ""); - - descr += ')'; - break; - } - - case LDObject::EBFC: - { - descr = LDBFC::k_statementStrings[static_cast<LDBFC*> (obj)->statement()]; - break; - } - - case LDObject::EOverlay: - { - LDOverlay* ovl = static_cast<LDOverlay*> (obj); - descr = format ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->camera()], - basename (ovl->fileName()), ovl->x(), ovl->y(), - ovl->width(), ovl->height()); - break; - } - - default: - { - descr = obj->typeName(); - break; - } - } - - QListWidgetItem* item = new QListWidgetItem (descr); - item->setIcon (getIcon (obj->typeName())); - - // 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->type() == LDObject::EError) - { - item->setBackground (QColor ("#AA0000")); - item->setForeground (QColor ("#FFAA00")); - } - elif (lv_colorize && obj->isColored() && obj->color() != maincolor && obj->color() != edgecolor) - { - // If the object isn't in the main or edge color, draw this - // list entry in said color. - LDColor* col = getColor (obj->color()); - - if (col) - item->setForeground (col->faceColor); - } - - obj->qObjListEntry = item; - ui->objectList->insertItem (ui->objectList->count(), item); - } - - g_isSelectionLocked = false; - updateSelection(); - scrollToSelection(); -} - -// ============================================================================= -// -void MainWindow::scrollToSelection() -{ - if (selection().isEmpty()) - return; - - LDObject* obj = selection().last(); - ui->objectList->scrollToItem (obj->qObjListEntry); -} - -// ============================================================================= -// -void MainWindow::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; - - LDObjectList priorSelection = selection(); - - // Get the objects from the object list selection - getCurrentDocument()->clearSelection(); - const QList<QListWidgetItem*> items = ui->objectList->selectedItems(); - - for (LDObject* obj : getCurrentDocument()->objects()) - { - for (QListWidgetItem* item : items) - { - if (item == obj->qObjListEntry) - { - obj->select(); - break; - } - } - } - - // Update the GL renderer - LDObjectList compound = priorSelection + selection(); - removeDuplicates (compound); - - for (LDObject* obj : compound) - m_renderer->compileObject (obj); - - m_renderer->update(); -} - -// ============================================================================= -// -void MainWindow::slot_recentFile() -{ - QAction* qAct = static_cast<QAction*> (sender()); - openMainFile (qAct->text()); -} - -// ============================================================================= -// -void MainWindow::slot_quickColor() -{ - QToolButton* button = static_cast<QToolButton*> (sender()); - LDColor* col = null; - - for (const LDQuickColor& entry : m_quickColors) - { - if (entry.toolButton() == button) - { - col = entry.color(); - 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); - } - - endAction(); - refresh(); -} - -// ============================================================================= -// -int MainWindow::getInsertionPoint() -{ - // If we have a selection, put the item after it. - if (!selection().isEmpty()) - return selection().last()->lineNumber() + 1; - - // Otherwise place the object at the end. - return getCurrentDocument()->getObjectCount(); -} - -// ============================================================================= -// -void MainWindow::doFullRefresh() -{ - buildObjList(); - m_renderer->hardRefresh(); -} - -// ============================================================================= -// -void MainWindow::refresh() -{ - buildObjList(); - m_renderer->update(); -} - -// ============================================================================= -// -void MainWindow::updateSelection() -{ - g_isSelectionLocked = true; - - for (LDObject* obj : getCurrentDocument()->objects()) - 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 MainWindow::getSelectedColor() -{ - int result = -1; - - for (LDObject* obj : selection()) - { - if (obj->isColored() == false) - continue; // doesn't use color - - if (result != -1 && obj->color() != result) - return -1; // No consensus in object color - - if (result == -1) - result = obj->color(); - } - - return result; -} - -// ============================================================================= -// -LDObject::Type MainWindow::getUniformSelectedType() -{ - LDObject::Type result = LDObject::EUnidentified; - - for (LDObject* obj : selection()) - { - if (result != LDObject::EUnidentified && obj->color() != result) - return LDObject::EUnidentified; - - if (result == LDObject::EUnidentified) - result = obj->type(); - } - - return result; -} - -// ============================================================================= -// -void MainWindow::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 MainWindow::spawnContextMenu (const QPoint pos) -{ - const bool single = (selection().size() == 1); - LDObject* singleObj = (single) ? selection()[0] : null; - - QMenu* contextMenu = new QMenu; - - if (single && singleObj->type() != LDObject::EEmpty) - { - 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 (selection().size() > 0) - { - contextMenu->addSeparator(); - contextMenu->addAction (ui->actionSubfileSelection); - } - - if (R()->camera() != GL::EFreeCamera) - { - contextMenu->addSeparator(); - contextMenu->addAction (ui->actionSetDrawDepth); - } - - contextMenu->exec (pos); -} - -// ============================================================================= -// -void MainWindow::deleteByColor (int colnum) -{ - LDObjectList objs; - - for (LDObject* obj : getCurrentDocument()->objects()) - { - if (!obj->isColored() || obj->color() != colnum) - continue; - - objs << obj; - } - - for (LDObject* obj : objs) - obj->destroy(); -} - -// ============================================================================= -// -void MainWindow::updateEditModeActions() -{ - const EditMode mode = R()->editMode(); - ui->actionModeSelect->setChecked (mode == ESelectMode); - ui->actionModeDraw->setChecked (mode == EDrawMode); - ui->actionModeCircle->setChecked (mode == ECircleMode); -} - -// ============================================================================= -// -void MainWindow::slot_editObject (QListWidgetItem* listitem) -{ - LDObject* obj = null; - - for (LDObject* it : getCurrentDocument()->objects()) - { - if (it->qObjListEntry == listitem) - { - obj = it; - break; - } - } - - AddObjectDialog::staticDialog (obj->type(), obj); -} - -// ============================================================================= -// -bool MainWindow::save (LDDocument* doc, bool saveAs) -{ - QString path = doc->fullPath(); - - if (saveAs || path.isEmpty()) - { - QString name = doc->defaultName(); - - if (!doc->fullPath().isEmpty()) - name = doc->fullPath(); - elif (!doc->name().isEmpty()) - name = doc->name(); - - name.replace ("\\", "/"); - path = QFileDialog::getSaveFileName (g_win, tr ("Save As"), - name, tr ("LDraw files (*.dat *.ldr)")); - - if (path.isEmpty()) - { - // User didn't give a file name, abort. - return false; - } - } - - if (doc->save (path)) - { - if (doc == getCurrentDocument()) - updateTitle(); - - print ("Saved to %1.", path); - - // Add it to recent files - addRecentFile (path); - return true; - } - - QString message = format (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) - return save (doc, true); // yay recursion! - - return false; -} - -void MainWindow::addMessage (QString msg) -{ - m_msglog->addLine (msg); -} - -// ============================================================================ -void ObjectList::contextMenuEvent (QContextMenuEvent* ev) -{ - g_win->spawnContextMenu (ev->globalPos()); -} - -// ============================================================================= -// -QPixmap getIcon (QString iconName) -{ - return (QPixmap (format (":/icons/%1.png", iconName))); -} - -// ============================================================================= -// -bool confirm (const QString& message) -{ - return confirm (MainWindow::tr ("Confirm"), message); -} - -// ============================================================================= -// -bool confirm (const QString& title, const QString& message) -{ - return QMessageBox::question (g_win, title, message, - (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes; -} - -// ============================================================================= -// -void critical (const QString& message) -{ - QMessageBox::critical (g_win, MainWindow::tr ("Error"), message, - (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; - 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()->objects()) - { - if (!obj->isColored()) - continue; - - if (counts.find (obj->color()) == counts.end()) - counts[obj->color()] = 1; - else - counts[obj->color()]++; - } - - 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, format ("[%1] %2 (%3 object%4)", - pair.first, col->name, pair.second, plural (pair.second))); - box->setItemData (row, pair.first); - - ++row; - } -} - -// ============================================================================= -// -void MainWindow::updateDocumentList() -{ - m_updatingTabs = true; - - while (m_tabs->count() > 0) - m_tabs->removeTab (0); - - 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 the tab index - // in the document so we can find documents by tab index. - f->setTabIndex (m_tabs->addTab ("")); - updateDocumentListItem (f); - } - - m_updatingTabs = false; -} - -// ============================================================================= -// -void MainWindow::updateDocumentListItem (LDDocument* doc) -{ - bool oldUpdatingTabs = m_updatingTabs; - m_updatingTabs = true; - - if (doc->tabIndex() == -1) - { - // 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 (doc == getCurrentDocument()) - m_tabs->setCurrentIndex (doc->tabIndex()); - - m_tabs->setTabText (doc->tabIndex(), doc->getDisplayName()); - - // If the document.has unsaved changes, draw a little icon next to it to mark that. - m_tabs->setTabIcon (doc->tabIndex(), doc->hasUnsavedChanges() ? getIcon ("file-save") : QIcon()); - m_updatingTabs = oldUpdatingTabs; -} - -// ============================================================================= -// -// 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 MainWindow::changeCurrentFile() -{ - if (m_updatingTabs) - return; - - LDDocument* f = null; - int tabIndex = m_tabs->currentIndex(); - - // Find the file pointer of the item that was selected. - for (LDDocument* it : g_loadedFiles) - { - if (it->tabIndex() == tabIndex) - { - f = it; - break; - } - } - - // If we picked the same file we're currently on, we don't need to do - // anything. - if (f == null || f == getCurrentDocument()) - return; - - LDDocument::setCurrent (f); -} - -// ============================================================================= -// -void MainWindow::refreshObjectList() -{ -#if 0 - ui->objectList->clear(); - LDDocument* f = getCurrentDocument(); - -for (LDObject* obj : *f) - ui->objectList->addItem (obj->qObjListEntry); - -#endif - - buildObjList(); -} - -// ============================================================================= -// -void MainWindow::updateActions() -{ - History* his = getCurrentDocument()->history(); - int pos = his->position(); - ui->actionUndo->setEnabled (pos != -1); - ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1); - ui->actionAxes->setChecked (gl_axes); - ui->actionBFCView->setChecked (gl_colorbfc); - ui->actionDrawAngles->setChecked (gl_drawangles); -} - -// ============================================================================= -// -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 color() == null; -}
--- a/src/MainWindow.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,378 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QMainWindow> -#include <QAction> -#include <QListWidget> -#include <QRadioButton> -#include "Configuration.h" -#include "LDObject.h" -#include "ui_ldforge.h" - -class MessageManager; -class MainWindow; -class LDColor; -class QToolButton; -class QDialogButtonBox; -class GLRenderer; -class QComboBox; -class QProgressBar; -class Ui_LDForgeUI; - -// Stuff for dialogs -#define IMPLEMENT_DIALOG_BUTTONS \ - bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); \ - connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept())); \ - connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); \ - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -#define DEFINE_ACTION(NAME, DEFSHORTCUT) \ - cfg (KeySequence, key_action##NAME, DEFSHORTCUT); \ - void MainWindow::slot_action##NAME() - -// Convenience macros for key sequences. -#define KEY(N) (Qt::Key_##N) -#define CTRL(N) (Qt::CTRL | Qt::Key_##N) -#define SHIFT(N) (Qt::SHIFT | Qt::Key_##N) -#define CTRL_SHIFT(N) (Qt::CTRL | Qt::SHIFT | Qt::Key_##N) - -// ============================================================================= -class LDQuickColor -{ - PROPERTY (public, LDColor*, color, setColor, STOCK_WRITE) - PROPERTY (public, QToolButton*, toolButton, setToolButton, STOCK_WRITE) - - public: - LDQuickColor (LDColor* color, QToolButton* toolButton); - bool isSeparator() const; - - static LDQuickColor getSeparator(); -}; - -//! -//! Object list class for MainWindow -//! -class ObjectList : public QListWidget -{ - Q_OBJECT - - protected: - void contextMenuEvent (QContextMenuEvent* ev); -}; - -//! -//! \brief The main window class. -//! -//! The MainWindow is LDForge's main GUI. It hosts the renderer, the object list, -//! the message log, etc. Contains \c slot_action(), which is what all actions -//! connect to. -//! -class MainWindow : public QMainWindow -{ - Q_OBJECT - - public: - //! Constructs the main window - explicit MainWindow (QWidget* parent = null, Qt::WindowFlags flags = 0); - - //! Rebuilds the object list, located to the right of the GUI. - void buildObjList(); - - //! Updates the window title. - void updateTitle(); - - //! Builds the object list and tells the GL renderer to init a full - //! refresh. - void doFullRefresh(); - - //! Builds the object list and tells the GL renderer to do a soft update. - void refresh(); - - //! \returns the suggested position to place a new object at. - int getInsertionPoint(); - - //! Updates the quick color toolbar - void updateColorToolbar(); - - //! Rebuilds the recent files submenu - void updateRecentFilesMenu(); - - //! Sets the selection based on what's selected in the object list. - void updateSelection(); - - //! Updates the grids, selects the selected grid and deselects others. - void updateGridToolBar(); - - //! Updates the edit modes, current one is selected and others are deselected. - void updateEditModeActions(); - - //! Rebuilds the document tab list. - void updateDocumentList(); - - //! Updates the document tab for \c doc. If no such tab exists, the - //! document list is rebuilt instead. - void updateDocumentListItem (LDDocument* doc); - - //! \returns the uniform selected color (i.e. 4 if everything selected is - //! red), -1 if there is no such consensus. - int getSelectedColor(); - - //! \returns the uniform selected type (i.e. \c LDObject::ELine if everything - //! selected is a line), \c LDObject::EUnidentified if there is no such - //! consensus. - LDObject::Type getUniformSelectedType(); - - //! Automatically scrolls the object list so that it points to the first - //! selected object. - void scrollToSelection(); - - //! Spawns the context menu at the given position. - void spawnContextMenu (const QPoint pos); - - //! Deletes all selected objects. - //! \returns the count of deleted objects. - int deleteSelection(); - - //! Deletes all objects by the given color number. - void deleteByColor (int colnum); - - //! Tries to save the given document. - //! \param doc the document to save - //! \param saveAs if true, always ask for a file path - //! \returns whether the save was successful - bool save (LDDocument* doc, bool saveAs); - - //! Updates various actions, undo/redo are set enabled/disabled where - //! appropriate, togglable actions are updated based on configuration, - //! etc. - void updateActions(); - - //! \returns a pointer to the renderer - inline GLRenderer* R() - { - return m_renderer; - } - - //! Sets the quick color list to the given list of colors. - inline void setQuickColors (const QList<LDQuickColor>& colors) - { - m_quickColors = colors; - updateColorToolbar(); - } - - //! Adds a message to the renderer's message manager. - void addMessage (QString msg); - - //! Updates the object list. Right now this just rebuilds it. - void refreshObjectList(); - - //! Updates all actions to ensure they have the correct shortcut as - //! defined in the configuration entries. - void updateActionShortcuts(); - - //! Gets the shortcut configuration for the given \c action - KeySequenceConfig* shortcutForAction (QAction* action); - - void endAction(); - - public slots: - void changeCurrentFile(); - void slot_action(); - void slot_actionNew(); - void slot_actionNewFile(); - void slot_actionOpen(); - void slot_actionDownloadFrom(); - void slot_actionSave(); - void slot_actionSaveAs(); - void slot_actionSaveAll(); - void slot_actionClose(); - void slot_actionCloseAll(); - void slot_actionInsertFrom(); - void slot_actionExportTo(); - void slot_actionSettings(); - void slot_actionSetLDrawPath(); - void slot_actionScanPrimitives(); - void slot_actionExit(); - void slot_actionResetView(); - void slot_actionAxes(); - void slot_actionWireframe(); - void slot_actionBFCView(); - void slot_actionSetOverlay(); - void slot_actionClearOverlay(); - void slot_actionScreenshot(); - void slot_actionInsertRaw(); - void slot_actionNewSubfile(); - void slot_actionNewLine(); - void slot_actionNewTriangle(); - void slot_actionNewQuad(); - void slot_actionNewCLine(); - void slot_actionNewComment(); - void slot_actionNewBFC(); - void slot_actionNewVertex(); - void slot_actionUndo(); - void slot_actionRedo(); - void slot_actionCut(); - void slot_actionCopy(); - void slot_actionPaste(); - void slot_actionDelete(); - void slot_actionSelectAll(); - void slot_actionSelectByColor(); - void slot_actionSelectByType(); - void slot_actionModeDraw(); - void slot_actionModeSelect(); - void slot_actionModeCircle(); - void slot_actionSetDrawDepth(); - void slot_actionSetColor(); - void slot_actionAutocolor(); - void slot_actionUncolorize(); - void slot_actionInline(); - void slot_actionInlineDeep(); - void slot_actionInvert(); - void slot_actionMakePrimitive(); - void slot_actionSplitQuads(); - void slot_actionEditRaw(); - void slot_actionBorders(); - void slot_actionCornerVerts(); - void slot_actionRoundCoordinates(); - void slot_actionVisibilityHide(); - void slot_actionVisibilityReveal(); - void slot_actionVisibilityToggle(); - void slot_actionReplaceCoords(); - void slot_actionFlip(); - void slot_actionDemote(); - void slot_actionYtruder(); - void slot_actionRectifier(); - void slot_actionIntersector(); - void slot_actionIsecalc(); - void slot_actionCoverer(); - void slot_actionEdger2(); - void slot_actionHelp(); - void slot_actionAbout(); - void slot_actionAboutQt(); - void slot_actionGridCoarse(); - void slot_actionGridMedium(); - void slot_actionGridFine(); - void slot_actionEdit(); - void slot_actionMoveUp(); - void slot_actionMoveDown(); - void slot_actionMoveXNeg(); - void slot_actionMoveXPos(); - void slot_actionMoveYNeg(); - void slot_actionMoveYPos(); - void slot_actionMoveZNeg(); - void slot_actionMoveZPos(); - void slot_actionRotateXNeg(); - void slot_actionRotateXPos(); - void slot_actionRotateYNeg(); - void slot_actionRotateYPos(); - void slot_actionRotateZNeg(); - void slot_actionRotateZPos(); - void slot_actionRotationPoint(); - void slot_actionAddHistoryLine(); - void slot_actionJumpTo(); - void slot_actionSubfileSelection(); - void slot_actionDrawAngles(); - - protected: - void closeEvent (QCloseEvent* ev); - - private: - GLRenderer* m_renderer; - LDObjectList m_sel; - QList<LDQuickColor> m_quickColors; - QList<QToolButton*> m_colorButtons; - QList<QAction*> m_recentFiles; - MessageManager* m_msglog; - Ui_LDForgeUI* ui; - QTabBar* m_tabs; - bool m_updatingTabs; - - private slots: - void slot_selectionChanged(); - void slot_recentFile(); - void slot_quickColor(); - void slot_lastSecondCleanup(); - void slot_editObject (QListWidgetItem* listitem); -}; - -//! Pointer to the instance of MainWindow. -extern MainWindow* g_win; - -//! Get an icon by name from the resources directory. -QPixmap getIcon (QString iconName); - -//! \returns a list of quick colors based on the configuration entry. -QList<LDQuickColor> quickColorsFromConfig(); - -//! Asks the user a yes/no question with the given \c message and the given -//! window \c title. -//! \returns true if the user answered yes, false if no. -bool confirm (const QString& title, const QString& message); // Generic confirm prompt - -//! An overload of \c confirm(), this asks the user a yes/no question with the -//! given \c message. -//! \returns true if the user answered yes, false if no. -bool confirm (const QString& message); - -//! Displays an error prompt with the given \c message -void critical (const QString& message); - -//! Makes an icon of \c size x \c size pixels to represent \c colinfo -QIcon makeColorIcon (LDColor* colinfo, const int size); - -//! Fills the given combo-box with color information -void makeColorComboBox (QComboBox* box); - -//! \returns a QImage from the given raw GL \c data -QImage imageFromScreencap (uchar* data, int w, int h); - -//! -//! Takes in pairs of radio buttons and respective values and finds the first -//! selected one. -//! \returns returns the value of the first found radio button that was checked -//! \returns by the user. -//! -template<class T> -T radioSwitch (const T& defval, QList<Pair<QRadioButton*, T>> haystack) -{ - for (Pair<QRadioButton*, const T&> i : haystack) - if (i.first->isChecked()) - return i.second; - - return defval; -} - -//! -//! Takes in pairs of radio buttons and respective values and checks the first -//! found radio button whose respsective value matches \c expr have the given value. -//! -template<class T> -void radioDefault (const T& expr, QList<Pair<QRadioButton*, T>> haystack) -{ - for (Pair<QRadioButton*, const T&> i : haystack) - { - if (i.second == expr) - { - i.first->setChecked (true); - return; - } - } -}
--- a/src/MessageLog.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,131 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "GLRenderer.h" -#include "MainWindow.h" - -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 (QString text) : - text (text), - alpha (1.0f), - expiry (QDateTime::currentDateTime().addSecs (g_expiry)) {} - -// ============================================================================= -// -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 (QString 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 (renderer()) - renderer()->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 && renderer()) - renderer()->update(); -} - -// ============================================================================= -// -const QList<MessageManager::Line>& MessageManager::getLines() const -{ - return m_lines; -} - -// ============================================================================= -// -void printToLog (const QString& msg) -{ - for (QString& a : msg.split ("\n", QString::SkipEmptyParts)) - { - if (g_win != null) - g_win->addMessage (a); - - // Also print it to stdout - fprint (stdout, "%1\n", a); - } -}
--- a/src/MessageLog.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QObject> -#include <QDate> -#include "Main.h" -#include "Types.h" - -class GLRenderer; -class QTimer; - -//! -//! \brief Manages the list of messages at the top-left of the renderer. -//! -//! The message manager is an object which keeps track of messages that appear -//! on the renderer's screen. Each line is contained in a separate object which -//! contains the text, expiry time and alpha. The message manager is doubly -//! linked to its corresponding renderer. -//! -//! Message manager calls its \c tick() function regularly to update the messages, -//! where each line's expiry is checked for. Lines begin to fade out when nearing -//! their expiry. If the message manager's lines change, the renderer undergoes -//! repainting. -//! -class MessageManager : public QObject -{ - Q_OBJECT - PROPERTY (public, GLRenderer*, renderer, setRenderer, STOCK_WRITE) - - public: - //! \class MessageManager::Line - //! A single line of the message log. - class Line - { - public: - //! Constructs a line with the given \c text - Line (QString text); - - //! Check this line's expiry and update alpha accordingly. - //! \c changed is updated to whether the line has somehow - //! changed since the last update. - //! \returns true if the line is to still stick around, false - //! \returns if it expired. - bool update (bool& changed); - - QString text; - float alpha; - QDateTime expiry; - }; - - //! Constructs the message manager. - explicit MessageManager (QObject* parent = null); - - //! Adds a line with the given \c text to the message manager. - void addLine (QString line); - - //! \returns all active lines in the message manager. - const QList<Line>& getLines() const; - - private: - QList<Line> m_lines; - QTimer* m_ticker; - - private slots: - //! Ticks the manager. This is called by the timer to update - //! the messages. - void tick(); -};
--- a/src/Misc.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,303 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "MainWindow.h" -#include "Dialogs.h" -#include "Document.h" -#include "ui_rotpoint.h" -#include "misc/DocumentPointer.cc" -#include "misc/RingFinder.cc" -#include "misc/InvokationDeferer.cc" - -// 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 long g_e10[] = -{ - 1l, - 10l, - 100l, - 1000l, - 10000l, - 100000l, - 1000000l, - 10000000l, - 100000000l, - 1000000000l, -}; - -// ============================================================================= -// -// 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 (Vertex, edit_customrotpoint, g_origin); - -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]; - 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 QString& 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 LDObjectList& objs) -{ - switch ((ERotationPoint) edit_rotpoint) - { - case EObjectOrigin: - { - LDBoundingBox box; - - // Calculate center vertex - for (LDObject* obj : objs) - { - if (obj->hasMatrix()) - box << dynamic_cast<LDMatrixObject*> (obj)->position(); - else - box << obj; - } - - return box.center(); - } - - case EWorldOrigin: - { - return g_origin; - } - - case ECustomPoint: - { - return edit_customrotpoint; - } - } - - return Vertex(); -} - -// ============================================================================= -// -void configRotationPoint() -{ - QDialog* dlg = new QDialog; - Ui::RotPointUI ui; - ui.setupUi (dlg); - - switch ((ERotationPoint) edit_rotpoint) - { - case EObjectOrigin: - ui.objectPoint->setChecked (true); - break; - - case EWorldOrigin: - ui.worldPoint->setChecked (true); - break; - - case ECustomPoint: - ui.customPoint->setChecked (true); - break; - } - - ui.customX->setValue (edit_customrotpoint.x()); - ui.customY->setValue (edit_customrotpoint.y()); - ui.customZ->setValue (edit_customrotpoint.z()); - - if (!dlg->exec()) - return; - - edit_rotpoint = - (ui.objectPoint->isChecked()) ? EObjectOrigin : - (ui.worldPoint->isChecked()) ? EWorldOrigin : - ECustomPoint; - - edit_customrotpoint.x() = ui.customX->value(); - edit_customrotpoint.y() = ui.customY->value(); - edit_customrotpoint.z() = ui.customZ->value(); -} - -// ============================================================================= -// -QString join (QList<StringFormatArg> vals, QString delim) -{ - QStringList list; - - for (const StringFormatArg& arg : vals) - list << arg.text(); - - return list.join (delim); -} - -// ============================================================================= -// -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.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QVector> -#include "Configuration.h" -#include "Main.h" -#include "Types.h" - -#define NUM_PRIMES 500 - -class LDDocument; -class QColor; -class QAction; - -// Prime numbers -extern const int g_primes[NUM_PRIMES]; - -// Returns whether a given string represents a floating point number. -bool numeric (const QString& tok); - -// Simplifies the given fraction. -void simplify (int& numer, int& denom); - -void roundToDecimals (double& a, int decimals); - -QString join (QList< StringFormatArg > vals, QString delim = " "); - -// Grid stuff -struct gridinfo -{ - const char* const name; - float* const confs[4]; -}; - -extern_cfg (Int, grid); -static const int g_NumGrids = 3; -extern const gridinfo g_GridInfo[3]; - -inline const gridinfo& currentGrid() -{ - return g_GridInfo[grid]; -} - -// ============================================================================= -enum ERotationPoint -{ - EObjectOrigin, - EWorldOrigin, - ECustomPoint -}; - -Vertex rotPoint (const LDObjectList& objs); -void configRotationPoint(); - -// ============================================================================= -namespace Grid -{ - enum Type - { - Coarse, - Medium, - Fine - }; - - enum Config - { - X, - Y, - Z, - Angle - }; - - double snap (double value, const Grid::Config axis); -} - -// ============================================================================= -// Plural expression -template<class T> static inline const char* plural (T n) -{ - return (n != 1) ? "s" : ""; -} - -// ============================================================================= -// Templated clamp -template<class T> static inline T clamp (T a, T min, T max) -{ - return (a > max) ? max : (a < min) ? min : a; -} - -// Templated minimum -template<class T> static inline T min (T a, T b) -{ - return (a < b) ? a : b; -} - -// Templated maximum -template<class T> static inline T max (T a, T b) -{ - return (a > b) ? a : b; -} - -// Templated absolute value -template<class T> static inline T abs (T a) -{ - return (a >= 0) ? a : -a; -} - -template<class T> inline bool isZero (T a) -{ - return abs<T> (a) < 0.0001; -} - -template<class T> inline bool isInteger (T a) -{ - return isZero (a - (int) a); -} - -template<class T> void removeDuplicates (QList<T>& a) -{ - std::sort (a.begin(), a.end()); - a.erase (std::unique (a.begin(), a.end()), a.end()); -}
--- a/src/PartDownloader.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,539 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "PartDownloader.h" -#include "ui_downloadfrom.h" -#include "Types.h" -#include "MainWindow.h" -#include "Document.h" -#include "GLRenderer.h" -#include "ConfigurationDialog.h" - -cfg (String, net_downloadpath, ""); -cfg (Bool, net_guesspaths, true); -cfg (Bool, net_autoclose, true); - -const QString g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/"); - -// ============================================================================= -// -void PartDownloader::staticBegin() -{ - QString 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(); -} - -// ============================================================================= -// -QString PartDownloader::getDownloadPath() -{ - QString path = net_downloadpath; - -#if DIRSLASH_CHAR != '/' - path.replace (DIRSLASH, "/"); -#endif - - return path; -} - -// ============================================================================= -// -PartDownloader::PartDownloader (QWidget* parent) : QDialog (parent) -{ - setInterface (new Ui_DownloadFrom); - interface()->setupUi (this); - interface()->fname->setFocus(); - interface()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); - - setDownloadButton (new QPushButton (tr ("Download"))); - interface()->buttonBox->addButton (downloadButton(), QDialogButtonBox::ActionRole); - getButton (Abort)->setEnabled (false); - - connect (interface()->source, SIGNAL (currentIndexChanged (int)), - this, SLOT (sourceChanged (int))); - connect (interface()->buttonBox, SIGNAL (clicked (QAbstractButton*)), - this, SLOT (buttonClicked (QAbstractButton*))); -} - -// ============================================================================= -// -PartDownloader::~PartDownloader() -{ - delete interface(); -} - -// ============================================================================= -// -QString PartDownloader::getURL() const -{ - const Source src = getSource(); - QString dest; - - switch (src) - { - case PartsTracker: - dest = interface()->fname->text(); - modifyDestination (dest); - return g_unofficialLibraryURL + dest; - - case CustomURL: - return interface()->fname->text(); - } - - // Shouldn't happen - return ""; -} - -// ============================================================================= -// -void PartDownloader::modifyDestination (QString& 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. */ - QString partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*"; - QString 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) interface()->source->currentIndex(); -} - -// ============================================================================= -// -void PartDownloader::sourceChanged (int i) -{ - if (i == CustomURL) - interface()->fileNameLabel->setText (tr ("URL:")); - else - interface()->fileNameLabel->setText (tr ("File name:")); -} - -// ============================================================================= -// -void PartDownloader::buttonClicked (QAbstractButton* btn) -{ - if (btn == getButton (Close)) - { - reject(); - } - elif (btn == getButton (Abort)) - { - setAborted (true); - - for (PartDownloadRequest* req : requests()) - req->abort(); - } - elif (btn == getButton (Download)) - { - QString dest = interface()->fname->text(); - setPrimaryFile (null); - setAborted (false); - - if (getSource() == CustomURL) - dest = basename (getURL()); - - modifyDestination (dest); - - if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest)) - { - const QString overwritemsg = format (tr ("%1 already exists in download directory. Overwrite?"), dest); - if (!confirm (tr ("Overwrite?"), overwritemsg)) - return; - } - - downloadButton()->setEnabled (false); - interface()->progress->setEnabled (true); - interface()->fname->setEnabled (false); - interface()->source->setEnabled (false); - downloadFile (dest, getURL(), true); - getButton (Close)->setEnabled (false); - getButton (Abort)->setEnabled (true); - getButton (Download)->setEnabled (false); - } -} - -// ============================================================================= -// -void PartDownloader::downloadFile (QString dest, QString url, bool primary) -{ - const int row = interface()->progress->rowCount(); - - // Don't download files repeadetly. - if (filesToDownload().indexOf (dest) != -1) - return; - - modifyDestination (dest); - PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); - m_filesToDownload << dest; - m_requests << req; - interface()->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 : requests()) - { - if (!req->isFinished()) - return; - - if (req->state() == PartDownloadRequest::EFailed) - failed = true; - } - - for (PartDownloadRequest* req : requests()) - delete req; - - m_requests.clear(); - - // Update everything now - if (primaryFile() != null) - { - LDDocument::setCurrent (primaryFile()); - 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 downloadButton(); - - case Abort: - return qobject_cast<QPushButton*> (interface()->buttonBox->button (QDialogButtonBox::Abort)); - - case Close: - return qobject_cast<QPushButton*> (interface()->buttonBox->button (QDialogButtonBox::Close)); - } - - return null; -} - -// ============================================================================= -// -PartDownloadRequest::PartDownloadRequest (QString url, QString 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_networkManager (new QNetworkAccessManager), - m_isFirstUpdate (true), - m_isPrimary (primary), - m_filePointer (null) -{ - // Make sure that we have a valid destination. - QString dirpath = dirname (filePath()); - - QDir dir (dirpath); - - if (dir.exists() == false) - { - print ("Creating %1...\n", dirpath); - - if (!dir.mkpath (dirpath)) - critical (format (tr ("Couldn't create the directory %1!"), dirpath)); - } - - setNetworkReply (networkManager()->get (QNetworkRequest (QUrl (url)))); - connect (networkReply(), SIGNAL (finished()), this, SLOT (downloadFinished())); - connect (networkReply(), SIGNAL (readyRead()), this, SLOT (readyRead())); - connect (networkReply(), SIGNAL (downloadProgress (qint64, qint64)), - this, SLOT (downloadProgress (qint64, qint64))); -} - -// ============================================================================= -// -PartDownloadRequest::~PartDownloadRequest() {} - -// ============================================================================= -// -void PartDownloadRequest::updateToTable() -{ - const int labelcol = PartDownloader::PartLabelColumn, - progcol = PartDownloader::ProgressColumn; - QTableWidget* table = prompt()->interface()->progress; - QProgressBar* prog; - - switch (state()) - { - case ERequesting: - case EDownloading: - { - prog = qobject_cast<QProgressBar*> (table->cellWidget (tableRow(), progcol)); - - if (!prog) - { - prog = new QProgressBar; - table->setCellWidget (tableRow(), progcol, prog); - } - - prog->setRange (0, numBytesTotal()); - prog->setValue (numBytesRead()); - } break; - - case EFinished: - case EFailed: - { - const QString text = (state() == 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 (tableRow(), progcol, lb); - } break; - } - - QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (tableRow(), labelcol)); - - if (isFirstUpdate()) - { - lb = new QLabel (format ("<b>%1</b>", destinaton()), table); - table->setCellWidget (tableRow(), 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 (networkReply()->error() != QNetworkReply::NoError) - { - if (isPrimary() && !prompt()->isAborted()) - critical (networkReply()->errorString()); - - setState (EFailed); - } - elif (state() != EFailed) - setState (EFinished); - - setNumBytesRead (numBytesTotal()); - updateToTable(); - - if (filePointer()) - { - filePointer()->close(); - delete filePointer(); - setFilePointer (null); - - if (state() == EFailed) - QFile::remove (filePath()); - } - - if (state() != EFinished) - { - prompt()->checkIfFinished(); - return; - } - - // Try to load this file now. - LDDocument* f = openDocument (filePath(), 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->objects()) - { - LDError* err = dynamic_cast<LDError*> (obj); - - if (err == null || err->fileReferenced().isEmpty()) - continue; - - QString dest = err->fileReferenced(); - prompt()->modifyDestination (dest); - prompt()->downloadFile (dest, g_unofficialLibraryURL + dest, false); - } - - if (isPrimary()) - { - addRecentFile (filePath()); - prompt()->setPrimaryFile (f); - } - - prompt()->checkIfFinished(); -} - -// ============================================================================= -// -void PartDownloadRequest::downloadProgress (int64 recv, int64 total) -{ - setNumBytesRead (recv); - setNumBytesTotal (total); - setState (EDownloading); - updateToTable(); -} - -// ============================================================================= -// -void PartDownloadRequest::readyRead() -{ - if (state() == EFailed) - return; - - if (filePointer() == null) - { - m_filePath.replace ("\\", "/"); - - // We have already asked the user whether we can overwrite so we're good - // to go here. - setFilePointer (new QFile (filePath().toLocal8Bit())); - - if (!filePointer()->open (QIODevice::WriteOnly)) - { - critical (format (tr ("Couldn't open %1 for writing: %2"), filePath(), strerror (errno))); - setState (EFailed); - networkReply()->abort(); - updateToTable(); - prompt()->checkIfFinished(); - return; - } - } - - filePointer()->write (networkReply()->readAll()); -} - -// ============================================================================= -// -bool PartDownloadRequest::isFinished() const -{ - return state() == EFinished || state() == EFailed; -} - -// ============================================================================= -// -void PartDownloadRequest::abort() -{ - networkReply()->abort(); -} - -// ============================================================================= -// -DEFINE_ACTION (DownloadFrom, 0) -{ - PartDownloader::staticBegin(); -}
--- a/src/PartDownloader.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QDialog> -#include "Main.h" -#include "Types.h" - -class LDDocument; -class QFile; -class PartDownloadRequest; -class Ui_DownloadFrom; -class QNetworkAccessManager; -class QNetworkRequest; -class QNetworkReply; -class QAbstractButton; - -// ============================================================================= -// -class PartDownloader : public QDialog -{ - public: - enum Source - { - PartsTracker, - CustomURL, - }; - - enum Button - { - Download, - Abort, - Close - }; - - enum TableColumn - { - PartLabelColumn, - ProgressColumn, - }; - - using RequestList = QList<PartDownloadRequest*>; - - Q_OBJECT - PROPERTY (public, LDDocument*, primaryFile, setPrimaryFile, STOCK_WRITE) - PROPERTY (public, bool, isAborted, setAborted, STOCK_WRITE) - PROPERTY (private, Ui_DownloadFrom*, interface, setInterface, STOCK_WRITE) - PROPERTY (private, QStringList, filesToDownload, setFilesToDownload, STOCK_WRITE) - PROPERTY (private, RequestList, requests, setRequests, STOCK_WRITE) - PROPERTY (private, QPushButton*, downloadButton, setDownloadButton, STOCK_WRITE) - - public: - explicit PartDownloader (QWidget* parent = null); - virtual ~PartDownloader(); - - void downloadFile (QString dest, QString url, bool primary); - QPushButton* getButton (Button i); - QString getURL() const; - Source getSource() const; - void modifyDestination (QString& dest) const; - - static QString getDownloadPath(); - static void staticBegin(); - - public slots: - void buttonClicked (QAbstractButton* btn); - void checkIfFinished(); - void sourceChanged (int i); -}; - -// ============================================================================= -// -class PartDownloadRequest : public QObject -{ - public: - enum EState - { - ERequesting, - EDownloading, - EFinished, - EFailed, - }; - - Q_OBJECT - PROPERTY (public, int, tableRow, setTableRow, STOCK_WRITE) - PROPERTY (private, EState, state, setState, STOCK_WRITE) - PROPERTY (private, PartDownloader*, prompt, setPrompt, STOCK_WRITE) - PROPERTY (private, QString, url, setURL, STOCK_WRITE) - PROPERTY (private, QString, destinaton, setDestination, STOCK_WRITE) - PROPERTY (private, QString, filePath, setFilePath, STOCK_WRITE) - PROPERTY (private, QNetworkAccessManager*, networkManager, setNetworkManager, STOCK_WRITE) - PROPERTY (private, QNetworkReply*, networkReply, setNetworkReply, STOCK_WRITE) - PROPERTY (private, bool, isFirstUpdate, setFirstUpdate, STOCK_WRITE) - PROPERTY (private, int64, numBytesRead, setNumBytesRead, STOCK_WRITE) - PROPERTY (private, int64, numBytesTotal, setNumBytesTotal, STOCK_WRITE) - PROPERTY (private, bool, isPrimary, setPrimary, STOCK_WRITE) - PROPERTY (private, QFile*, filePointer, setFilePointer, STOCK_WRITE) - - public: - explicit PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent); - PartDownloadRequest (const PartDownloadRequest&) = delete; - virtual ~PartDownloadRequest(); - void updateToTable(); - bool isFinished() const; - void operator= (const PartDownloadRequest&) = delete; - - public slots: - void downloadFinished(); - void readyRead(); - void downloadProgress (qint64 recv, qint64 total); - void abort(); -};
--- a/src/Primitives.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,703 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "MainWindow.h" -#include "Primitives.h" -#include "ui_makeprim.h" -#include "Misc.h" -#include "Colors.h" - -QList<PrimitiveCategory*> g_PrimitiveCategories; -QList<Primitive> g_primitives; -static PrimitiveScanner* g_activeScanner = null; -PrimitiveCategory* g_unmatched = null; - -extern_cfg (String, ld_defaultname); -extern_cfg (String, ld_defaultuser); -extern_cfg (Int, ld_defaultlicense); - -static const QStringList g_radialNameRoots = -{ - "edge", - "cyli", - "disc", - "ndis", - "ring", - "con" -}; - -PrimitiveScanner* getActivePrimitiveScanner() -{ - return g_activeScanner; -} - -// ============================================================================= -// -void loadPrimitives() -{ - PrimitiveCategory::loadCategories(); - - // Try to load prims.cfg - QFile conf (Config::filepath ("prims.cfg")); - - if (conf.open (QIODevice::ReadOnly) == false) - { - // No prims.cfg, build it - PrimitiveScanner::start(); - } - else - { - while (conf.atEnd() == false) - { - QString line = conf.readLine(); - - if (line.endsWith ("\n")) - line.chop (1); - - 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(); - print ("%1 primitives loaded.\n", g_primitives.size()); - } -} - -// ============================================================================= -// -static void recursiveGetFilenames (QDir dir, QList<QString>& fnames) -{ - QFileInfoList flist = dir.entryInfoList (QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); - - for (const QFileInfo& info : flist) - { - if (info.isDir()) - recursiveGetFilenames (QDir (info.absoluteFilePath()), fnames); - else - fnames << info.absoluteFilePath(); - } -} - -// ============================================================================= -// -PrimitiveScanner::PrimitiveScanner (QObject* parent) : - QObject (parent), - m_i (0) -{ - g_activeScanner = this; - QDir dir (LDPaths::prims()); - assert (dir.exists()); - m_baselen = dir.absolutePath().length(); - recursiveGetFilenames (dir, m_files); - emit starting (m_files.size()); - print ("Scanning primitives..."); -} - -// ============================================================================= -// -PrimitiveScanner::~PrimitiveScanner() -{ - g_activeScanner = null; -} - -// ============================================================================= -// -void PrimitiveScanner::work() -{ - int j = min (m_i + 100, m_files.size()); - - for (; m_i < j; ++m_i) - { - QString fname = m_files[m_i]; - QFile f (fname); - - if (!f.open (QIODevice::ReadOnly)) - continue; - - Primitive info; - info.name = fname.mid (m_baselen + 1); // make full path relative - info.name.replace ('/', '\\'); // use DOS backslashes, they're expected - info.category = null; - QByteArray titledata = f.readLine(); - - if (titledata != QByteArray()) - info.title = QString::fromUtf8 (titledata); - - info.title = info.title.simplified(); - - if (Q_LIKELY (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 - QString path = Config::filepath ("prims.cfg"); - QFile conf (path); - - if (!conf.open (QIODevice::WriteOnly | QIODevice::Text)) - critical (format ("Couldn't write primitive list %1: %2", - path, conf.errorString())); - else - { - for (Primitive& info : m_prims) - fprint (conf, "%1 %2\r\n", info.name, info.title); - - conf.close(); - } - - g_primitives = m_prims; - PrimitiveCategory::populateCategories(); - print ("%1 primitives scanned", g_primitives.size()); - g_activeScanner = 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 PrimitiveScanner::start() -{ - if (g_activeScanner) - return; - - PrimitiveScanner* scanner = new PrimitiveScanner; - scanner->work(); -} - -// ============================================================================= -// -PrimitiveCategory::PrimitiveCategory (QString 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.category = 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.category = cat; - break; - } - } - - // Drop out if a category was decided on. - if (prim.category != null) - break; - } - - // If there was a match, add the primitive to the category. - // Otherwise, add it to the list of unmatched primitives. - if (prim.category != null) - prim.category->prims << prim; - else - g_unmatched->prims << prim; - } -} - -// ============================================================================= -// -void PrimitiveCategory::loadCategories() -{ - for (PrimitiveCategory* cat : g_PrimitiveCategories) - delete cat; - - g_PrimitiveCategories.clear(); - QString path = Config::dirpath() + "primregexps.cfg"; - - if (!QFile::exists (path)) - path = ":/data/primitive-categories.cfg"; - - QFile f (path); - - if (!f.open (QIODevice::ReadOnly)) - { - critical (format (QObject::tr ("Failed to open primitive categories: %1"), f.errorString())); - return; - } - - PrimitiveCategory* cat = null; - - while (f.atEnd() == false) - { - QString line = f.readLine(); - int colon; - - if (line.endsWith ("\n")) - line.chop (1); - - 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) - { - QString cmd = line.left (colon); - RegexType type = EFilenameRegex; - - if (cmd == "f") - type = EFilenameRegex; - elif (cmd == "t") - type = ETitleRegex; - else - { - print (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 - print ("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; - f.close(); -} - -// ============================================================================= -// -bool PrimitiveCategory::isValidToInclude() -{ - if (regexes.isEmpty()) - { - print (tr ("Warning: category \"%1\" left without patterns"), name()); - deleteLater(); - return false; - } - - return true; -} - -// ============================================================================= -// -bool isPrimitiveLoaderBusy() -{ - return g_activeScanner != 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)); - } -} - -// ============================================================================= -// -LDObjectList makePrimitive (PrimitiveType type, int segs, int divs, int num) -{ - LDObjectList 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 QString 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"; -} - -// ============================================================================= -// -QString 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 - QString prefix = (divs == g_lores) ? "" : format ("%1/", divs); - QString frac = format ("%1-%2", numer, denom); - QString root = g_radialNameRoots[type]; - QString numstr = (type == Ring || type == Cone) ? format ("%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 - QString frac = QString::number ((float) segs / divs); - QString name = radialFileName (type, segs, divs, num); - QString descr; - - // Ensure that there's decimals, even if they're 0. - if (frac.indexOf (".") == -1) - frac += ".0"; - - if (type == Ring || type == Cone) - { - QString spacing = - (num < 10) ? " " : - (num < 100) ? " " : ""; - - descr = format ("%1 %2%3 x %4", primitiveTypeName (type), spacing, num, frac); - } - else - descr = format ("%1 %2", primitiveTypeName (type), frac); - - // Prepend "Hi-Res" if 48/ primitive. - if (divs == g_hires) - descr.insert (0, "Hi-Res "); - - LDDocument* f = new LDDocument; - f->setDefaultName (name); - - QString author = APPNAME; - QString license = ""; - - if (ld_defaultname.isEmpty() == false) - { - license = getLicenseText (ld_defaultlicense); - author = format ("%1 [%2]", ld_defaultname, ld_defaultuser); - } - - f->addObjects ( - { - new LDComment (descr), - new LDComment (format ("Name: %1", name)), - new LDComment (format ("Author: %1", author)), - new LDComment (format ("!LDRAW_ORG Unofficial_%1Primitive", divs == g_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) -{ - QString 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 ? g_hires : g_lores); - - // If the current value is 16 and we switch to hi-res, default the - // spinbox to 48. - if (on && ui->sb_segs->value() == g_lores) - ui->sb_segs->setValue (g_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() ? g_hires : g_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.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,130 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include "Main.h" -#include "Types.h" -#include <QRegExp> -#include <QDialog> - -class LDDocument; -class Ui_MakePrimUI; -class PrimitiveCategory; -struct Primitive -{ - QString name, - title; - PrimitiveCategory* category; -}; - -class PrimitiveCategory : public QObject -{ - Q_OBJECT - PROPERTY (public, QString, name, setName, STOCK_WRITE) - - public: - enum RegexType - { - EFilenameRegex, - ETitleRegex - }; - - struct RegexEntry - { - QRegExp regex; - RegexType type; - }; - - QList<RegexEntry> regexes; - QList<Primitive> prims; - - explicit PrimitiveCategory (QString name, QObject* parent = 0); - bool isValidToInclude(); - - static void loadCategories(); - static void populateCategories(); -}; - -// ============================================================================= -// -// PrimitiveScanner -// -// Worker object that scans the primitives folder for primitives and -// builds an index of them. -// -class PrimitiveScanner : public QObject -{ - Q_OBJECT - - public: - explicit PrimitiveScanner (QObject* parent = 0); - virtual ~PrimitiveScanner(); - static void start(); - - public slots: - void work(); - - signals: - void starting (int num); - void workDone(); - void update (int i); - - private: - QList<Primitive> m_prims; - QStringList m_files; - int m_i; - int m_baselen; -}; - -extern QList<PrimitiveCategory*> g_PrimitiveCategories; - -void loadPrimitives(); -PrimitiveScanner* getActivePrimitiveScanner(); - -enum PrimitiveType -{ - Circle, - Cylinder, - Disc, - DiscNeg, - Ring, - Cone, -}; - -// ============================================================================= -class PrimitivePrompt : public QDialog -{ - Q_OBJECT - - public: - explicit PrimitivePrompt (QWidget* parent = null, Qt::WindowFlags f = 0); - virtual ~PrimitivePrompt(); - Ui_MakePrimUI* ui; - - public slots: - void hiResToggled (bool on); -}; - -void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines); -LDDocument* generatePrimitive (PrimitiveType type, int segs, int divs, int num); - -// Gets a primitive by the given specs. If the primitive cannot be found, it will -// be automatically generated. -LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num); - -QString radialFileName (PrimitiveType type, int segs, int divs, int num);
--- a/src/Types.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,403 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "LDObject.h" -#include "Document.h" - -// ============================================================================= -// -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]; -} - -// ============================================================================= -// -double Vertex::distanceTo (const Vertex& other) const -{ - double dx = abs (x() - other.x()); - double dy = abs (y() - other.y()); - double dz = abs (z() - other.z()); - return sqrt ((dx * dx) + (dy * dy) + (dz * dz)); -} - -// ============================================================================= -// -Vertex Vertex::midpoint (const Vertex& other) -{ - Vertex mid; - - for_axes (ax) - mid[ax] = (getCoordinate (ax) + other[ax]) / 2; - - return mid; -} - -// ============================================================================= -// -QString Vertex::toString (bool mangled) const -{ - QString formatstr = "%1 %2 %3"; - - if (mangled) - formatstr = "(%1, %2, %3)"; - - return format (formatstr, x(), y(), z()); -} - -// ============================================================================= -// -void Vertex::transform (const Matrix& matr, const 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); -} - -// ============================================================================= -// -bool Vertex::operator== (const Vertex& other) const -{ - return getCoordinate (X) == other[X] && - getCoordinate (Y) == other[Y] && - getCoordinate (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 (getCoordinate (X) < other[X]) - return true; - - if (getCoordinate (X) > other[X]) - return false; - - if (getCoordinate (Y) < other[Y]) - return true; - - if (getCoordinate (Y) > other[Y]) - return false; - - return getCoordinate (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 (const std::initializer_list< double >& vals) -{ - assert (vals.size() == 9); - memcpy (&m_vals[0], & (*vals.begin()), sizeof m_vals); -} - -// ============================================================================= -// -void Matrix::dump() const -{ - for (int i = 0; i < 3; ++i) - { - for (int j = 0; j < 3; ++j) - print ("%1\t", m_vals[ (i * 3) + j]); - - print ("\n"); - } -} - -// ============================================================================= -// -QString Matrix::toString() const -{ - QString val; - - for (int i = 0; i < 9; ++i) - { - if (i > 0) - val += ' '; - - val += QString::number (m_vals[i]); - } - - return val; -} - -// ============================================================================= -// -void Matrix::zero() -{ - memset (&m_vals[0], 0, sizeof m_vals); -} - -// ============================================================================= -// -Matrix Matrix::mult (const 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= (const Matrix& other) -{ - memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals); - return *this; -} - -// ============================================================================= -// -double Matrix::getDeterminant() const -{ - return (value (0) * value (4) * value (8)) + - (value (1) * value (5) * value (6)) + - (value (2) * value (3) * value (7)) - - (value (2) * value (4) * value (6)) - - (value (1) * value (3) * value (8)) - - (value (0) * value (5) * value (7)); -} - -// ============================================================================= -// -bool Matrix::operator== (const Matrix& other) const -{ - for (int i = 0; i < 9; ++i) - if (value (i) != other[i]) - return false; - - return true; -} - -// ============================================================================= -// -LDBoundingBox::LDBoundingBox() -{ - reset(); -} - -// ============================================================================= -// -void LDBoundingBox::calculateFromCurrentDocument() -{ - reset(); - - if (!getCurrentDocument()) - return; - - for (LDObject* obj : getCurrentDocument()->objects()) - calcObject (obj); -} - -// ============================================================================= -// -void LDBoundingBox::calcObject (LDObject* obj) -{ - switch (obj->type()) - { - case LDObject::ELine: - case LDObject::ETriangle: - case LDObject::EQuad: - case LDObject::ECondLine: - { - for (int i = 0; i < obj->vertices(); ++i) - calcVertex (obj->vertex (i)); - } break; - - case LDObject::ESubfile: - { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline); - - for (LDObject * obj : objs) - { - calcObject (obj); - obj->destroy(); - } - } - 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& vertex) -{ - for_axes (ax) - { - m_vertex0[ax] = min (vertex[ax], m_vertex0[ax]); - m_vertex1[ax] = max (vertex[ax], m_vertex1[ax]); - } - - setEmpty (false); -} - -// ============================================================================= -// -void LDBoundingBox::reset() -{ - m_vertex0[X] = m_vertex0[Y] = m_vertex0[Z] = 10000.0; - m_vertex1[X] = m_vertex1[Y] = m_vertex1[Z] = -10000.0; - setEmpty (true); -} - -// ============================================================================= -// -double LDBoundingBox::longestMeasurement() 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 Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,335 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QString> -#include <QObject> -#include <QStringList> -#include <QMetaType> -#include "Macros.h" - -class LDObject; -class QFile; -class QTextStream; - -using int8 = qint8; -using int16 = qint16; -using int32 = qint32; -using int64 = qint64; -using uint8 = quint8; -using uint16 = quint16; -using uint32 = quint32; -using uint64 = quint64; - -template<typename T, typename R> -using Pair = std::pair<T, R>; - -enum Axis -{ - X, - Y, - Z -}; - -// ============================================================================= -// -class LDObject; -using LDObjectList = QList<LDObject*>; - -//! -//! \brief A mathematical 3 x 3 matrix -//! -class Matrix -{ - public: - //! Constructs a matrix with undetermined values. - Matrix() {} - - //! Constructs a matrix with the given values. - //! \note \c vals is expected to have exactly 9 elements. - Matrix (const std::initializer_list<double>& vals); - - //! Constructs a matrix all 9 elements initialized to the same value. - //! \param fillval the value to initialize the matrix coordinates as - Matrix (double fillval); - - //! Constructs a matrix with a C-array. - //! \note \c vals is expected to have exactly 9 elements. - Matrix (double vals[]); - - //! Calculates the matrix's determinant. - //! \returns the calculated determinant. - double getDeterminant() const; - - //! Multiplies this matrix with \c other - //! \param other the matrix to multiply with. - //! \returns the resulting matrix - //! \note a.mult(b) is not equivalent to b.mult(a)! - Matrix mult (const Matrix& other) const; - - //! Prints the matrix to stdout. - void dump() const; - - //! \returns a string representation of the matrix. - QString toString() const; - - //! Zeroes the matrix out. - void zero(); - - //! Assigns the matrix values to the values of \c other. - //! \param other the matrix to assign this to. - //! \returns a reference to self - Matrix& operator= (const Matrix& other); - - //! \returns a mutable reference to a value by \c idx - inline double& value (int idx) - { - return m_vals[idx]; - } - - //! An overload of \c value() for const matrices. - //! \returns a const reference to a value by \c idx - inline const double& value (int idx) const - { - return m_vals[idx]; - } - - //! An operator overload for \c mult(). - //! \returns the multiplied matrix. - inline Matrix operator* (const Matrix& other) const - { - return mult (other); - } - - //! An operator overload for \c value(). - //! \returns a mutable reference to a value by \c idx - inline double& operator[] (int idx) - { - return value (idx); - } - - //! An operator overload for \c value() const. - //! \returns a const reference to a value by \c idx - inline const double& operator[] (int idx) const - { - return value (idx); - } - - //! \param other the matrix to check against - //! \returns whether the two matrices have the same values. - bool operator== (const Matrix& other) const; - - private: - double m_vals[9]; -}; - -//! -//! \brief A vertex in 3D space -//! -//! Contains a single point in 3D space. Not to be confused with -//! LDVertex, which is a vertex used in an LDraw part file. -//! -//! This also sees use as a position vector. -//! -class Vertex -{ - public: - //! Constructs a zero vertex - Vertex() : - m_coords{0, 0, 0} {} - - //! Constructs a vertex with the given \c x, \c y and \c z. - Vertex (double x, double y, double z); - - //! \returns the distance from this vertex to \c other - double distanceTo (const Vertex& other) const; - - //! \returns the vertex at the midpoint between this and \c other - Vertex midpoint (const Vertex& other); - - //! Moves this vertex using \param other as a position vector. - void move (const Vertex& other); - - //! Yields a string representation of the vertex. The string returned - //! can possibly be mangled. - //! - As mangled: {1.5, 2.8, 3.14} - //! - Without mangling: 1.5 2.8 3.14 - //! - //! The mangled version is suitable for printing to the user, the - //! non-mangled one is used when writing the vertex to LDraw files. - //! - //! \returns a string representation of this vertex - //! \param mangled whether to return a mangled representation or not - QString toString (bool mangled) const; - - //! Transforms this vertex with \c matr as transformation matrix - //! and \c pos as the position column of the 4x4 matrix. - void transform (const Matrix& matr, const Vertex& pos); - - //! An operator overload for \c move(). - Vertex& operator+= (const Vertex& other); - - //! An operator overload for \c move(), using a temporary vertex. - Vertex operator+ (const Vertex& other) const; - - //! Divides all values by \c d. - Vertex operator/ (const double d) const; - - //! Divides all values by \c d. - Vertex& operator/= (const double d); - - //! Checks whether this vertex has the same values as \c other. - bool operator== (const Vertex& other) const; - - //! Checks whether this vertex has different values than \c other. - bool operator!= (const Vertex& other) const; - - //! \returns a negated version the vertex - Vertex operator-() const; - - //! \returns whether the vertex has lesser values than \c other. - int operator< (const Vertex& other) const; - - //! An operator overload for \c getCoordinate(). - inline double& operator[] (const Axis ax) - { - return getCoordinate ((int) ax); - } - - //! An operator overload for \c getCoordinate() const. - inline const double& operator[] (const Axis ax) const - { - return getCoordinate ((int) ax); - } - - //! An operator overload for \c getCoordinate(). - inline double& operator[] (const int ax) - { - return getCoordinate (ax); - } - - //! An operator overload for \c getCoordinate() const. - inline const double& operator[] (const int ax) const - { - return getCoordinate (ax); - } - - //! \returns a mutable reference for the coordinate designated by \param n. - inline double& getCoordinate (int n) - { - return m_coords[n]; - } - - //! An overload of \c getCoordinate for const vertices. - //! \returns a const reference for the coordinate designated by \param n. - inline const double& getCoordinate (int n) const - { - return m_coords[n]; - } - - //! \returns a mutable reference to X. - inline double& x() - { - return m_coords[X]; - } - - //! An overload of \c x() for const vertices. - //! \returns a const reference to X. - inline const double& x() const - { - return m_coords[X]; - } - - //! \returns a mutable reference to Y. - inline double& y() - { - return m_coords[Y]; - } - - //! An overload of \c y() for const vertices. - //! \returns a const reference to Y. - inline const double& y() const - { - return m_coords[Y]; - } - - //! \returns a mutable reference to Z. - inline double& z() - { - return m_coords[Z]; - } - - //! An overload of \c z() for const vertices. - //! \returns a const reference to Z. - inline const double& z() const - { - return m_coords[Z]; - } - - private: - double m_coords[3]; -}; - -Q_DECLARE_METATYPE (Vertex) - -//! -//! Defines a bounding box that encompasses a given set of objects. -//! vertex0 is the minimum vertex, vertex1 is the maximum vertex. -// -class LDBoundingBox -{ - PROPERTY (private, bool, isEmpty, setEmpty, STOCK_WRITE) - PROPERTY (private, Vertex, vertex0, setVertex0, STOCK_WRITE) - PROPERTY (private, Vertex, vertex1, setVertex1, STOCK_WRITE) - - public: - //! Constructs an empty bounding box. - LDBoundingBox(); - - //! Clears the bounding box - void reset(); - - //! Calculates the bounding box's values from the objects in the current - //! document. - void calculateFromCurrentDocument(); - - //! \returns the length of the bounding box on the longest measure. - double longestMeasurement() const; - - //! Calculates the given \c obj to the bounding box, adjusting - //! extremas if necessary. - void calcObject (LDObject* obj); - - //! Calculates the given \c vertex to the bounding box, adjusting - //! extremas if necessary. - void calcVertex (const Vertex& vertex); - - //! \returns the center of the bounding box. - Vertex center() const; - - //! An operator overload for \c calcObject() - LDBoundingBox& operator<< (LDObject* obj); - - //! An operator overload for \c calcVertex() - LDBoundingBox& operator<< (const Vertex& v); -}; - -extern const Vertex g_origin; // Vertex at (0, 0, 0) -extern const Matrix g_identity; // Identity matrix - -static const double pi = 3.14159265358979323846;
--- a/src/Version.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 <stdio.h> -#include <string.h> -#include "Version.h" -#include "Git.h" - -char gVersionString[64] = {'\0'}; -char gFullVersionString[256] = {'\0'}; - -// ============================================================================= -// -const char* versionString() -{ - if (gVersionString[0] == '\0') - { -#if VERSION_PATCH == 0 - sprintf (gVersionString, "%d.%d", VERSION_MAJOR, VERSION_MINOR); -#else - sprintf (gVersionString, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); -#endif // VERSION_PATCH - } - - return gVersionString; -} - -// ============================================================================= -// -const char* fullVersionString() -{ - if (gFullVersionString[0] == '\0') - { -#if BUILD_ID != BUILD_RELEASE - strcpy (gFullVersionString, GIT_DESCRIPTION); -#else - sprintf (gFullVersionString, "v%s", versionString()); -#endif - } - - return gFullVersionString; -}
--- a/src/Version.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 Version.h -//! Contains macros related to application name and version. - -#pragma once - -//! The application name. -#define APPNAME "LDForge" - -//! The unix-style name of the application. used in filenames -#define UNIXNAME "ldforge" - -//! The major version number. -#define VERSION_MAJOR 0 - -//! The minor version number. -#define VERSION_MINOR 3 - -//! The patch level version number. -#define VERSION_PATCH 0 - -//! The build ID, use either BUILD_INTERNAL or BUILD_RELEASE -#define BUILD_ID BUILD_INTERNAL - -//! The build code for internal builds -#define BUILD_INTERNAL 0 - -//! The build code for release builds. -#define BUILD_RELEASE 1 - -// ============================================= -#ifdef DEBUG -# undef RELEASE -#endif // DEBUG - -#ifdef RELEASE -# undef DEBUG -#endif // RELEASE
--- a/src/Widgets.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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" - -// ============================================================================= -// -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, QList<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.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QGroupBox> -#include <QSpinBox> -#include <map> -#include "Main.h" -#include "Types.h" - -class QIcon; -class QCheckBox; -class QButtonGroup; -class QBoxLayout; -class QRadioButton; - -// ============================================================================= -// RadioGroup -// -// Convenience widget - is a groupbox of radio buttons. -// ============================================================================= -class RadioGroup : public QGroupBox -{ - Q_OBJECT - - public: - typedef QList<QRadioButton*>::Iterator Iterator; - - explicit RadioGroup() - { - init (Qt::Vertical); - } - - explicit RadioGroup (QWidget* parent = null) : QGroupBox (parent) - { - init (Qt::Vertical); - } - - explicit RadioGroup (const QString& title, QWidget* parent = null); - explicit RadioGroup (const QString& title, QList<char const*> entries, int const defaultId, - const Qt::Orientation orient = Qt::Vertical, QWidget* parent = null); - - void addButton (const char* entry); - void addButton (QRadioButton* button); - Iterator begin(); - Iterator end(); - void init (Qt::Orientation orient); - bool isChecked (int n) const; - void rowBreak(); - void setCurrentRow (int row); - void setValue (int val); - int value() const; - - QRadioButton* operator[] (int n) const; - RadioGroup& operator<< (QRadioButton* button); - RadioGroup& operator<< (const char* entry); - - signals: - void buttonPressed (int btn); - void buttonReleased (int btn); - void valueChanged (int val); - - private: - QList<QRadioButton*> m_objects; - QList<QBoxLayout*> m_layouts; - QBoxLayout* m_coreLayout; - QBoxLayout* m_currentLayout; - bool m_vert; - int m_curId, m_defId, m_oldId; - QButtonGroup* m_buttonGroup; - - Q_DISABLE_COPY (RadioGroup) - - private slots: - void slot_buttonPressed (int btn); - void slot_buttonReleased (int btn); -};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/actions.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,873 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "mainWindow.h" +#include "ldDocument.h" +#include "editHistory.h" +#include "configDialog.h" +#include "addObjectDialog.h" +#include "miscallenous.h" +#include "glRenderer.h" +#include "dialogs.h" +#include "primitives.h" +#include "radioGroup.h" +#include "colors.h" +#include "ui_newpart.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); +extern_cfg (Bool, gl_drawangles); + +// ============================================================================= +// +DEFINE_ACTION (New, CTRL_SHIFT (N)) +{ + QDialog* dlg = new QDialog (g_win); + Ui::NewPartUI ui; + ui.setupUi (dlg); + + QString authortext = ld_defaultname; + + if (!ld_defaultuser.isEmpty()) + authortext.append (format (" [%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", + format ("Unknown ld_defaultlicense value %1!", ld_defaultlicense)); + break; + } + + if (dlg->exec() == false) + return; + + newFile(); + + const LDBFC::Statement BFCType = + ui.rb_bfc_ccw->isChecked() ? LDBFC::CertifyCCW : + ui.rb_bfc_cw->isChecked() ? LDBFC::CertifyCW : LDBFC::NoCertify; + + const QString license = + ui.rb_license_ca->isChecked() ? g_CALicense : + ui.rb_license_nonca->isChecked() ? g_nonCALicense : ""; + + getCurrentDocument()->addObjects ( + { + new LDComment (ui.le_title->text()), + new LDComment ("Name: <untitled>.dat"), + new LDComment (format ("Author: %1", ui.le_author->text())), + new LDComment (format ("!LDRAW_ORG Unofficial_Part")), + (license != "" ? new LDComment (license) : null), + new LDEmpty, + new LDBFC (BFCType), + new LDEmpty, + }); + + doFullRefresh(); +} + +// ============================================================================= +// +DEFINE_ACTION (NewFile, CTRL (N)) +{ + newFile(); +} + +// ============================================================================= +// +DEFINE_ACTION (Open, CTRL (O)) +{ + QString name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)"); + + if (name.length() == 0) + return; + + openMainFile (name); +} + +// ============================================================================= +// +DEFINE_ACTION (Save, CTRL (S)) +{ + save (getCurrentDocument(), false); +} + +// ============================================================================= +// +DEFINE_ACTION (SaveAs, CTRL_SHIFT (S)) +{ + save (getCurrentDocument(), true); +} + +// ============================================================================= +// +DEFINE_ACTION (SaveAll, CTRL (L)) +{ + for (LDDocument* file : g_loadedFiles) + { + if (file->isImplicit()) + continue; + + 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::ESubfile, null); +} + +// ============================================================================= +// +DEFINE_ACTION (NewLine, 0) +{ + AddObjectDialog::staticDialog (LDObject::ELine, null); +} + +// ============================================================================= +// +DEFINE_ACTION (NewTriangle, 0) +{ + AddObjectDialog::staticDialog (LDObject::ETriangle, null); +} + +// ============================================================================= +// +DEFINE_ACTION (NewQuad, 0) +{ + AddObjectDialog::staticDialog (LDObject::EQuad, null); +} + +// ============================================================================= +// +DEFINE_ACTION (NewCLine, 0) +{ + AddObjectDialog::staticDialog (LDObject::ECondLine, null); +} + +// ============================================================================= +// +DEFINE_ACTION (NewComment, 0) +{ + AddObjectDialog::staticDialog (LDObject::EComment, null); +} + +// ============================================================================= +// +DEFINE_ACTION (NewBFC, 0) +{ + AddObjectDialog::staticDialog (LDObject::EBFC, null); +} + +// ============================================================================= +// +DEFINE_ACTION (NewVertex, 0) +{ + AddObjectDialog::staticDialog (LDObject::EVertex, null); +} + +// ============================================================================= +// +DEFINE_ACTION (Edit, 0) +{ + if (selection().size() != 1) + return; + + LDObject* obj = selection() [0]; + AddObjectDialog::staticDialog (obj->type(), 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()->objects()) + obj->select(); + + updateSelection(); +} + +// ============================================================================= +// +DEFINE_ACTION (SelectByColor, CTRL_SHIFT (A)) +{ + int colnum = getSelectedColor(); + + if (colnum == -1) + return; // no consensus on color + + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : getCurrentDocument()->objects()) + if (obj->color() == colnum) + obj->select(); + + updateSelection(); +} + +// ============================================================================= +// +DEFINE_ACTION (SelectByType, 0) +{ + if (selection().isEmpty()) + return; + + LDObject::Type type = getUniformSelectedType(); + + if (type == LDObject::EUnidentified) + return; + + // If we're selecting subfile references, the reference filename must also + // be uniform. + QString refName; + + if (type == LDObject::ESubfile) + { + refName = static_cast<LDSubfile*> (selection()[0])->fileInfo()->name(); + + for (LDObject* obj : selection()) + if (static_cast<LDSubfile*> (obj)->fileInfo()->name() != refName) + return; + } + + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : getCurrentDocument()->objects()) + { + if (obj->type() != type) + continue; + + if (type == LDObject::ESubfile && static_cast<LDSubfile*> (obj)->fileInfo()->name() != refName) + continue; + + obj->select(); + } + + updateSelection(); +} + +// ============================================================================= +// +DEFINE_ACTION (GridCoarse, 0) +{ + grid = Grid::Coarse; + updateGridToolBar(); +} + +DEFINE_ACTION (GridMedium, 0) +{ + grid = Grid::Medium; + updateGridToolBar(); +} + +DEFINE_ACTION (GridFine, 0) +{ + grid = Grid::Fine; + updateGridToolBar(); +} + +// ============================================================================= +// +DEFINE_ACTION (ResetView, CTRL (0)) +{ + R()->resetAngles(); + R()->update(); +} + +// ============================================================================= +// +DEFINE_ACTION (InsertFrom, 0) +{ + QString fname = QFileDialog::getOpenFileName(); + int idx = getInsertionPoint(); + + if (!fname.length()) + return; + + QFile f (fname); + + if (!f.open (QIODevice::ReadOnly)) + { + critical (format ("Couldn't open %1 (%2)", fname, f.errorString())); + return; + } + + LDObjectList objs = loadFileContents (&f, null); + + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : objs) + { + getCurrentDocument()->insertObj (idx, obj); + obj->select(); + R()->compileObject (obj); + + idx++; + } + + refresh(); + scrollToSelection(); +} + +// ============================================================================= +// +DEFINE_ACTION (ExportTo, 0) +{ + if (selection().isEmpty()) + return; + + QString fname = QFileDialog::getSaveFileName(); + + if (fname.length() == 0) + return; + + QFile file (fname); + + if (!file.open (QIODevice::WriteOnly | QIODevice::Text)) + { + critical (format ("Unable to open %1 for writing (%2)", fname, file.errorString())); + return; + } + + for (LDObject* obj : selection()) + { + QString contents = obj->asText(); + QByteArray data = contents.toUtf8(); + file.write (data, data.size()); + file.write ("\r\n", 2); + } +} + +// ============================================================================= +// +DEFINE_ACTION (InsertRaw, 0) +{ + int idx = 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 (QString line : QString (te_edit->toPlainText()).split ("\n")) + { + LDObject* obj = parseLine (line); + + getCurrentDocument()->insertObj (idx, obj); + obj->select(); + R()->compileObject (obj); + idx++; + } + + refresh(); + scrollToSelection(); +} + +// ============================================================================= +// +DEFINE_ACTION (Screenshot, 0) +{ + setlocale (LC_ALL, "C"); + + int w, h; + uchar* imgdata = R()->getScreencap (w, h); + QImage img = imageFromScreencap (imgdata, w, h); + + QString root = basename (getCurrentDocument()->name()); + + if (root.right (4) == ".dat") + root.chop (4); + + QString defaultname = (root.length() > 0) ? format ("%1.png", root) : ""; + QString 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 (format ("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; + updateActions(); + R()->update(); +} + +// ============================================================================= +// +DEFINE_ACTION (VisibilityToggle, 0) +{ + for (LDObject* obj : selection()) + obj->setHidden (!obj->isHidden()); + + refresh(); +} + +// ============================================================================= +// +DEFINE_ACTION (VisibilityHide, 0) +{ + for (LDObject* obj : selection()) + obj->setHidden (true); + + refresh(); +} + +// ============================================================================= +// +DEFINE_ACTION (VisibilityReveal, 0) +{ + for (LDObject* obj : selection()) + obj->setHidden (false); + refresh(); +} + +// ============================================================================= +// +DEFINE_ACTION (Wireframe, 0) +{ + gl_wireframe = !gl_wireframe; + R()->refresh(); +} + +// ============================================================================= +// +DEFINE_ACTION (SetOverlay, 0) +{ + OverlayDialog dlg; + + if (!dlg.exec()) + return; + + R()->setupOverlay ((GL::EFixedCamera) dlg.camera(), dlg.fpath(), dlg.ofsx(), + dlg.ofsy(), dlg.lwidth(), dlg.lheight()); +} + +// ============================================================================= +// +DEFINE_ACTION (ClearOverlay, 0) +{ + R()->clearOverlay(); +} + +// ============================================================================= +// +DEFINE_ACTION (ModeSelect, CTRL (1)) +{ + R()->setEditMode (ESelectMode); +} + +// ============================================================================= +// +DEFINE_ACTION (ModeDraw, CTRL (2)) +{ + R()->setEditMode (EDrawMode); +} + +// ============================================================================= +// +DEFINE_ACTION (ModeCircle, CTRL (3)) +{ + R()->setEditMode (ECircleMode); +} + +// ============================================================================= +// +DEFINE_ACTION (DrawAngles, 0) +{ + gl_drawangles = !gl_drawangles; + R()->refresh(); +} + +// ============================================================================= +// +DEFINE_ACTION (SetDrawDepth, 0) +{ + if (R()->camera() == GL::EFreeCamera) + return; + + bool ok; + double depth = QInputDialog::getDouble (g_win, "Set Draw Depth", + format ("Depth value for %1 Camera:", R()->getCameraName()), + R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok); + + if (ok) + 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) +{ + PrimitiveScanner::start(); +} + +// ============================================================================= +// +DEFINE_ACTION (BFCView, SHIFT (B)) +{ + gl_colorbfc = !gl_colorbfc; + updateActions(); + R()->refresh(); +} + +// ============================================================================= +// +DEFINE_ACTION (JumpTo, CTRL (G)) +{ + bool ok; + int defval = 0; + LDObject* obj; + + if (selection().size() == 1) + defval = selection()[0]->lineNumber(); + + 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(); + updateSelection(); +} + +// ============================================================================= +// +DEFINE_ACTION (SubfileSelection, 0) +{ + if (selection().size() == 0) + return; + + QString parentpath = getCurrentDocument()->fullPath(); + + // BFC type of the new subfile - it shall inherit the BFC type of the parent document + LDBFC::Statement bfctype = LDBFC::NoCertify; + + // Dirname of the new subfile + QString subdirname = dirname (parentpath); + + // Title of the new subfile + QString subtitle; + + // Comment containing the title of the parent document + LDComment* titleobj = dynamic_cast<LDComment*> (getCurrentDocument()->getObject (0)); + + // License text for the subfile + QString license = getLicenseText (ld_defaultlicense); + + // LDraw code body of the new subfile (i.e. code of the selection) + QStringList code; + + // Full path of the subfile to be + QString fullsubname; + + // Where to insert the subfile reference? + int refidx = selection()[0]->lineNumber(); + + // Determine title of subfile + if (titleobj != null) + subtitle = "~" + titleobj->text(); + else + subtitle = "~subfile"; + + // Remove duplicate tildes + while (subtitle[0] == '~' && subtitle[1] == '~') + subtitle.remove (0, 1); + + // If this the parent document isn't already in s/, we need to stuff it into + // a subdirectory named s/. Ensure it exists! + QString topdirname = basename (dirname (getCurrentDocument()->fullPath())); + + if (topdirname != "s") + { + QString desiredPath = subdirname + "/s"; + QString title = tr ("Create subfile directory?"); + QString text = format (tr ("The directory <b>%1</b> is suggested for " + "subfiles. This directory does not exist, create it?"), desiredPath); + + if (QDir (desiredPath).exists() || confirm (title, text)) + { + subdirname = desiredPath; + QDir().mkpath (subdirname); + } + } + + // Determine the body of the name of the subfile + if (!parentpath.isEmpty()) + { + if (parentpath.endsWith (".dat")) + parentpath.chop (4); + + // Remove the s?? suffix if it's there, otherwise we'll get filenames + // like s01s01.dat when subfiling subfiles. + QRegExp subfilesuffix ("s[0-9][0-9]$"); + if (subfilesuffix.indexIn (parentpath) != -1) + parentpath.chop (subfilesuffix.matchedLength()); + + int subidx = 1; + QString digits; + QFile f; + QString testfname; + + do + { + digits.setNum (subidx++); + + // pad it with a zero + if (digits.length() == 1) + digits.prepend ("0"); + + fullsubname = subdirname + "/" + basename (parentpath) + "s" + digits + ".dat"; + } while (findDocument ("s\\" + basename (fullsubname)) != null || QFile (fullsubname).exists()); + } + + // Determine the BFC winding type used in the main document - it is to + // be carried over to the subfile. + for (LDObject* obj : getCurrentDocument()->objects()) + { + LDBFC* bfc = dynamic_cast<LDBFC*> (obj); + + if (!bfc) + continue; + + LDBFC::Statement a = bfc->statement(); + + if (a == LDBFC::CertifyCCW || a == LDBFC::CertifyCW || a == LDBFC::NoCertify) + { + bfctype = a; + break; + } + } + + // Get the body of the document in LDraw code + for (LDObject* obj : selection()) + code << obj->asText(); + + // Create the new subfile document + LDDocument* doc = new LDDocument; + doc->setImplicit (false); + doc->setFullPath (fullsubname); + doc->setName (LDDocument::shortenName (fullsubname)); + doc->addObjects ( + { + new LDComment (subtitle), + new LDComment ("Name: "), + new LDComment (format ("Author: %1 [%2]", ld_defaultname, ld_defaultuser)), + new LDComment (format ("!LDRAW_ORG Unofficial_Subpart")), + (license != "" ? new LDComment (license) : null), + new LDEmpty, + new LDBFC (bfctype), + new LDEmpty, + }); + + // Add the actual subfile code to the new document + for (QString line : code) + { + LDObject* obj = parseLine (line); + doc->addObject (obj); + } + + // Try save it + if (save (doc, true)) + { + // Remove the selection now + for (LDObject* obj : selection()) + obj->destroy(); + + // Compile all objects in the new subfile + for (LDObject* obj : doc->objects()) + R()->compileObject (obj); + + g_loadedFiles << doc; + + // Add a reference to the new subfile to where the selection was + LDSubfile* ref = new LDSubfile(); + ref->setColor (maincolor); + ref->setFileInfo (doc); + ref->setPosition (g_origin); + ref->setTransform (g_identity); + getCurrentDocument()->insertObj (refidx, ref); + R()->compileObject (ref); + + // Refresh stuff + updateDocumentList(); + doFullRefresh(); + } + else + { + // Failed to save. + delete doc; + } +}
--- a/src/actions/EditActions.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,836 +0,0 @@ -/* - * LDForge: LDasText parts authoring CAD - * Copyright (C) 2013, 2014 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 "../MainWindow.h" -#include "../Main.h" -#include "../Document.h" -#include "../ColorSelector.h" -#include "../Misc.h" -#include "../Widgets.h" -#include "../GLRenderer.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() -{ - LDObjectList objs = selection(); - int num = 0; - - // Clear the clipboard first. - qApp->clipboard()->clear(); - - // Now, copy the contents into the clipboard. - QString data; - - for (LDObject* obj : objs) - { - if (data.length() > 0) - data += "\n"; - - data += obj->asText(); - ++num; - } - - qApp->clipboard()->setText (data); - return num; -} - -// ============================================================================= -// -DEFINE_ACTION (Cut, CTRL (X)) -{ - int num = copyToClipboard(); - deleteSelection(); - print (tr ("%1 objects cut"), num); -} - -// ============================================================================= -// -DEFINE_ACTION (Copy, CTRL (C)) -{ - int num = copyToClipboard(); - print (tr ("%1 objects copied"), num); -} - -// ============================================================================= -// -DEFINE_ACTION (Paste, CTRL (V)) -{ - const QString clipboardText = qApp->clipboard()->text(); - int idx = getInsertionPoint(); - getCurrentDocument()->clearSelection(); - int num = 0; - - for (QString line : clipboardText.split ("\n")) - { - LDObject* pasted = parseLine (line); - getCurrentDocument()->insertObj (idx++, pasted); - pasted->select(); - R()->compileObject (pasted); - ++num; - } - - print (tr ("%1 objects pasted"), num); - refresh(); - scrollToSelection(); -} - -// ============================================================================= -// -DEFINE_ACTION (Delete, KEY (Delete)) -{ - int num = deleteSelection(); - print (tr ("%1 objects deleted"), num); -} - -// ============================================================================= -// -static void doInline (bool deep) -{ - LDObjectList sel = selection(); - - for (LDObject* obj : sel) - { - // Get the index of the subfile so we know where to insert the - // inlined contents. - long idx = obj->lineNumber(); - - if (idx == -1) - continue; - - LDObjectList objs; - - if (obj->type() == LDObject::ESubfile) - { - LDSubfile::InlineFlags flags = deep ? LDSubfile::DeepCacheInline : LDSubfile::CacheInline; - objs = static_cast<LDSubfile*> (obj)->inlineContents (flags); - } - else - continue; - - // Merge in the inlined objects - for (LDObject* inlineobj : objs) - { - QString line = inlineobj->asText(); - inlineobj->destroy(); - LDObject* newobj = parseLine (line); - getCurrentDocument()->insertObj (idx++, newobj); - newobj->select(); - g_win->R()->compileObject (newobj); - } - - // Delete the subfile now as it's been inlined. - obj->destroy(); - } - - g_win->refresh(); -} - -DEFINE_ACTION (Inline, CTRL (I)) -{ - doInline (false); -} - -DEFINE_ACTION (InlineDeep, CTRL_SHIFT (I)) -{ - doInline (true); -} - -// ============================================================================= -// -DEFINE_ACTION (SplitQuads, 0) -{ - LDObjectList objs = selection(); - int num = 0; - - for (LDObject* obj : objs) - { - if (obj->type() != LDObject::EQuad) - continue; - - // Find the index of this quad - long index = obj->lineNumber(); - - 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) - R()->compileObject (t); - - // Delete this quad now, it has been split. - obj->destroy(); - - num++; - } - - print ("%1 quadrilaterals split", num); - 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->asText()); - - if (obj->type() == LDObject::EError) - 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 - R()->compileObject (obj); - refresh(); -} - -// ============================================================================= -// -DEFINE_ACTION (SetColor, KEY (C)) -{ - if (selection().isEmpty()) - return; - - int colnum; - int defcol = -1; - - LDObjectList objs = selection(); - - // If all selected objects have the same color, said color is our default - // value to the color selection dialog. - defcol = 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); - R()->compileObject (obj); - } - - refresh(); - } -} - -// ============================================================================= -// -DEFINE_ACTION (Borders, CTRL_SHIFT (B)) -{ - LDObjectList objs = selection(); - int num = 0; - - for (LDObject* obj : objs) - { - const LDObject::Type type = obj->type(); - if (type != LDObject::EQuad && type != LDObject::ETriangle) - continue; - - int numLines; - LDLine* lines[4]; - - if (type == LDObject::EQuad) - { - numLines = 4; - - LDQuad* quad = static_cast<LDQuad*> (obj); - lines[0] = new LDLine (quad->vertex (0), quad->vertex (1)); - lines[1] = new LDLine (quad->vertex (1), quad->vertex (2)); - lines[2] = new LDLine (quad->vertex (2), quad->vertex (3)); - lines[3] = new LDLine (quad->vertex (3), quad->vertex (0)); - } - else - { - numLines = 3; - - LDTriangle* tri = static_cast<LDTriangle*> (obj); - lines[0] = new LDLine (tri->vertex (0), tri->vertex (1)); - lines[1] = new LDLine (tri->vertex (1), tri->vertex (2)); - lines[2] = new LDLine (tri->vertex (2), tri->vertex (0)); - } - - for (int i = 0; i < numLines; ++i) - { - long idx = obj->lineNumber() + i + 1; - - lines[i]->setColor (edgecolor); - getCurrentDocument()->insertObj (idx, lines[i]); - R()->compileObject (lines[i]); - } - - num += numLines; - } - - print (tr ("Added %1 border lines"), num); - refresh(); -} - -// ============================================================================= -// -DEFINE_ACTION (CornerVerts, 0) -{ - int num = 0; - - for (LDObject* obj : selection()) - { - if (obj->vertices() < 2) - continue; - - int ln = obj->lineNumber(); - - for (int i = 0; i < obj->vertices(); ++i) - { - LDVertex* vert = new LDVertex; - vert->pos = obj->vertex (i); - vert->setColor (obj->color()); - - getCurrentDocument()->insertObj (++ln, vert); - R()->compileObject (vert); - ++num; - } - } - - print (tr ("Added %1 vertices"), num); - refresh(); -} - -// ============================================================================= -// -static void doMoveSelection (const bool up) -{ - LDObjectList 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]; - vect[Y] *= *currentGrid().confs[Grid::Y]; - vect[Z] *= *currentGrid().confs[Grid::Z]; - - 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)) -{ - LDObjectList sel = selection(); - - for (LDObject* obj : sel) - { - obj->invert(); - R()->compileObject (obj); - } - - 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) -{ - LDObjectList sel = selection(); - QList<Vertex*> queue; - const Vertex rotpoint = rotPoint (sel); - const double angle = (pi * *currentGrid().confs[Grid::Angle]) / 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->vertex (i); - rotateVertex (v, rotpoint, transform); - obj->setVertex (i, v); - } - } - elif (obj->hasMatrix()) - { - LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj); - - // Transform the position - Vertex v = mo->position(); - rotateVertex (v, rotpoint, transform); - mo->setPosition (v); - - // Transform the matrix - mo->setTransform (transform * mo->transform()); - } - elif (obj->type() == LDObject::EVertex) - { - 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->position(); - Matrix t = mo->transform(); - - 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->vertex (i); - - for_axes (ax) - roundToDecimals (v[ax], 3); - - obj->setVertex (i, v); - R()->compileObject (obj); - num += 3; - } - } - } - - print (tr ("Rounded %1 values"), num); - refreshObjectList(); - refresh(); -} - -// ============================================================================= -// -DEFINE_ACTION (Uncolorize, 0) -{ - int num = 0; - - for (LDObject* obj : selection()) - { - if (obj->isColored() == false) - continue; - - int col = maincolor; - - if (obj->type() == LDObject::ELine || obj->type() == LDObject::ECondLine) - col = edgecolor; - - obj->setColor (col); - R()->compileObject (obj); - num++; - } - - print (tr ("%1 objects uncolored"), num); - 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->vertex (i); - - for (Axis ax : sel) - { - double& coord = v[ax]; - - if (any || coord == search) - { - if (!rel) - coord = 0; - - coord += replacement; - num++; - } - } - - obj->setVertex (i, v); - R()->compileObject (obj); - } - } - - print (tr ("Altered %1 values"), num); - 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->vertex (i); - - for (Axis ax : sel) - v[ax] *= -1; - - obj->setVertex (i, v); - R()->compileObject (obj); - } - } - - refresh(); -} - -// ============================================================================= -// -DEFINE_ACTION (Demote, 0) -{ - LDObjectList sel = selection(); - int num = 0; - - for (LDObject* obj : sel) - { - if (obj->type() != LDObject::ECondLine) - continue; - - LDLine* repl = static_cast<LDCondLine*> (obj)->demote(); - R()->compileObject (repl); - ++num; - } - - print (tr ("Demoted %1 conditional lines"), num); - refresh(); -} - -// ============================================================================= -// -static bool isColorUsed (int colnum) -{ - for (LDObject* obj : getCurrentDocument()->objects()) - if (obj->isColored() && obj->color() == 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) - { - print (tr ("Cannot auto-color: all colors are in use!")); - return; - } - - for (LDObject* obj : selection()) - { - if (obj->isColored() == false) - continue; - - obj->setColor (colnum); - R()->compileObject (obj); - } - - print (tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name); - 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 - QString commentText = format ("!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 != null && 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->lineNumber() : 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); - - buildObjList(); - delete ui; -}
--- a/src/actions/MainActions.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,873 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "../MainWindow.h" -#include "../Document.h" -#include "../EditHistory.h" -#include "../ConfigurationDialog.h" -#include "../AddObjectDialog.h" -#include "../Misc.h" -#include "../GLRenderer.h" -#include "../Dialogs.h" -#include "../Primitives.h" -#include "../Widgets.h" -#include "../Colors.h" -#include "ui_newpart.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); -extern_cfg (Bool, gl_drawangles); - -// ============================================================================= -// -DEFINE_ACTION (New, CTRL_SHIFT (N)) -{ - QDialog* dlg = new QDialog (g_win); - Ui::NewPartUI ui; - ui.setupUi (dlg); - - QString authortext = ld_defaultname; - - if (!ld_defaultuser.isEmpty()) - authortext.append (format (" [%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", - format ("Unknown ld_defaultlicense value %1!", ld_defaultlicense)); - break; - } - - if (dlg->exec() == false) - return; - - newFile(); - - const LDBFC::Statement BFCType = - ui.rb_bfc_ccw->isChecked() ? LDBFC::CertifyCCW : - ui.rb_bfc_cw->isChecked() ? LDBFC::CertifyCW : LDBFC::NoCertify; - - const QString license = - ui.rb_license_ca->isChecked() ? g_CALicense : - ui.rb_license_nonca->isChecked() ? g_nonCALicense : ""; - - getCurrentDocument()->addObjects ( - { - new LDComment (ui.le_title->text()), - new LDComment ("Name: <untitled>.dat"), - new LDComment (format ("Author: %1", ui.le_author->text())), - new LDComment (format ("!LDRAW_ORG Unofficial_Part")), - (license != "" ? new LDComment (license) : null), - new LDEmpty, - new LDBFC (BFCType), - new LDEmpty, - }); - - doFullRefresh(); -} - -// ============================================================================= -// -DEFINE_ACTION (NewFile, CTRL (N)) -{ - newFile(); -} - -// ============================================================================= -// -DEFINE_ACTION (Open, CTRL (O)) -{ - QString name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)"); - - if (name.length() == 0) - return; - - openMainFile (name); -} - -// ============================================================================= -// -DEFINE_ACTION (Save, CTRL (S)) -{ - save (getCurrentDocument(), false); -} - -// ============================================================================= -// -DEFINE_ACTION (SaveAs, CTRL_SHIFT (S)) -{ - save (getCurrentDocument(), true); -} - -// ============================================================================= -// -DEFINE_ACTION (SaveAll, CTRL (L)) -{ - for (LDDocument* file : g_loadedFiles) - { - if (file->isImplicit()) - continue; - - 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::ESubfile, null); -} - -// ============================================================================= -// -DEFINE_ACTION (NewLine, 0) -{ - AddObjectDialog::staticDialog (LDObject::ELine, null); -} - -// ============================================================================= -// -DEFINE_ACTION (NewTriangle, 0) -{ - AddObjectDialog::staticDialog (LDObject::ETriangle, null); -} - -// ============================================================================= -// -DEFINE_ACTION (NewQuad, 0) -{ - AddObjectDialog::staticDialog (LDObject::EQuad, null); -} - -// ============================================================================= -// -DEFINE_ACTION (NewCLine, 0) -{ - AddObjectDialog::staticDialog (LDObject::ECondLine, null); -} - -// ============================================================================= -// -DEFINE_ACTION (NewComment, 0) -{ - AddObjectDialog::staticDialog (LDObject::EComment, null); -} - -// ============================================================================= -// -DEFINE_ACTION (NewBFC, 0) -{ - AddObjectDialog::staticDialog (LDObject::EBFC, null); -} - -// ============================================================================= -// -DEFINE_ACTION (NewVertex, 0) -{ - AddObjectDialog::staticDialog (LDObject::EVertex, null); -} - -// ============================================================================= -// -DEFINE_ACTION (Edit, 0) -{ - if (selection().size() != 1) - return; - - LDObject* obj = selection() [0]; - AddObjectDialog::staticDialog (obj->type(), 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()->objects()) - obj->select(); - - updateSelection(); -} - -// ============================================================================= -// -DEFINE_ACTION (SelectByColor, CTRL_SHIFT (A)) -{ - int colnum = getSelectedColor(); - - if (colnum == -1) - return; // no consensus on color - - getCurrentDocument()->clearSelection(); - - for (LDObject* obj : getCurrentDocument()->objects()) - if (obj->color() == colnum) - obj->select(); - - updateSelection(); -} - -// ============================================================================= -// -DEFINE_ACTION (SelectByType, 0) -{ - if (selection().isEmpty()) - return; - - LDObject::Type type = getUniformSelectedType(); - - if (type == LDObject::EUnidentified) - return; - - // If we're selecting subfile references, the reference filename must also - // be uniform. - QString refName; - - if (type == LDObject::ESubfile) - { - refName = static_cast<LDSubfile*> (selection()[0])->fileInfo()->name(); - - for (LDObject* obj : selection()) - if (static_cast<LDSubfile*> (obj)->fileInfo()->name() != refName) - return; - } - - getCurrentDocument()->clearSelection(); - - for (LDObject* obj : getCurrentDocument()->objects()) - { - if (obj->type() != type) - continue; - - if (type == LDObject::ESubfile && static_cast<LDSubfile*> (obj)->fileInfo()->name() != refName) - continue; - - obj->select(); - } - - updateSelection(); -} - -// ============================================================================= -// -DEFINE_ACTION (GridCoarse, 0) -{ - grid = Grid::Coarse; - updateGridToolBar(); -} - -DEFINE_ACTION (GridMedium, 0) -{ - grid = Grid::Medium; - updateGridToolBar(); -} - -DEFINE_ACTION (GridFine, 0) -{ - grid = Grid::Fine; - updateGridToolBar(); -} - -// ============================================================================= -// -DEFINE_ACTION (ResetView, CTRL (0)) -{ - R()->resetAngles(); - R()->update(); -} - -// ============================================================================= -// -DEFINE_ACTION (InsertFrom, 0) -{ - QString fname = QFileDialog::getOpenFileName(); - int idx = getInsertionPoint(); - - if (!fname.length()) - return; - - QFile f (fname); - - if (!f.open (QIODevice::ReadOnly)) - { - critical (format ("Couldn't open %1 (%2)", fname, f.errorString())); - return; - } - - LDObjectList objs = loadFileContents (&f, null); - - getCurrentDocument()->clearSelection(); - - for (LDObject* obj : objs) - { - getCurrentDocument()->insertObj (idx, obj); - obj->select(); - R()->compileObject (obj); - - idx++; - } - - refresh(); - scrollToSelection(); -} - -// ============================================================================= -// -DEFINE_ACTION (ExportTo, 0) -{ - if (selection().isEmpty()) - return; - - QString fname = QFileDialog::getSaveFileName(); - - if (fname.length() == 0) - return; - - QFile file (fname); - - if (!file.open (QIODevice::WriteOnly | QIODevice::Text)) - { - critical (format ("Unable to open %1 for writing (%2)", fname, file.errorString())); - return; - } - - for (LDObject* obj : selection()) - { - QString contents = obj->asText(); - QByteArray data = contents.toUtf8(); - file.write (data, data.size()); - file.write ("\r\n", 2); - } -} - -// ============================================================================= -// -DEFINE_ACTION (InsertRaw, 0) -{ - int idx = 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 (QString line : QString (te_edit->toPlainText()).split ("\n")) - { - LDObject* obj = parseLine (line); - - getCurrentDocument()->insertObj (idx, obj); - obj->select(); - R()->compileObject (obj); - idx++; - } - - refresh(); - scrollToSelection(); -} - -// ============================================================================= -// -DEFINE_ACTION (Screenshot, 0) -{ - setlocale (LC_ALL, "C"); - - int w, h; - uchar* imgdata = R()->getScreencap (w, h); - QImage img = imageFromScreencap (imgdata, w, h); - - QString root = basename (getCurrentDocument()->name()); - - if (root.right (4) == ".dat") - root.chop (4); - - QString defaultname = (root.length() > 0) ? format ("%1.png", root) : ""; - QString 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 (format ("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; - updateActions(); - R()->update(); -} - -// ============================================================================= -// -DEFINE_ACTION (VisibilityToggle, 0) -{ - for (LDObject* obj : selection()) - obj->setHidden (!obj->isHidden()); - - refresh(); -} - -// ============================================================================= -// -DEFINE_ACTION (VisibilityHide, 0) -{ - for (LDObject* obj : selection()) - obj->setHidden (true); - - refresh(); -} - -// ============================================================================= -// -DEFINE_ACTION (VisibilityReveal, 0) -{ - for (LDObject* obj : selection()) - obj->setHidden (false); - refresh(); -} - -// ============================================================================= -// -DEFINE_ACTION (Wireframe, 0) -{ - gl_wireframe = !gl_wireframe; - R()->refresh(); -} - -// ============================================================================= -// -DEFINE_ACTION (SetOverlay, 0) -{ - OverlayDialog dlg; - - if (!dlg.exec()) - return; - - R()->setupOverlay ((GL::EFixedCamera) dlg.camera(), dlg.fpath(), dlg.ofsx(), - dlg.ofsy(), dlg.lwidth(), dlg.lheight()); -} - -// ============================================================================= -// -DEFINE_ACTION (ClearOverlay, 0) -{ - R()->clearOverlay(); -} - -// ============================================================================= -// -DEFINE_ACTION (ModeSelect, CTRL (1)) -{ - R()->setEditMode (ESelectMode); -} - -// ============================================================================= -// -DEFINE_ACTION (ModeDraw, CTRL (2)) -{ - R()->setEditMode (EDrawMode); -} - -// ============================================================================= -// -DEFINE_ACTION (ModeCircle, CTRL (3)) -{ - R()->setEditMode (ECircleMode); -} - -// ============================================================================= -// -DEFINE_ACTION (DrawAngles, 0) -{ - gl_drawangles = !gl_drawangles; - R()->refresh(); -} - -// ============================================================================= -// -DEFINE_ACTION (SetDrawDepth, 0) -{ - if (R()->camera() == GL::EFreeCamera) - return; - - bool ok; - double depth = QInputDialog::getDouble (g_win, "Set Draw Depth", - format ("Depth value for %1 Camera:", R()->getCameraName()), - R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok); - - if (ok) - 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) -{ - PrimitiveScanner::start(); -} - -// ============================================================================= -// -DEFINE_ACTION (BFCView, SHIFT (B)) -{ - gl_colorbfc = !gl_colorbfc; - updateActions(); - R()->refresh(); -} - -// ============================================================================= -// -DEFINE_ACTION (JumpTo, CTRL (G)) -{ - bool ok; - int defval = 0; - LDObject* obj; - - if (selection().size() == 1) - defval = selection()[0]->lineNumber(); - - 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(); - updateSelection(); -} - -// ============================================================================= -// -DEFINE_ACTION (SubfileSelection, 0) -{ - if (selection().size() == 0) - return; - - QString parentpath = getCurrentDocument()->fullPath(); - - // BFC type of the new subfile - it shall inherit the BFC type of the parent document - LDBFC::Statement bfctype = LDBFC::NoCertify; - - // Dirname of the new subfile - QString subdirname = dirname (parentpath); - - // Title of the new subfile - QString subtitle; - - // Comment containing the title of the parent document - LDComment* titleobj = dynamic_cast<LDComment*> (getCurrentDocument()->getObject (0)); - - // License text for the subfile - QString license = getLicenseText (ld_defaultlicense); - - // LDraw code body of the new subfile (i.e. code of the selection) - QStringList code; - - // Full path of the subfile to be - QString fullsubname; - - // Where to insert the subfile reference? - int refidx = selection()[0]->lineNumber(); - - // Determine title of subfile - if (titleobj != null) - subtitle = "~" + titleobj->text(); - else - subtitle = "~subfile"; - - // Remove duplicate tildes - while (subtitle[0] == '~' && subtitle[1] == '~') - subtitle.remove (0, 1); - - // If this the parent document isn't already in s/, we need to stuff it into - // a subdirectory named s/. Ensure it exists! - QString topdirname = basename (dirname (getCurrentDocument()->fullPath())); - - if (topdirname != "s") - { - QString desiredPath = subdirname + "/s"; - QString title = tr ("Create subfile directory?"); - QString text = format (tr ("The directory <b>%1</b> is suggested for " - "subfiles. This directory does not exist, create it?"), desiredPath); - - if (QDir (desiredPath).exists() || confirm (title, text)) - { - subdirname = desiredPath; - QDir().mkpath (subdirname); - } - } - - // Determine the body of the name of the subfile - if (!parentpath.isEmpty()) - { - if (parentpath.endsWith (".dat")) - parentpath.chop (4); - - // Remove the s?? suffix if it's there, otherwise we'll get filenames - // like s01s01.dat when subfiling subfiles. - QRegExp subfilesuffix ("s[0-9][0-9]$"); - if (subfilesuffix.indexIn (parentpath) != -1) - parentpath.chop (subfilesuffix.matchedLength()); - - int subidx = 1; - QString digits; - QFile f; - QString testfname; - - do - { - digits.setNum (subidx++); - - // pad it with a zero - if (digits.length() == 1) - digits.prepend ("0"); - - fullsubname = subdirname + "/" + basename (parentpath) + "s" + digits + ".dat"; - } while (findDocument ("s\\" + basename (fullsubname)) != null || QFile (fullsubname).exists()); - } - - // Determine the BFC winding type used in the main document - it is to - // be carried over to the subfile. - for (LDObject* obj : getCurrentDocument()->objects()) - { - LDBFC* bfc = dynamic_cast<LDBFC*> (obj); - - if (!bfc) - continue; - - LDBFC::Statement a = bfc->statement(); - - if (a == LDBFC::CertifyCCW || a == LDBFC::CertifyCW || a == LDBFC::NoCertify) - { - bfctype = a; - break; - } - } - - // Get the body of the document in LDraw code - for (LDObject* obj : selection()) - code << obj->asText(); - - // Create the new subfile document - LDDocument* doc = new LDDocument; - doc->setImplicit (false); - doc->setFullPath (fullsubname); - doc->setName (LDDocument::shortenName (fullsubname)); - doc->addObjects ( - { - new LDComment (subtitle), - new LDComment ("Name: "), - new LDComment (format ("Author: %1 [%2]", ld_defaultname, ld_defaultuser)), - new LDComment (format ("!LDRAW_ORG Unofficial_Subpart")), - (license != "" ? new LDComment (license) : null), - new LDEmpty, - new LDBFC (bfctype), - new LDEmpty, - }); - - // Add the actual subfile code to the new document - for (QString line : code) - { - LDObject* obj = parseLine (line); - doc->addObject (obj); - } - - // Try save it - if (save (doc, true)) - { - // Remove the selection now - for (LDObject* obj : selection()) - obj->destroy(); - - // Compile all objects in the new subfile - for (LDObject* obj : doc->objects()) - R()->compileObject (obj); - - g_loadedFiles << doc; - - // Add a reference to the new subfile to where the selection was - LDSubfile* ref = new LDSubfile(); - ref->setColor (maincolor); - ref->setFileInfo (doc); - ref->setPosition (g_origin); - ref->setTransform (g_identity); - getCurrentDocument()->insertObj (refidx, ref); - R()->compileObject (ref); - - // Refresh stuff - updateDocumentList(); - doFullRefresh(); - } - else - { - // Failed to save. - delete doc; - } -} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/actionsEdit.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,836 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "mainWindow.h" +#include "main.h" +#include "ldDocument.h" +#include "colorSelector.h" +#include "miscallenous.h" +#include "radioGroup.h" +#include "glRenderer.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() +{ + LDObjectList objs = selection(); + int num = 0; + + // Clear the clipboard first. + qApp->clipboard()->clear(); + + // Now, copy the contents into the clipboard. + QString data; + + for (LDObject* obj : objs) + { + if (data.length() > 0) + data += "\n"; + + data += obj->asText(); + ++num; + } + + qApp->clipboard()->setText (data); + return num; +} + +// ============================================================================= +// +DEFINE_ACTION (Cut, CTRL (X)) +{ + int num = copyToClipboard(); + deleteSelection(); + print (tr ("%1 objects cut"), num); +} + +// ============================================================================= +// +DEFINE_ACTION (Copy, CTRL (C)) +{ + int num = copyToClipboard(); + print (tr ("%1 objects copied"), num); +} + +// ============================================================================= +// +DEFINE_ACTION (Paste, CTRL (V)) +{ + const QString clipboardText = qApp->clipboard()->text(); + int idx = getInsertionPoint(); + getCurrentDocument()->clearSelection(); + int num = 0; + + for (QString line : clipboardText.split ("\n")) + { + LDObject* pasted = parseLine (line); + getCurrentDocument()->insertObj (idx++, pasted); + pasted->select(); + R()->compileObject (pasted); + ++num; + } + + print (tr ("%1 objects pasted"), num); + refresh(); + scrollToSelection(); +} + +// ============================================================================= +// +DEFINE_ACTION (Delete, KEY (Delete)) +{ + int num = deleteSelection(); + print (tr ("%1 objects deleted"), num); +} + +// ============================================================================= +// +static void doInline (bool deep) +{ + LDObjectList sel = selection(); + + for (LDObject* obj : sel) + { + // Get the index of the subfile so we know where to insert the + // inlined contents. + long idx = obj->lineNumber(); + + if (idx == -1) + continue; + + LDObjectList objs; + + if (obj->type() == LDObject::ESubfile) + { + LDSubfile::InlineFlags flags = deep ? LDSubfile::DeepCacheInline : LDSubfile::CacheInline; + objs = static_cast<LDSubfile*> (obj)->inlineContents (flags); + } + else + continue; + + // Merge in the inlined objects + for (LDObject* inlineobj : objs) + { + QString line = inlineobj->asText(); + inlineobj->destroy(); + LDObject* newobj = parseLine (line); + getCurrentDocument()->insertObj (idx++, newobj); + newobj->select(); + g_win->R()->compileObject (newobj); + } + + // Delete the subfile now as it's been inlined. + obj->destroy(); + } + + g_win->refresh(); +} + +DEFINE_ACTION (Inline, CTRL (I)) +{ + doInline (false); +} + +DEFINE_ACTION (InlineDeep, CTRL_SHIFT (I)) +{ + doInline (true); +} + +// ============================================================================= +// +DEFINE_ACTION (SplitQuads, 0) +{ + LDObjectList objs = selection(); + int num = 0; + + for (LDObject* obj : objs) + { + if (obj->type() != LDObject::EQuad) + continue; + + // Find the index of this quad + long index = obj->lineNumber(); + + 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) + R()->compileObject (t); + + // Delete this quad now, it has been split. + obj->destroy(); + + num++; + } + + print ("%1 quadrilaterals split", num); + 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->asText()); + + if (obj->type() == LDObject::EError) + 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 + R()->compileObject (obj); + refresh(); +} + +// ============================================================================= +// +DEFINE_ACTION (SetColor, KEY (C)) +{ + if (selection().isEmpty()) + return; + + int colnum; + int defcol = -1; + + LDObjectList objs = selection(); + + // If all selected objects have the same color, said color is our default + // value to the color selection dialog. + defcol = 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); + R()->compileObject (obj); + } + + refresh(); + } +} + +// ============================================================================= +// +DEFINE_ACTION (Borders, CTRL_SHIFT (B)) +{ + LDObjectList objs = selection(); + int num = 0; + + for (LDObject* obj : objs) + { + const LDObject::Type type = obj->type(); + if (type != LDObject::EQuad && type != LDObject::ETriangle) + continue; + + int numLines; + LDLine* lines[4]; + + if (type == LDObject::EQuad) + { + numLines = 4; + + LDQuad* quad = static_cast<LDQuad*> (obj); + lines[0] = new LDLine (quad->vertex (0), quad->vertex (1)); + lines[1] = new LDLine (quad->vertex (1), quad->vertex (2)); + lines[2] = new LDLine (quad->vertex (2), quad->vertex (3)); + lines[3] = new LDLine (quad->vertex (3), quad->vertex (0)); + } + else + { + numLines = 3; + + LDTriangle* tri = static_cast<LDTriangle*> (obj); + lines[0] = new LDLine (tri->vertex (0), tri->vertex (1)); + lines[1] = new LDLine (tri->vertex (1), tri->vertex (2)); + lines[2] = new LDLine (tri->vertex (2), tri->vertex (0)); + } + + for (int i = 0; i < numLines; ++i) + { + long idx = obj->lineNumber() + i + 1; + + lines[i]->setColor (edgecolor); + getCurrentDocument()->insertObj (idx, lines[i]); + R()->compileObject (lines[i]); + } + + num += numLines; + } + + print (tr ("Added %1 border lines"), num); + refresh(); +} + +// ============================================================================= +// +DEFINE_ACTION (CornerVerts, 0) +{ + int num = 0; + + for (LDObject* obj : selection()) + { + if (obj->vertices() < 2) + continue; + + int ln = obj->lineNumber(); + + for (int i = 0; i < obj->vertices(); ++i) + { + LDVertex* vert = new LDVertex; + vert->pos = obj->vertex (i); + vert->setColor (obj->color()); + + getCurrentDocument()->insertObj (++ln, vert); + R()->compileObject (vert); + ++num; + } + } + + print (tr ("Added %1 vertices"), num); + refresh(); +} + +// ============================================================================= +// +static void doMoveSelection (const bool up) +{ + LDObjectList 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]; + vect[Y] *= *currentGrid().confs[Grid::Y]; + vect[Z] *= *currentGrid().confs[Grid::Z]; + + 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)) +{ + LDObjectList sel = selection(); + + for (LDObject* obj : sel) + { + obj->invert(); + R()->compileObject (obj); + } + + 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) +{ + LDObjectList sel = selection(); + QList<Vertex*> queue; + const Vertex rotpoint = rotPoint (sel); + const double angle = (pi * *currentGrid().confs[Grid::Angle]) / 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->vertex (i); + rotateVertex (v, rotpoint, transform); + obj->setVertex (i, v); + } + } + elif (obj->hasMatrix()) + { + LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj); + + // Transform the position + Vertex v = mo->position(); + rotateVertex (v, rotpoint, transform); + mo->setPosition (v); + + // Transform the matrix + mo->setTransform (transform * mo->transform()); + } + elif (obj->type() == LDObject::EVertex) + { + 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->position(); + Matrix t = mo->transform(); + + 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->vertex (i); + + for_axes (ax) + roundToDecimals (v[ax], 3); + + obj->setVertex (i, v); + R()->compileObject (obj); + num += 3; + } + } + } + + print (tr ("Rounded %1 values"), num); + refreshObjectList(); + refresh(); +} + +// ============================================================================= +// +DEFINE_ACTION (Uncolorize, 0) +{ + int num = 0; + + for (LDObject* obj : selection()) + { + if (obj->isColored() == false) + continue; + + int col = maincolor; + + if (obj->type() == LDObject::ELine || obj->type() == LDObject::ECondLine) + col = edgecolor; + + obj->setColor (col); + R()->compileObject (obj); + num++; + } + + print (tr ("%1 objects uncolored"), num); + 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->vertex (i); + + for (Axis ax : sel) + { + double& coord = v[ax]; + + if (any || coord == search) + { + if (!rel) + coord = 0; + + coord += replacement; + num++; + } + } + + obj->setVertex (i, v); + R()->compileObject (obj); + } + } + + print (tr ("Altered %1 values"), num); + 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->vertex (i); + + for (Axis ax : sel) + v[ax] *= -1; + + obj->setVertex (i, v); + R()->compileObject (obj); + } + } + + refresh(); +} + +// ============================================================================= +// +DEFINE_ACTION (Demote, 0) +{ + LDObjectList sel = selection(); + int num = 0; + + for (LDObject* obj : sel) + { + if (obj->type() != LDObject::ECondLine) + continue; + + LDLine* repl = static_cast<LDCondLine*> (obj)->demote(); + R()->compileObject (repl); + ++num; + } + + print (tr ("Demoted %1 conditional lines"), num); + refresh(); +} + +// ============================================================================= +// +static bool isColorUsed (int colnum) +{ + for (LDObject* obj : getCurrentDocument()->objects()) + if (obj->isColored() && obj->color() == 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) + { + print (tr ("Cannot auto-color: all colors are in use!")); + return; + } + + for (LDObject* obj : selection()) + { + if (obj->isColored() == false) + continue; + + obj->setColor (colnum); + R()->compileObject (obj); + } + + print (tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name); + 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 + QString commentText = format ("!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 != null && 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->lineNumber() : 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); + + buildObjList(); + delete ui; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/addObjectDialog.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,443 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "mainWindow.h" +#include "addObjectDialog.h" +#include "ldDocument.h" +#include "colors.h" +#include "colorSelector.h" +#include "editHistory.h" +#include "radioGroup.h" +#include "miscallenous.h" +#include "primitives.h" + +// ============================================================================= +// +class SubfileListItem : public QTreeWidgetItem +{ + PROPERTY (public, Primitive*, primitive, setPrimitive, STOCK_WRITE) + + public: + SubfileListItem (QTreeWidgetItem* parent, Primitive* info) : + QTreeWidgetItem (parent), + m_primitive (info) {} + + SubfileListItem (QTreeWidget* parent, Primitive* info) : + QTreeWidgetItem (parent), + m_primitive (info) {} +}; + +// ============================================================================= +// +AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) : + QDialog (parent) +{ + setlocale (LC_ALL, "C"); + + int coordCount = 0; + QString typeName = LDObject::typeName (type); + + switch (type) + { + case LDObject::EComment: + { + le_comment = new QLineEdit; + + if (obj) + le_comment->setText (static_cast<LDComment*> (obj)->text()); + + le_comment->setMinimumWidth (384); + } break; + + case LDObject::ELine: + { + coordCount = 6; + } break; + + case LDObject::ETriangle: + { + coordCount = 9; + } break; + + case LDObject::EQuad: + case LDObject::ECondLine: + { + coordCount = 12; + } break; + + case LDObject::EVertex: + { + coordCount = 3; + } break; + + case LDObject::EBFC: + { + 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::k_statementStrings[i]); + } + + if (obj) + rb_bfcType->setValue ( (int) static_cast<LDBFC*> (obj)->statement()); + } break; + + case LDObject::ESubfile: + { + 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->name()); + QList<QTreeWidgetItem*> subfileItems; + + for (Primitive& prim : cat->prims) + { + SubfileListItem* item = new SubfileListItem (parentItem, &prim); + item->setText (0, format ("%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)->fileInfo()->name() == 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->fileInfo()->name()); + } + } break; + + default: + { + critical (format ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName)); + } return; + } + + QPixmap icon = getIcon (format ("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->color(); + else + colnum = (type == LDObject::ECondLine || type == LDObject::ELine) ? 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::ELine: + case LDObject::ECondLine: + case LDObject::ETriangle: + case LDObject::EQuad: + + // 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->vertex (i).getCoordinate (j)); + } + + break; + + case LDObject::EComment: + layout->addWidget (le_comment, 0, 1); + break; + + case LDObject::EBFC: + layout->addWidget (rb_bfcType, 0, 1); + break; + + case LDObject::ESubfile: + 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->position()[ax]); + + defaultMatrix = mo->transform(); + } + + le_matrix->setText (defaultMatrix.toString()); + 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 (format (tr ("Edit %1"), typeName)); + + setWindowIcon (icon); + defaults->destroy(); +} + +// ============================================================================= +// ============================================================================= +void AddObjectDialog::setButtonBackground (QPushButton* button, int colnum) +{ + LDColor* col = ::getColor (colnum); + + button->setIcon (getIcon ("palette")); + button->setAutoFillBackground (true); + + if (col) + button->setStyleSheet (format ("background-color: %1", col->hexcode)); +} + +// ============================================================================= +// ============================================================================= +QString AddObjectDialog::currentSubfileName() +{ + SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem()); + + if (item->primitive() == null) + return ""; // selected a heading + + return item->primitive()->name; +} + +// ============================================================================= +// ============================================================================= +void AddObjectDialog::slot_colorButtonClicked() +{ + ColorSelector::selectColor (colnum, colnum, this); + setButtonBackground (pb_color, colnum); +} + +// ============================================================================= +// ============================================================================= +void AddObjectDialog::slot_subfileTypeChanged() +{ + QString 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->type() == LDObject::EError) + return; + + if (type == LDObject::EEmpty) + return; // Nothing to edit with empties + + const bool newObject = (obj == null); + Matrix transform = g_identity; + AddObjectDialog dlg (type, obj); + + assert (obj == null || obj->type() == type); + + if (dlg.exec() == false) + return; + + if (type == LDObject::ESubfile) + { + QStringList matrixstrvals = dlg.le_matrix->text().split (" ", QString::SkipEmptyParts); + + if (matrixstrvals.size() == 9) + { + double matrixvals[9]; + int i = 0; + + for (QString val : matrixstrvals) + matrixvals[i++] = val.toFloat(); + + transform = Matrix (matrixvals); + } + } + + switch (type) + { + case LDObject::EComment: + { + LDComment* comm = initObj<LDComment> (obj); + comm->setText (dlg.le_comment->text()); + } + break; + + case LDObject::ELine: + case LDObject::ETriangle: + case LDObject::EQuad: + case LDObject::ECondLine: + { + 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::EBFC: + { + LDBFC* bfc = initObj<LDBFC> (obj); + bfc->setStatement ((LDBFC::Statement) dlg.rb_bfcType->value()); + } break; + + case LDObject::EVertex: + { + LDVertex* vert = initObj<LDVertex> (obj); + + for_axes (ax) + vert->pos[ax] = dlg.dsb_coords[ax]->value(); + } + break; + + case LDObject::ESubfile: + { + QString name = dlg.le_subfileName->text(); + + if (name.length() == 0) + return; // no subfile filename + + LDDocument* file = getDocument (name); + + if (!file) + { + critical (format ("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->refresh(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/addObjectDialog.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,69 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QDialog> +#include "ldObject.h" + +class QTreeWidgetItem; +class QLineEdit; +class RadioGroup; +class QCheckBox; +class QSpinBox; +class QLabel; +class QTreeWidget; +class QDoubleSpinBox; + +class AddObjectDialog : public QDialog +{ + Q_OBJECT + + public: + AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent = null); + static void staticDialog (const LDObject::Type type, LDObject* obj); + + QLabel* lb_typeIcon; + + // Comment line edit + QLineEdit* le_comment; + + // Coordinate edits for.. anything with coordinates, really. + QDoubleSpinBox* dsb_coords[12]; + + // Color selection dialog button + QPushButton* pb_color; + + // BFC-related widgets + RadioGroup* rb_bfcType; + + // Subfile stuff + QTreeWidget* tw_subfileList; + QLineEdit* le_subfileName; + QLabel* lb_subfileName; + QLineEdit* le_matrix; + + private: + void setButtonBackground (QPushButton* button, int color); + QString currentSubfileName(); + + int colnum; + + private slots: + void slot_colorButtonClicked(); + void slot_subfileTypeChanged(); +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/basics.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,403 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "basics.h" +#include "miscallenous.h" +#include "ldObject.h" +#include "ldDocument.h" + +// ============================================================================= +// +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]; +} + +// ============================================================================= +// +double Vertex::distanceTo (const Vertex& other) const +{ + double dx = abs (x() - other.x()); + double dy = abs (y() - other.y()); + double dz = abs (z() - other.z()); + return sqrt ((dx * dx) + (dy * dy) + (dz * dz)); +} + +// ============================================================================= +// +Vertex Vertex::midpoint (const Vertex& other) +{ + Vertex mid; + + for_axes (ax) + mid[ax] = (getCoordinate (ax) + other[ax]) / 2; + + return mid; +} + +// ============================================================================= +// +QString Vertex::toString (bool mangled) const +{ + QString formatstr = "%1 %2 %3"; + + if (mangled) + formatstr = "(%1, %2, %3)"; + + return format (formatstr, x(), y(), z()); +} + +// ============================================================================= +// +void Vertex::transform (const Matrix& matr, const 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); +} + +// ============================================================================= +// +bool Vertex::operator== (const Vertex& other) const +{ + return getCoordinate (X) == other[X] && + getCoordinate (Y) == other[Y] && + getCoordinate (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 (getCoordinate (X) < other[X]) + return true; + + if (getCoordinate (X) > other[X]) + return false; + + if (getCoordinate (Y) < other[Y]) + return true; + + if (getCoordinate (Y) > other[Y]) + return false; + + return getCoordinate (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 (const std::initializer_list< double >& vals) +{ + assert (vals.size() == 9); + memcpy (&m_vals[0], & (*vals.begin()), sizeof m_vals); +} + +// ============================================================================= +// +void Matrix::dump() const +{ + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + print ("%1\t", m_vals[ (i * 3) + j]); + + print ("\n"); + } +} + +// ============================================================================= +// +QString Matrix::toString() const +{ + QString val; + + for (int i = 0; i < 9; ++i) + { + if (i > 0) + val += ' '; + + val += QString::number (m_vals[i]); + } + + return val; +} + +// ============================================================================= +// +void Matrix::zero() +{ + memset (&m_vals[0], 0, sizeof m_vals); +} + +// ============================================================================= +// +Matrix Matrix::mult (const 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= (const Matrix& other) +{ + memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals); + return *this; +} + +// ============================================================================= +// +double Matrix::getDeterminant() const +{ + return (value (0) * value (4) * value (8)) + + (value (1) * value (5) * value (6)) + + (value (2) * value (3) * value (7)) - + (value (2) * value (4) * value (6)) - + (value (1) * value (3) * value (8)) - + (value (0) * value (5) * value (7)); +} + +// ============================================================================= +// +bool Matrix::operator== (const Matrix& other) const +{ + for (int i = 0; i < 9; ++i) + if (value (i) != other[i]) + return false; + + return true; +} + +// ============================================================================= +// +LDBoundingBox::LDBoundingBox() +{ + reset(); +} + +// ============================================================================= +// +void LDBoundingBox::calculateFromCurrentDocument() +{ + reset(); + + if (!getCurrentDocument()) + return; + + for (LDObject* obj : getCurrentDocument()->objects()) + calcObject (obj); +} + +// ============================================================================= +// +void LDBoundingBox::calcObject (LDObject* obj) +{ + switch (obj->type()) + { + case LDObject::ELine: + case LDObject::ETriangle: + case LDObject::EQuad: + case LDObject::ECondLine: + { + for (int i = 0; i < obj->vertices(); ++i) + calcVertex (obj->vertex (i)); + } break; + + case LDObject::ESubfile: + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline); + + for (LDObject * obj : objs) + { + calcObject (obj); + obj->destroy(); + } + } + 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& vertex) +{ + for_axes (ax) + { + m_vertex0[ax] = min (vertex[ax], m_vertex0[ax]); + m_vertex1[ax] = max (vertex[ax], m_vertex1[ax]); + } + + setEmpty (false); +} + +// ============================================================================= +// +void LDBoundingBox::reset() +{ + m_vertex0[X] = m_vertex0[Y] = m_vertex0[Z] = 10000.0; + m_vertex1[X] = m_vertex1[Y] = m_vertex1[Z] = -10000.0; + setEmpty (true); +} + +// ============================================================================= +// +double LDBoundingBox::longestMeasurement() 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); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/basics.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,335 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QString> +#include <QObject> +#include <QStringList> +#include <QMetaType> +#include "macros.h" + +class LDObject; +class QFile; +class QTextStream; + +using int8 = qint8; +using int16 = qint16; +using int32 = qint32; +using int64 = qint64; +using uint8 = quint8; +using uint16 = quint16; +using uint32 = quint32; +using uint64 = quint64; + +template<typename T, typename R> +using Pair = std::pair<T, R>; + +enum Axis +{ + X, + Y, + Z +}; + +// ============================================================================= +// +class LDObject; +using LDObjectList = QList<LDObject*>; + +//! +//! \brief A mathematical 3 x 3 matrix +//! +class Matrix +{ + public: + //! Constructs a matrix with undetermined values. + Matrix() {} + + //! Constructs a matrix with the given values. + //! \note \c vals is expected to have exactly 9 elements. + Matrix (const std::initializer_list<double>& vals); + + //! Constructs a matrix all 9 elements initialized to the same value. + //! \param fillval the value to initialize the matrix coordinates as + Matrix (double fillval); + + //! Constructs a matrix with a C-array. + //! \note \c vals is expected to have exactly 9 elements. + Matrix (double vals[]); + + //! Calculates the matrix's determinant. + //! \returns the calculated determinant. + double getDeterminant() const; + + //! Multiplies this matrix with \c other + //! \param other the matrix to multiply with. + //! \returns the resulting matrix + //! \note a.mult(b) is not equivalent to b.mult(a)! + Matrix mult (const Matrix& other) const; + + //! Prints the matrix to stdout. + void dump() const; + + //! \returns a string representation of the matrix. + QString toString() const; + + //! Zeroes the matrix out. + void zero(); + + //! Assigns the matrix values to the values of \c other. + //! \param other the matrix to assign this to. + //! \returns a reference to self + Matrix& operator= (const Matrix& other); + + //! \returns a mutable reference to a value by \c idx + inline double& value (int idx) + { + return m_vals[idx]; + } + + //! An overload of \c value() for const matrices. + //! \returns a const reference to a value by \c idx + inline const double& value (int idx) const + { + return m_vals[idx]; + } + + //! An operator overload for \c mult(). + //! \returns the multiplied matrix. + inline Matrix operator* (const Matrix& other) const + { + return mult (other); + } + + //! An operator overload for \c value(). + //! \returns a mutable reference to a value by \c idx + inline double& operator[] (int idx) + { + return value (idx); + } + + //! An operator overload for \c value() const. + //! \returns a const reference to a value by \c idx + inline const double& operator[] (int idx) const + { + return value (idx); + } + + //! \param other the matrix to check against + //! \returns whether the two matrices have the same values. + bool operator== (const Matrix& other) const; + + private: + double m_vals[9]; +}; + +//! +//! \brief A vertex in 3D space +//! +//! Contains a single point in 3D space. Not to be confused with +//! LDVertex, which is a vertex used in an LDraw part file. +//! +//! This also sees use as a position vector. +//! +class Vertex +{ + public: + //! Constructs a zero vertex + Vertex() : + m_coords{0, 0, 0} {} + + //! Constructs a vertex with the given \c x, \c y and \c z. + Vertex (double x, double y, double z); + + //! \returns the distance from this vertex to \c other + double distanceTo (const Vertex& other) const; + + //! \returns the vertex at the midpoint between this and \c other + Vertex midpoint (const Vertex& other); + + //! Moves this vertex using \param other as a position vector. + void move (const Vertex& other); + + //! Yields a string representation of the vertex. The string returned + //! can possibly be mangled. + //! - As mangled: {1.5, 2.8, 3.14} + //! - Without mangling: 1.5 2.8 3.14 + //! + //! The mangled version is suitable for printing to the user, the + //! non-mangled one is used when writing the vertex to LDraw files. + //! + //! \returns a string representation of this vertex + //! \param mangled whether to return a mangled representation or not + QString toString (bool mangled) const; + + //! Transforms this vertex with \c matr as transformation matrix + //! and \c pos as the position column of the 4x4 matrix. + void transform (const Matrix& matr, const Vertex& pos); + + //! An operator overload for \c move(). + Vertex& operator+= (const Vertex& other); + + //! An operator overload for \c move(), using a temporary vertex. + Vertex operator+ (const Vertex& other) const; + + //! Divides all values by \c d. + Vertex operator/ (const double d) const; + + //! Divides all values by \c d. + Vertex& operator/= (const double d); + + //! Checks whether this vertex has the same values as \c other. + bool operator== (const Vertex& other) const; + + //! Checks whether this vertex has different values than \c other. + bool operator!= (const Vertex& other) const; + + //! \returns a negated version the vertex + Vertex operator-() const; + + //! \returns whether the vertex has lesser values than \c other. + int operator< (const Vertex& other) const; + + //! An operator overload for \c getCoordinate(). + inline double& operator[] (const Axis ax) + { + return getCoordinate ((int) ax); + } + + //! An operator overload for \c getCoordinate() const. + inline const double& operator[] (const Axis ax) const + { + return getCoordinate ((int) ax); + } + + //! An operator overload for \c getCoordinate(). + inline double& operator[] (const int ax) + { + return getCoordinate (ax); + } + + //! An operator overload for \c getCoordinate() const. + inline const double& operator[] (const int ax) const + { + return getCoordinate (ax); + } + + //! \returns a mutable reference for the coordinate designated by \param n. + inline double& getCoordinate (int n) + { + return m_coords[n]; + } + + //! An overload of \c getCoordinate for const vertices. + //! \returns a const reference for the coordinate designated by \param n. + inline const double& getCoordinate (int n) const + { + return m_coords[n]; + } + + //! \returns a mutable reference to X. + inline double& x() + { + return m_coords[X]; + } + + //! An overload of \c x() for const vertices. + //! \returns a const reference to X. + inline const double& x() const + { + return m_coords[X]; + } + + //! \returns a mutable reference to Y. + inline double& y() + { + return m_coords[Y]; + } + + //! An overload of \c y() for const vertices. + //! \returns a const reference to Y. + inline const double& y() const + { + return m_coords[Y]; + } + + //! \returns a mutable reference to Z. + inline double& z() + { + return m_coords[Z]; + } + + //! An overload of \c z() for const vertices. + //! \returns a const reference to Z. + inline const double& z() const + { + return m_coords[Z]; + } + + private: + double m_coords[3]; +}; + +Q_DECLARE_METATYPE (Vertex) + +//! +//! Defines a bounding box that encompasses a given set of objects. +//! vertex0 is the minimum vertex, vertex1 is the maximum vertex. +// +class LDBoundingBox +{ + PROPERTY (private, bool, isEmpty, setEmpty, STOCK_WRITE) + PROPERTY (private, Vertex, vertex0, setVertex0, STOCK_WRITE) + PROPERTY (private, Vertex, vertex1, setVertex1, STOCK_WRITE) + + public: + //! Constructs an empty bounding box. + LDBoundingBox(); + + //! Clears the bounding box + void reset(); + + //! Calculates the bounding box's values from the objects in the current + //! document. + void calculateFromCurrentDocument(); + + //! \returns the length of the bounding box on the longest measure. + double longestMeasurement() const; + + //! Calculates the given \c obj to the bounding box, adjusting + //! extremas if necessary. + void calcObject (LDObject* obj); + + //! Calculates the given \c vertex to the bounding box, adjusting + //! extremas if necessary. + void calcVertex (const Vertex& vertex); + + //! \returns the center of the bounding box. + Vertex center() const; + + //! An operator overload for \c calcObject() + LDBoundingBox& operator<< (LDObject* obj); + + //! An operator overload for \c calcVertex() + LDBoundingBox& operator<< (const Vertex& v); +}; + +extern const Vertex g_origin; // Vertex at (0, 0, 0) +extern const Matrix g_identity; // Identity matrix + +static const double pi = 3.14159265358979323846;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colorSelector.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,210 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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.cxx: Color selector box. + */ + +#include <QGraphicsScene> +#include <QGraphicsItem> +#include <QMouseEvent> +#include <QScrollBar> + +#include "main.h" +#include "mainWindow.h" +#include "colorSelector.h" +#include "colors.h" +#include "configuration.h" +#include "miscallenous.h" +#include "ui_colorsel.h" + +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 (format ("%1", i)); + numtext->setDefaultTextColor ( (luma (col) < 80) ? Qt::white : Qt::black); + numtext->setPos (x, y); + + if (selection() && i == selection()->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 (!selection()) + { + ui->colorLabel->setText ("---"); + return; + } + + ui->colorLabel->setText (format ("%1 - %2", selection()->index, selection()->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 (selection() && selection()->index >= visibleColors) + { + int y = (selection()->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.selection() != null) + { + val = dlg.selection()->index; + return true; + } + + return false; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colorSelector.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,50 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QDialog> +#include "main.h" + +class LDColor; +class Ui_ColorSelUI; +class QGraphicsScene; + +class ColorSelector : public QDialog +{ + Q_OBJECT + PROPERTY (private, LDColor*, selection, setSelection, STOCK_WRITE) + + public: + explicit ColorSelector (int defval = -1, QWidget* parent = null); + virtual ~ColorSelector(); + static bool selectColor (int& val, int defval = -1, QWidget* parent = null); + + protected: + void mousePressEvent (QMouseEvent* event); + void resizeEvent (QResizeEvent* ev); + + private: + Ui_ColorSelUI* ui; + QGraphicsScene* m_scene; + bool m_firstResize; + + int numRows() const; + int viewportWidth() const; + void drawScene(); + void drawColorInfo(); +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colors.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,82 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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.cxx: 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 "ldDocument.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "ldConfig.h" +#include <QColor> + +static LDColor* g_LDColors[MAX_COLORS]; + +// ============================================================================= +// ============================================================================= +void initColors() +{ + LDColor* col; + print ("Initializing color information.\n"); + + // 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/colors.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,42 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QColor> +#include "main.h" + +#define MAX_COLORS 512 + +class LDColor +{ + public: + QString name, hexcode; + QColor faceColor, edgeColor; + int index; +}; + +void initColors(); +int luma (QColor& col); + +// Safely gets a color with the given number or null if no such color. +LDColor* getColor (int colnum); +void setColor (int colnum, LDColor* col); + +// Main and edge color identifiers +static const int maincolor = 16; +static const int edgecolor = 24;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/configDialog.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,803 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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.cxx: Settings dialog and everything related to it. + * Actual configuration core is in config.cxx. + */ + +#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 "ldDocument.h" +#include "configuration.h" +#include "miscallenous.h" +#include "colors.h" +#include "colorSelector.h" +#include "glRenderer.h" +#include "ui_config.h" + +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 (Bool, gl_linelengths); +extern_cfg (String, ld_defaultname); +extern_cfg (String, ld_defaultuser); +extern_cfg (Int, ld_defaultlicense); +extern_cfg (String, gl_selectcolor); +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); + +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())); + + setButtonBackground (ui->selColorButton, gl_selectcolor); + connect (ui->selColorButton, SIGNAL (clicked()), + this, SLOT (slot_setGLSelectColor())); + + 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); + ui->linelengths->setChecked (gl_linelengths); + + int i = 0; + + for (QAction* act : g_win->findChildren<QAction*>()) + { + KeySequenceConfig* cfg = g_win->shortcutForAction (act); + + if (cfg) + addShortcut (*cfg, act, i); + } + + 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 : QList<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 (format ("grid-%1", QString (g_GridInfo[i].name).toLower()))); + + // Text label + lb_gridLabels[i] = new QLabel (format ("%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; + + // Set the maximum angle + if (j == 3) + dsb_gridData[i][j]->setMaximum (360); + + dsb_gridData[i][j]->setValue (*g_GridInfo[i].confs[j]); + gridlayout->addWidget (dsb_gridData[i][j], i + 1, j + 1); + } + } + + ui->grids->setLayout (gridlayout); +} + +// ============================================================================= +// ============================================================================= +static struct LDExtProgInfo +{ + const QString name, + iconname; + QString* const path; + QLineEdit* input; + QPushButton* setPathButton; +#ifndef _WIN32 + bool* const wine; + 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 (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); + 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(); + gl_linelengths = ui->linelengths->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] = dsb_gridData[i][j]->value(); + + // Apply key shortcuts + g_win->updateActionShortcuts(); + + // 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->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.color(); + + 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->color()->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 (QString& conf, QPushButton* button) +{ + QColor col = QColorDialog::getColor (QColor (conf)); + + if (col.isValid()) + { + int r = col.red(), + g = col.green(), + b = col.blue(); + + QString colname; + colname.sprintf ("#%.2X%.2X%.2X", r, g, b); + conf = colname; + setButtonBackground (button, colname); + } +} + +// ============================================================================= +// ============================================================================= +void ConfigDialog::slot_setGLBackground() +{ + pickColor (gl_bgcolor, ui->backgroundColorButton); +} + +// ============================================================================= +// ============================================================================= +void ConfigDialog::slot_setGLForeground() +{ + pickColor (gl_maincolor, ui->mainColorButton); +} + +// ============================================================================= +// ============================================================================= +void ConfigDialog::slot_setGLSelectColor() +{ + pickColor (gl_selectcolor, ui->selColorButton); +} + +// ============================================================================= +// Sets background color of a given button. +// ============================================================================= +void ConfigDialog::setButtonBackground (QPushButton* button, QString value) +{ + button->setIcon (getIcon ("colorselect")); + button->setAutoFillBackground (true); + button->setStyleSheet (format ("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->keyConfig(), this)) + setShortcutText (item); +} + +// ============================================================================= +// Reset a shortcut to defaults +// ============================================================================= +void ConfigDialog::slot_resetShortcut() +{ + QList<ShortcutListItem*> sel = getShortcutSelection(); + + for (ShortcutListItem* item : sel) + { + item->keyConfig()->reset(); + setShortcutText (item); + } +} + +// ============================================================================= +// Remove the shortcut of an action. +// ============================================================================= +void ConfigDialog::slot_clearShortcut() +{ + QList<ShortcutListItem*> sel = getShortcutSelection(); + + for (ShortcutListItem* item : sel) + { + item->keyConfig()->setValue (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); + QString fpath = QFileDialog::getOpenFileName (this, format ("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() +{ + QString 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->action(); + QString label = act->iconText(); + QString keybind = item->keyConfig()->getValue().toString(); + item->setText (format ("%1 (%2)", label, keybind)); +} + +// ============================================================================= +// Gets the configuration string of the quick color toolbar +// ============================================================================= +QString ConfigDialog::quickColorString() +{ + QString val; + + for (const LDQuickColor& entry : quickColors) + { + if (val.length() > 0) + val += ':'; + + if (entry.isSeparator()) + val += '|'; + else + val += format ("%1", entry.color()->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->getValue(), parent); + + if (dlg.exec() == false) + return false; + + cfg->setValue (dlg.seq); + return true; +} + +// ============================================================================= +// ============================================================================= +void KeySequenceDialog::updateOutput() +{ + QString shortcut = seq.toString(); + + if (seq == QKeySequence()) + shortcut = "<empty>"; + + QString text = format ("<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/configDialog.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,119 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include "mainWindow.h" +#include <QDialog> + +class Ui_ConfigUI; +class QLabel; +class QDoubleSpinBox; + +// ============================================================================= +class ShortcutListItem : public QListWidgetItem +{ + PROPERTY (public, KeySequenceConfig*, keyConfig, setKeyConfig, STOCK_WRITE) + PROPERTY (public, QAction*, action, setAction, STOCK_WRITE) + + public: + explicit ShortcutListItem (QListWidget* view = null, int type = Type) : + QListWidgetItem (view, type) {} +}; + +// ============================================================================= +class ConfigDialog : public QDialog +{ + Q_OBJECT + + public: + enum Tab + { + InterfaceTab, + ProfileTab, + ShortcutsTab, + QuickColorsTab, + GridsTab, + ExtProgsTab, + DownloadTab + }; + + explicit ConfigDialog (Tab deftab = InterfaceTab, QWidget* parent = null, Qt::WindowFlags f = 0); + virtual ~ConfigDialog(); + float getGridValue (int i, int j) const; + + QList<LDQuickColor> quickColors; + QDoubleSpinBox* dsb_gridData[3][4]; + + private: + Ui_ConfigUI* ui; + QLabel* lb_gridLabels[3]; + QLabel* lb_gridIcons[3]; + QList<QListWidgetItem*> quickColorItems; + + void applySettings(); + void addShortcut (KeySequenceConfig& cfg, QAction* act, int& i); + void setButtonBackground (QPushButton* button, QString value); + void pickColor (QString& conf, QPushButton* button); + void updateQuickColorList (LDQuickColor* sel = null); + void setShortcutText (ShortcutListItem* item); + int getItemRow (QListWidgetItem* item, QList<QListWidgetItem*>& haystack); + QString quickColorString(); + QListWidgetItem* getSelectedQuickColor(); + QList<ShortcutListItem*> getShortcutSelection(); + void initGrids(); + void initExtProgs(); + + private slots: + void slot_setGLBackground(); + void slot_setGLForeground(); + void slot_setGLSelectColor(); + void slot_setShortcut(); + void slot_resetShortcut(); + void slot_clearShortcut(); + void slot_setColor(); + void slot_delColor(); + void slot_addColorSeparator(); + void slot_moveColor(); + void slot_clearColors(); + void slot_setExtProgPath(); + void slot_findDownloadFolder(); + void buttonClicked (QAbstractButton* button); + void selectPage (int row); +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class KeySequenceDialog : public QDialog +{ + Q_OBJECT + + public: + explicit KeySequenceDialog (QKeySequence seq, QWidget* parent = null, Qt::WindowFlags f = 0); + static bool staticDialog (KeySequenceConfig* cfg, QWidget* parent = null); + + QLabel* lb_output; + QDialogButtonBox* bbx_buttons; + QKeySequence seq; + + private: + void updateOutput(); + + private slots: + virtual void keyPressEvent (QKeyEvent* ev) override; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/configuration.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,179 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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.cxx: 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 "configuration.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "ldDocument.h" + +#ifdef _WIN32 +# define EXTENSION ".ini" +#else +# define EXTENSION ".cfg" +#endif // _WIN32 + +Config* g_configPointers[MAX_CONFIG]; +static int g_cfgPointerCursor = 0; +static QMap<QString, Config*> g_configsByName; +static QList<Config*> g_configs; + +// ============================================================================= +// Get the QSettings object. +// ============================================================================= +static QSettings* getSettingsObject() +{ + QString path = qApp->applicationDirPath() + "/" UNIXNAME EXTENSION; + return new QSettings (path, QSettings::IniFormat); +} + +Config::Config (QString name) : + m_name (name) {} + +// ============================================================================= +// Load the configuration from file +// ============================================================================= +bool Config::load() +{ + QSettings* settings = getSettingsObject(); + print ("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->getDefaultAsVariant()); + cfg->loadFromVariant (val); + g_configsByName[cfg->name()] = cfg; + g_configs << cfg; + } + + settings->deleteLater(); + return true; +} + +// ============================================================================= +// +// Save the configuration to disk +// +bool Config::save() +{ + QSettings* settings = getSettingsObject(); + + for (Config* cfg : g_configs) + { + if (!cfg->isDefault()) + settings->setValue (cfg->name(), cfg->toVariant()); + else + settings->remove (cfg->name()); + } + + settings->sync(); + print ("Configuration saved to %1.\n", settings->fileName()); + settings->deleteLater(); + return true; +} + +// ============================================================================= +// Reset configuration to defaults. +// ============================================================================= +void Config::reset() +{ + for (Config* cfg : g_configs) + cfg->resetValue(); +} + +// ============================================================================= +// Where is the configuration file located at? +// ============================================================================= +QString Config::filepath (QString file) +{ + return Config::dirpath() + DIRSLASH + file; +} + +// ============================================================================= +// Directory of the configuration file. +// ============================================================================= +QString 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; +} + +// ============================================================================= +// ============================================================================= +template<class T> T* getConfigByName (QString name, Config::Type type) +{ + auto it = g_configsByName.find (name); + + if (it == g_configsByName.end()) + return null; + + Config* cfg = it.value(); + + if (cfg->getType() != type) + { + fprint (stderr, "type of %1 is %2, not %3\n", name, cfg->getType(), type); + abort(); + } + + return reinterpret_cast<T*> (cfg); +} + +// ============================================================================= +// ============================================================================= +#undef IMPLEMENT_CONFIG + +#define IMPLEMENT_CONFIG(NAME) \ + NAME##Config* NAME##Config::getByName (QString name) \ + { \ + return getConfigByName<NAME##Config> (name, E##NAME##Type); \ + } + +IMPLEMENT_CONFIG (Int) +IMPLEMENT_CONFIG (String) +IMPLEMENT_CONFIG (Bool) +IMPLEMENT_CONFIG (Float) +IMPLEMENT_CONFIG (List) +IMPLEMENT_CONFIG (KeySequence) +IMPLEMENT_CONFIG (Vertex)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/configuration.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,194 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QString> +#include <QVariant> +#include <QKeySequence> +#include "macros.h" +#include "basics.h" + +class QSettings; + +#define MAX_INI_LINE 512 +#define MAX_CONFIG 512 + +#define cfg(T, NAME, DEFAULT) \ + Config::T##Type NAME; \ + T##Config config_##NAME (&NAME, #NAME, DEFAULT); + +#define extern_cfg(T, NAME) extern Config::T##Type NAME; + +// ========================================================= +class Config +{ + PROPERTY (private, QString, name, setName, STOCK_WRITE) + + public: + enum Type + { + EIntType, + EStringType, + EFloatType, + EBoolType, + EKeySequenceType, + EListType, + EVertexType, + }; + + using IntType = int; + using StringType = QString; + using FloatType = float; + using BoolType = bool; + using KeySequenceType = QKeySequence; + using ListType = QList<QVariant>; + using VertexType = Vertex; + + Config (QString name); + + virtual QVariant getDefaultAsVariant() const = 0; + virtual Type getType() const = 0; + virtual bool isDefault() const = 0; + virtual void loadFromVariant (const QVariant& val) = 0; + virtual void resetValue() = 0; + virtual QVariant toVariant() const = 0; + + // ------------------------------------------ + static bool load(); + static bool save(); + static void reset(); + static QString dirpath(); + static QString filepath (QString file); + + protected: + static void addToArray (Config* ptr); +}; + +// ============================================================================= +#define IMPLEMENT_CONFIG(NAME) \ +public: \ + using ValueType = Config::NAME##Type; \ + \ + NAME##Config (ValueType* valueptr, QString name, ValueType def) : \ + Config (name), \ + m_valueptr (valueptr), \ + m_default (def) \ + { \ + Config::addToArray (this); \ + *m_valueptr = def; \ + } \ + \ + inline ValueType getValue() const \ + { \ + return *m_valueptr; \ + } \ + \ + inline void setValue (ValueType val) \ + { \ + *m_valueptr = val; \ + } \ + \ + virtual Config::Type getType() const \ + { \ + return Config::E##NAME##Type; \ + } \ + \ + virtual void resetValue() \ + { \ + *m_valueptr = m_default; \ + } \ + \ + virtual const ValueType& getDefault() const \ + { \ + return m_default; \ + } \ + \ + virtual bool isDefault() const \ + { \ + return *m_valueptr == m_default; \ + } \ + \ + virtual void loadFromVariant (const QVariant& val) \ + { \ + *m_valueptr = val.value<ValueType>(); \ + } \ + \ + virtual QVariant toVariant() const \ + { \ + return QVariant::fromValue<ValueType> (*m_valueptr); \ + } \ + \ + virtual QVariant getDefaultAsVariant() const \ + { \ + return QVariant::fromValue<ValueType> (m_default); \ + } \ + \ + static NAME##Config* getByName (QString name); \ + \ +private: \ + ValueType* m_valueptr; \ + ValueType m_default; + +// ============================================================================= +// +class IntConfig : public Config +{ + IMPLEMENT_CONFIG (Int) +}; + +// ============================================================================= +// +class StringConfig : public Config +{ + IMPLEMENT_CONFIG (String) +}; + +// ============================================================================= +// +class FloatConfig : public Config +{ + IMPLEMENT_CONFIG (Float) +}; + +// ============================================================================= +// +class BoolConfig : public Config +{ + IMPLEMENT_CONFIG (Bool) +}; + +// ============================================================================= +// +class KeySequenceConfig : public Config +{ + IMPLEMENT_CONFIG (KeySequence) +}; + +// ============================================================================= +// +class ListConfig : public Config +{ + IMPLEMENT_CONFIG (List) +}; + +// ============================================================================= +// +class VertexConfig : public Config +{ + IMPLEMENT_CONFIG (Vertex) +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/crashCatcher.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,139 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#ifdef __unix__ + +#include <QString> +#include <QProcess> +#include <QTemporaryFile> +#include <QMessageBox> +#include <unistd.h> +#include <signal.h> + +#ifdef Q_OS_LINUX +# include <sys/prctl.h> +#endif + +#include "crashCatcher.h" +#include "basics.h" +#include "dialogs.h" + +// Is the crash catcher active now? +static bool g_crashCatcherActive = false; + +// If an assertion failed, what was it? +static QString 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 (format ("attach %1\n", pid).toLocal8Bit()); + commandsFile.write (QString ("backtrace full\n").toLocal8Bit()); + commandsFile.write (QString ("detach\n").toLocal8Bit()); + commandsFile.write (QString ("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. +#ifdef Q_OS_LINUX + prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0); +#endif + + proc.waitForFinished (1000); + QString output = QString (proc.readAllStandardOutput()); + QString err = QString (proc.readAllStandardError()); + + bombBox (format ("<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); + + print ("%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) +{ + QString errmsg = format ( + "<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/crashCatcher.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,24 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#ifdef __unix__ +void initCrashCatcher(); +#else // ifdef __unix__ +# define initCrashCatcher() +#endif // ifdef __unix__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dialogs.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,378 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "radioGroup.h" +#include "mainWindow.h" +#include "glRenderer.h" +#include "documentation.h" +#include "ldDocument.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" + +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); + } +} + +// ============================================================================= +// ============================================================================= +QString 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 (QString path) +{ + ui->path->setText (path); +} + +QString LDrawPathDialog::filename() const +{ + return ui->path->text(); +} + +// ============================================================================= +// ============================================================================= +void LDrawPathDialog::slot_findPath() +{ + QString 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 (format ("<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, numLines()); + updateValues(); +} + +// ============================================================================= +// ============================================================================= +void OpenProgressDialog::updateValues() +{ + ui->progressText->setText (format ("Parsing... %1 / %2", progress(), numLines())); + ui->progressBar->setValue (progress()); +} + +// ============================================================================= +// ============================================================================= +void OpenProgressDialog::updateProgress (int progress) +{ + setProgress (progress); + updateValues(); +} + +// ============================================================================= +// ============================================================================= +ExtProgPathPrompt::ExtProgPathPrompt (QString progName, QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f), + ui (new Ui_ExtProgPath) +{ + ui->setupUi (this); + QString 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() +{ + QString path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter); + + if (!path.isEmpty()) + ui->m_path->setText (path); +} + +// ============================================================================= +// ============================================================================= +QString 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 (APPNAME " " + QString (fullVersionString())); + + QPushButton* mailButton = new QPushButton; + mailButton->setText (tr ("Contact")); + mailButton->setIcon (getIcon ("mail")); + ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole); + connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail())); + + setWindowTitle (format (tr ("About %1"), APPNAME)); +} + +// ============================================================================= +// ============================================================================= +void AboutDialog::slot_mail() +{ + QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <arezey@gmail.com>?subject=LDForge")); +} + +// ============================================================================= +// ============================================================================= +void bombBox (const QString& 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/dialogs.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,141 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QDialog> +#include "main.h" +#include "basics.h" + +class Ui_ExtProgPath; +class QRadioButton; +class QCheckBox; +class QProgressBar; +class QGroupBox; +class QDialogButtonBox; +class QDoubleSpinBox; +class QPushButton; +class QLineEdit; +class QSpinBox; +class RadioGroup; +class QLabel; +class QAbstractButton; +class Ui_OverlayUI; +class Ui_LDPathUI; +class Ui_OpenProgressUI; + +class OverlayDialog : public QDialog +{ + Q_OBJECT + + public: + explicit OverlayDialog (QWidget* parent = null, Qt::WindowFlags f = 0); + virtual ~OverlayDialog(); + + QString fpath() const; + int ofsx() const; + int ofsy() const; + double lwidth() const; + double lheight() const; + int camera() const; + + private: + Ui_OverlayUI* ui; + QList<Pair<QRadioButton*, int>> m_cameraArgs; + + private slots: + void slot_fpath(); + void slot_help(); + void slot_dimensionsChanged(); + void fillDefaults (int newcam); +}; + +// ============================================================================= +class LDrawPathDialog : public QDialog +{ + Q_OBJECT + + public: + explicit LDrawPathDialog (const bool validDefault, QWidget* parent = null, Qt::WindowFlags f = 0); + virtual ~LDrawPathDialog(); + QString filename() const; + void setPath (QString path); + + private: + Q_DISABLE_COPY (LDrawPathDialog) + const bool m_validDefault; + Ui_LDPathUI* ui; + QPushButton* okButton(); + QPushButton* cancelButton(); + + private slots: + void slot_findPath(); + void slot_tryConfigure(); + void slot_exit(); + void slot_accept(); +}; + +// ============================================================================= +class OpenProgressDialog : public QDialog +{ + Q_OBJECT + PROPERTY (public, int, progress, setProgress, STOCK_WRITE) + PROPERTY (public, int, numLines, setNumLines, CUSTOM_WRITE) + + public: + explicit OpenProgressDialog (QWidget* parent = null, Qt::WindowFlags f = 0); + virtual ~OpenProgressDialog(); + + public slots: + void updateProgress (int progress); + + private: + Ui_OpenProgressUI* ui; + + void updateValues(); +}; + +// ============================================================================= +class ExtProgPathPrompt : public QDialog +{ + Q_OBJECT + + public: + explicit ExtProgPathPrompt (QString progName, QWidget* parent = 0, Qt::WindowFlags f = 0); + virtual ~ExtProgPathPrompt(); + QString getPath() const; + + public slots: + void findPath(); + + private: + Ui_ExtProgPath* ui; +}; + +// ============================================================================= +class AboutDialog : public QDialog +{ + Q_OBJECT + + public: + AboutDialog (QWidget* parent = null, Qt::WindowFlags f = 0); + + private slots: + void slot_mail(); +}; + +void bombBox (const QString& message);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/documentation.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,76 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "basics.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/documentation.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,21 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +extern const char* g_docs_overlays; +void showDocumentation (const char* text);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editHistory.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,202 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "editHistory.h" +#include "ldObject.h" +#include "ldDocument.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "glRenderer.h" + +// ============================================================================= +// +History::History() : + m_position (-1) {} + +// ============================================================================= +// +void History::undo() +{ + if (m_changesets.isEmpty() || position() == -1) + return; + + // Don't take the changes done here as actual edits to the document + setIgnoring (true); + + const Changeset& set = getChangeset (position()); + + // Iterate the list in reverse and undo all actions + for (int i = set.size() - 1; i >= 0; --i) + { + AbstractHistoryEntry* change = set[i]; + change->undo(); + } + + m_position--; + g_win->refresh(); + g_win->updateActions(); + dprint ("Position is now %1", position()); + setIgnoring (false); +} + +// ============================================================================= +// +void History::redo() +{ + if (position() == m_changesets.size()) + return; + + setIgnoring (true); + const Changeset& set = getChangeset (position() + 1); + + // Redo things - in the order as they were done in the first place + for (const AbstractHistoryEntry* change : set) + change->redo(); + + setPosition (position() + 1); + g_win->refresh(); + g_win->updateActions(); + dprint ("Position is now %1", position()); + setIgnoring (false); +} + +// ============================================================================= +// +void History::clear() +{ + for (Changeset set : m_changesets) + for (AbstractHistoryEntry* change : set) + delete change; + + m_changesets.clear(); + dprint ("History: cleared"); +} + +// ============================================================================= +// +void History::addStep() +{ + if (m_currentChangeset.isEmpty()) + return; + + while (position() < getSize() - 1) + { + Changeset last = m_changesets.last(); + + for (AbstractHistoryEntry* entry : last) + delete entry; + + m_changesets.removeLast(); + } + + dprint ("History: step added (%1 changes)", m_currentChangeset.size()); + m_changesets << m_currentChangeset; + m_currentChangeset.clear(); + setPosition (position() + 1); + g_win->updateActions(); +} + +// ============================================================================= +// +void History::add (AbstractHistoryEntry* entry) +{ + if (isIgnoring()) + { + delete entry; + return; + } + + entry->setParent (this); + m_currentChangeset << entry; + dprint ("History: added entry of type %1", entry->getTypeName()); +} + +// ============================================================================= +// +void AddHistory::undo() const +{ + LDObject* obj = parent()->document()->getObject (index()); + obj->destroy(); +} + +// ============================================================================= +// +void AddHistory::redo() const +{ + LDObject* obj = parseLine (code()); + parent()->document()->insertObj (index(), obj); + g_win->R()->compileObject (obj); +} + +// ============================================================================= +// +DelHistory::DelHistory (int idx, LDObject* obj) : + m_index (idx), + m_code (obj->asText()) {} + +// ============================================================================= +// heh +// +void DelHistory::undo() const +{ + LDObject* obj = parseLine (code()); + parent()->document()->insertObj (index(), obj); + g_win->R()->compileObject (obj); +} + +// ============================================================================= +// +void DelHistory::redo() const +{ + LDObject* obj = parent()->document()->getObject (index()); + obj->destroy(); +} + +// ============================================================================= +// +void EditHistory::undo() const +{ + LDObject* obj = getCurrentDocument()->getObject (index()); + LDObject* newobj = parseLine (oldCode()); + obj->replace (newobj); + g_win->R()->compileObject (newobj); +} + +// ============================================================================= +// +void EditHistory::redo() const +{ + LDObject* obj = getCurrentDocument()->getObject (index()); + LDObject* newobj = parseLine (newCode()); + obj->replace (newobj); + g_win->R()->compileObject (newobj); +} + +// ============================================================================= +// +void SwapHistory::undo() const +{ + LDObject::fromID (a)->swap (LDObject::fromID (b)); +} + +// ============================================================================= +// +void SwapHistory::redo() const +{ + undo(); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editHistory.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,175 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include "main.h" +#include "ldObject.h" + +#define IMPLEMENT_HISTORY_TYPE(N) \ + virtual ~N##History() {} \ + virtual void undo() const override; \ + virtual void redo() const override; \ + \ + virtual History::EHistoryType getType() const override \ + { \ + return History::E##N##History; \ + } \ + \ + virtual QString getTypeName() const \ + { \ + return #N; \ + } + +class AbstractHistoryEntry; + +// ============================================================================= +class History +{ + PROPERTY (private, int, position, setPosition, STOCK_WRITE) + PROPERTY (public, LDDocument*, document, setDocument, STOCK_WRITE) + PROPERTY (public, bool, isIgnoring, setIgnoring, STOCK_WRITE) + + public: + typedef QList<AbstractHistoryEntry*> Changeset; + + enum EHistoryType + { + EDelHistory, + EEditHistory, + EAddHistory, + EMoveHistory, + ESwapHistory, + }; + + History(); + void undo(); + void redo(); + void clear(); + + void addStep(); + void add (AbstractHistoryEntry* entry); + + inline long getSize() const + { + return m_changesets.size(); + } + + inline History& operator<< (AbstractHistoryEntry* entry) + { + add (entry); + return *this; + } + + inline const Changeset& getChangeset (long pos) const + { + return m_changesets[pos]; + } + + private: + Changeset m_currentChangeset; + QList<Changeset> m_changesets; +}; + +// ============================================================================= +// +class AbstractHistoryEntry +{ + PROPERTY (public, History*, parent, setParent, STOCK_WRITE) + + public: + virtual ~AbstractHistoryEntry() {} + virtual void undo() const = 0; + virtual void redo() const = 0; + virtual History::EHistoryType getType() const = 0; + virtual QString getTypeName() const = 0; +}; + +// ============================================================================= +// +class DelHistory : public AbstractHistoryEntry +{ + PROPERTY (private, int, index, setIndex, STOCK_WRITE) + PROPERTY (private, QString, code, setCode, STOCK_WRITE) + + public: + IMPLEMENT_HISTORY_TYPE (Del) + DelHistory (int idx, LDObject* obj); +}; + +// ============================================================================= +// +class EditHistory : public AbstractHistoryEntry +{ + PROPERTY (private, int, index, setIndex, STOCK_WRITE) + PROPERTY (private, QString, oldCode, setOldCode, STOCK_WRITE) + PROPERTY (private, QString, newCode, setNewCode, STOCK_WRITE) + + public: + IMPLEMENT_HISTORY_TYPE (Edit) + + EditHistory (int idx, QString oldCode, QString newCode) : + m_index (idx), + m_oldCode (oldCode), + m_newCode (newCode) {} +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class AddHistory : public AbstractHistoryEntry +{ + PROPERTY (private, int, index, setIndex, STOCK_WRITE) + PROPERTY (private, QString, code, setCode, STOCK_WRITE) + + public: + IMPLEMENT_HISTORY_TYPE (Add) + + AddHistory (int idx, LDObject* obj) : + m_index (idx), + m_code (obj->asText()) {} +}; + +// ============================================================================= +// +class MoveHistory : public AbstractHistoryEntry +{ + public: + IMPLEMENT_HISTORY_TYPE (Move) + + QList<int> indices; + Vertex dest; + + MoveHistory (QList<int> indices, Vertex dest) : + indices (indices), + dest (dest) {} +}; + +// ============================================================================= +// +class SwapHistory : public AbstractHistoryEntry +{ + public: + IMPLEMENT_HISTORY_TYPE (Swap) + + SwapHistory (int a, int b) : + a (a), + b (b) {} + + private: + int a, b; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/extPrograms.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,696 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "configuration.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "ldDocument.h" +#include "radioGroup.h" +#include "editHistory.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, ""); + +QString* 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); + +bool* 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 mkTempFile (QTemporaryFile& tmp, QString& fname) +{ + if (!tmp.open()) + return false; + + fname = tmp.fileName(); + tmp.close(); + return true; +} + +// ============================================================================= +// +static bool checkProgPath (const extprog prog) +{ + QString& path = *g_extProgPaths[prog]; + + 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 QString processErrorString (extprog prog, QProcess& proc) +{ + switch (proc.error()) + { + case QProcess::FailedToStart: + { + QString wineblurb; + +#ifndef _WIN32 + if (*g_extProgWine[prog]) + wineblurb = "make sure Wine is installed and "; +#endif + + return format ("Program failed to start, %1check your permissions", wineblurb); + } break; + + 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 format ("Timed out (30 seconds)"); + } + + return ""; +} + +// ============================================================================= +// +static void writeObjects (const LDObjectList& objects, QFile& f) +{ + for (LDObject* obj : objects) + { + if (obj->type() == LDObject::ESubfile) + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + LDObjectList objs = ref->inlineContents (LDSubfile::DeepInline); + + writeObjects (objs, f); + + for (LDObject* obj : objs) + obj->destroy(); + } + else + f.write ((obj->asText() + "\r\n").toUtf8()); + } +} + +// ============================================================================= +// +static void writeObjects (const LDObjectList& objects, QString fname) +{ + // Write the input file + QFile f (fname); + + if (!f.open (QIODevice::WriteOnly | QIODevice::Text)) + { + critical (format ("Couldn't open temporary file %1 for writing: %2\n", fname, f.errorString())); + return; + } + + writeObjects (objects, f); + f.close(); + +#ifdef DEBUG + QFile::copy (fname, "debug_lastInput"); +#endif +} + +// ============================================================================= +// +void writeSelection (QString fname) +{ + writeObjects (selection(), fname); +} + +// ============================================================================= +// +void writeColorGroup (const int colnum, QString fname) +{ + LDObjectList objects; + + for (LDObject* obj : getCurrentDocument()->objects()) + { + if (obj->isColored() == false || obj->color() != colnum) + continue; + + objects << obj; + } + + writeObjects (objects, fname); +} + +// ============================================================================= +// +bool runUtilityProcess (extprog prog, QString path, QString argvstr) +{ + QTemporaryFile input; + QStringList argv = argvstr.split (" ", QString::SkipEmptyParts); + +#ifndef _WIN32 + if (*g_extProgWine[prog]) + { + argv.insert (0, path); + path = "wine"; + } +#endif // _WIN32 + + print ("Running command: %1 %2\n", path, argv.join (" ")); + + if (!input.open()) + return false; + + QProcess proc; + + // Begin! + proc.setStandardInputFile (input.fileName()); + proc.start (path, argv); + + if (!proc.waitForStarted()) + { + critical (format ("Couldn't start %1: %2\n", g_extProgNames[prog], processErrorString (prog, proc))); + return false; + } + + // Write an enter, the utility tools all expect one + input.write ("\n"); + + // Wait while it runs + proc.waitForFinished(); + + QString err = ""; + + if (proc.exitStatus() != QProcess::NormalExit) + err = processErrorString (prog, proc); + + // Check the return code + if (proc.exitCode() != 0) + err = format ("Program exited abnormally (return code %1).", proc.exitCode()); + + if (!err.isEmpty()) + { + critical (format ("%1 failed: %2\n", g_extProgNames[prog], err)); + return false; + } + + return true; +} + +// ============================================================================= +// +static void insertOutput (QString fname, bool replace, QList<int> colorsToReplace) +{ +#ifdef DEBUG + QFile::copy (fname, "./debug_lastOutput"); +#endif // RELEASE + + // Read the output file + QFile f (fname); + + if (!f.open (QIODevice::ReadOnly)) + { + critical (format ("Couldn't open temporary file %1 for reading.\n", fname)); + return; + } + + LDObjectList objs = loadFileContents (&f, null); + + // If we replace the objects, delete the selection now. + if (replace) + g_win->deleteSelection(); + + for (int colnum : colorsToReplace) + g_win->deleteByColor (colnum); + + // Insert the new objects + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : objs) + { + if (!obj->isScemantic()) + { + obj->destroy(); + 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; + QString inDATName, outDATName; + + // Make temp files for the input and output files + if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) + return; + + // Compose the command-line arguments + QString 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; + QString inDATName, outDATName; + + // Make temp files for the input and output files + if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) + return; + + // Compose arguments + QString 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; + QString inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName; + + if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) || + !mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) || + !mkTempFile (edgesdat, edgesDATName)) + { + return; + } + + QString parms = join ( + { + (ui.cb_colorize->isChecked()) ? "-c" : "", + (ui.cb_nocondense->isChecked()) ? "-t" : "", + "-s", + ui.dsb_prescale->value() + }); + + QString argv_normal = join ( + { + parms, + inDATName, + cutDATName, + outDATName + }); + + QString 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; + QString in1DATName, in2DATName, outDATName; + + if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName)) + return; + + QString argv = join ( + { + (ui.cb_oldsweep->isChecked() ? "-s" : ""), + (ui.cb_reverse->isChecked() ? "-r" : ""), + (ui.dsb_segsplit->value() != 0 ? format ("-l %1", ui.dsb_segsplit->value()) : ""), + (ui.sb_bias->value() != 0 ? format ("-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; + QString in1DATName, in2DATName, outDATName; + + if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName)) + return; + + QString 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; + QString inName, outName; + + if (!mkTempFile (in, inName) || !mkTempFile (out, outName)) + return; + + int unmatched = ui.unmatched->currentIndex(); + + QString argv = join ( + { + format ("-p %1", ui.precision->value()), + format ("-af %1", ui.flatAngle->value()), + format ("-ac %1", ui.condAngle->value()), + format ("-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/format.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,172 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QString> +#include "basics.h" + +//! \file Format.h +//! Contains string formatting-related functions and classes. + +//! +//! Converts a given value into a string that can be retrieved with text(). +//! Used as the argument type to the formatting functions, hence its name. +//! +class StringFormatArg +{ + public: + StringFormatArg (const QString& a) : m_text (a) {} + StringFormatArg (const char& a) : m_text (a) {} + StringFormatArg (const uchar& a) : m_text (a) {} + StringFormatArg (const QChar& a) : m_text (a) {} + StringFormatArg (int a) : m_text (QString::number (a)) {} + StringFormatArg (long a) : m_text (QString::number (a)) {} + StringFormatArg (const float& a) : m_text (QString::number (a)) {} + StringFormatArg (const double& a) : m_text (QString::number (a)) {} + StringFormatArg (const Vertex& a) : m_text (a.toString (false)) {} + StringFormatArg (const Matrix& a) : m_text (a.toString()) {} + StringFormatArg (const char* a) : m_text (a) {} + + StringFormatArg (const void* a) + { + m_text.sprintf ("%p", a); + } + + template<typename T> + StringFormatArg (const QList<T>& a) + { + m_text = "{"; + + for (const T& it : a) + { + if (&it != &a.first()) + m_text += ", "; + + StringFormatArg arg (it); + m_text += arg.text(); + } + + m_text += "}"; + } + + inline QString text() const + { + return m_text; + } + + private: + QString m_text; +}; + +//! +//! Helper function for \c format +//! +template<typename Arg1, typename... Rest> +void formatHelper (QString& str, Arg1 arg1, Rest... rest) +{ + str = str.arg (StringFormatArg (arg1).text()); + formatHelper (str, rest...); +} + +//! +//! Overload of \c formatHelper() with no template args +//! +static void formatHelper (QString& str) __attribute__ ((unused)); +static void formatHelper (QString& str) +{ + (void) str; +} + +//! +//! @brief Format the message with the given args. +//! +//! The formatting ultimately uses QString's arg() method to actually format +//! the args so the format string should be prepared accordingly, with %1 +//! referring to the first arg, %2 to the second, etc. +//! +//! \param fmtstr The string to format +//! \param args The args to format with +//! \return The formatted string +//! +template<typename... Args> +QString format (QString fmtstr, Args... args) +{ + formatHelper (fmtstr, args...); + return fmtstr; +} + +//! +//! From MessageLog.cc - declared here so that I don't need to include +//! messageLog.h here. Prints the given message to log. +//! +void printToLog (const QString& msg); + +//! +//! Format and print the given args to the message log. +//! \param fmtstr The string to format +//! \param args The args to format with +//! +template<typename... Args> +void print (QString fmtstr, Args... args) +{ + formatHelper (fmtstr, args...); + printToLog (fmtstr); +} + +//! +//! Format and print the given args to the given file descriptor +//! \param fp The file descriptor to print to +//! \param fmtstr The string to format +//! \param args The args to format with +//! +template<typename... Args> +void fprint (FILE* fp, QString fmtstr, Args... args) +{ + formatHelper (fmtstr, args...); + fprintf (fp, "%s", qPrintable (fmtstr)); +} + +//! +//! Overload of \c fprint with a QIODevice +//! \param dev The IO device to print to +//! \param fmtstr The string to format +//! \param args The args to format with +//! +template<typename... Args> +void fprint (QIODevice& dev, QString fmtstr, Args... args) +{ + formatHelper (fmtstr, args...); + dev.write (fmtstr.toUtf8()); +} + +//! +//! Exactly like print() except no-op in release builds. +//! \param fmtstr The string to format +//! \param args The args to format with +//! +template<typename... Args> +void dprint (QString fmtstr, Args... args) +{ +#ifndef RELEASE + formatHelper (fmtstr, args...); + printToLog (fmtstr); +#else + (void) fmtstr; + (void) args; +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/glRenderer.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,2225 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "configuration.h" +#include "ldDocument.h" +#include "glRenderer.h" +#include "colors.h" +#include "mainWindow.h" +#include "miscallenous.h" +#include "editHistory.h" +#include "dialogs.h" +#include "addObjectDialog.h" +#include "messageLog.h" +#include "primitives.h" +#include "misc/ringFinder.h" + +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 }, +}; + +// Matrix templates for circle drawing. 2 is substituted with +// the scale value, 1 is inverted to -1 if needed. +static const Matrix g_circleDrawMatrixTemplates[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, "#FFFFFF") +cfg (String, gl_maincolor, "#A0A0A0") +cfg (Float, gl_maincolor_alpha, 1.0) +cfg (String, gl_selectcolor, "#0080FF") +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) +cfg (Bool, gl_linelengths, true) +cfg (Bool, gl_drawangles, false) + +// 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 +}; + +// Definitions for visual axes, drawn on the screen +const struct LDGLAxis +{ + const QColor col; + const Vertex vert; +} g_GLAxes[3] = +{ + { QColor (255, 0, 0), Vertex (10000, 0, 0) }, // X + { QColor (80, 192, 0), Vertex (0, 10000, 0) }, // Y + { QColor (0, 160, 192), Vertex (0, 0, 10000) }, // Z +}; + +static bool g_glInvert = false; +static QList<int> g_warnedColors; + +// ============================================================================= +// +GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) +{ + m_isPicking = m_rangepick = false; + m_camera = (GL::EFixedCamera) gl_camera; + m_drawToolTip = false; + m_editMode = ESelectMode; + m_rectdraw = false; + m_panning = false; + setDocument (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) + { + QString iconname = format ("camera-%1", tr (g_CameraNames[cam]).toLower()); + + CameraIcon* info = &m_cameraIcons[cam]; + info->img = new QPixmap (getIcon (iconname)); + info->cam = cam; + } + + calcCameraIcons(); +} + +// ============================================================================= +// +GLRenderer::~GLRenderer() +{ + for (int i = 0; i < 6; ++i) + delete currentDocumentData().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()->id(); + + // 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 / 0x10000) % 0x100, + g = (i / 0x100) % 0x100, + b = i % 0x100; + + qglColor (QColor (r, g, b)); + return; + } + + if ((list == BFCFrontList || list == BFCBackList) && + obj->type() != LDObject::ELine && + obj->type() != LDObject::ECondLine) + { + if (list == GL::BFCFrontList) + qcol = QColor (40, 192, 0); + else + qcol = QColor (224, 0, 0); + } + else + { + if (obj->color() == maincolor) + qcol = getMainColor(); + else + { + LDColor* col = ::getColor (obj->color()); + + if (col) + qcol = col->faceColor; + } + + if (obj->color() == edgecolor) + { + LDColor* col; + + if (!gl_blackedges && obj->parent() && (col = ::getColor (obj->parent()->color()))) + qcol = col->edgeColor; + else + qcol = (m_darkbg == false) ? Qt::black : Qt::white; + } + + if (qcol.isValid() == false) + { + // The color was unknown. Use main color to make the object at least + // not appear pitch-black. + if (obj->color() != edgecolor) + qcol = getMainColor(); + + // Warn about the unknown colors, but only once. + for (int i : g_warnedColors) + if (obj->color() == i) + return; + + print ("%1: Unknown color %2!\n", __func__, obj->color()); + g_warnedColors << obj->color(); + return; + } + } + + int r = qcol.red(), + g = qcol.green(), + b = qcol.blue(), + a = qcol.alpha(); + + if (obj->topLevelParent()->isSelected()) + { + // Brighten it up for the select list. + QColor selcolor (gl_selectcolor); + r = (r + selcolor.red()) / 2; + g = (g + selcolor.green()) / 2; + b = (b + selcolor.blue()) / 2; + } + + 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 (document() == 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 : document()->objects()) + { + 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 : document()->objects()) + { + 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; + + roundToDecimals (cx, 4); + roundToDecimals (cy, 4); + + // Create the vertex from the coordinates + pos3d[axisX] = cx; + pos3d[axisY] = cy; + pos3d[3 - axisX - axisY] = getDepthValue(); + return pos3d; +} + +// ============================================================================= +// +// Inverse operation for the above - convert a 3D position to a 2D screen +// position. Don't ask me how this code manages to work, I don't even know. +// +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 (m_darkbg ? Qt::white : Qt::black); + 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 = currentDocumentData().overlays[m_camera]; + + if (overlay.img != null) + { + QPoint v0 = coordconv3_2 (currentDocumentData().overlays[m_camera].v0), + v1 = coordconv3_2 (currentDocumentData().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. + QString text = format (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 (textpen); + 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); + + // Mode-specific rendering + if (editMode() == EDrawMode) + { + QPoint poly[4]; + Vertex poly3d[4]; + int numverts = 4; + + // Calculate polygon data + if (!m_rectdraw) + { + numverts = m_drawedVerts.size() + 1; + int i = 0; + + for (Vertex& vert : m_drawedVerts) + poly3d[i++] = vert; + + // Draw the cursor vertex as the last one in the list. + if (numverts <= 4) + poly3d[i] = m_hoverpos; + else + numverts = 4; + } + else + { + // Get vertex information from m_rectverts + if (m_drawedVerts.size() > 0) + for (int i = 0; i < numverts; ++i) + poly3d[i] = m_rectverts[i]; + else + poly3d[0] = m_hoverpos; + } + + // Convert to 2D + for (int i = 0; i < numverts; ++i) + poly[i] = coordconv3_2 (poly3d[i]); + + if (numverts > 0) + { + // Draw the polygon-to-be + paint.setBrush (polybrush); + paint.drawPolygon (poly, numverts); + + // Draw vertex blips + for (int i = 0; i < numverts; ++i) + { + QPoint& blip = poly[i]; + paint.setPen (linepen); + drawBlip (paint, blip); + + // Draw their coordinates + paint.setPen (textpen); + paint.drawText (blip.x(), blip.y() - 8, poly3d[i].toString (true)); + } + + // Draw line lenghts and angle info if appropriate + if (numverts >= 2) + { + int numlines = (m_drawedVerts.size() == 1) ? 1 : m_drawedVerts.size() + 1; + paint.setPen (textpen); + + for (int i = 0; i < numlines; ++i) + { + const int j = (i + 1 < numverts) ? i + 1 : 0; + const int h = (i - 1 >= 0) ? i - 1 : numverts - 1; + + if (gl_linelengths) + { + const QString label = QString::number (poly3d[i].distanceTo (poly3d[j])); + QPoint origin = QLineF (poly[i], poly[j]).pointAt (0.5).toPoint(); + paint.drawText (origin, label); + } + + if (gl_drawangles) + { + QLineF l0 (poly[h], poly[i]), + l1 (poly[i], poly[j]); + + double angle = 180 - l0.angleTo (l1); + + if (angle < 0) + angle = 180 - l1.angleTo (l0); + + QString label = QString::number (angle) + QString::fromUtf8 (QByteArray ("\302\260")); + QPoint pos = poly[i]; + pos.setY (pos.y() + metrics.height()); + + paint.drawText (pos, label); + } + } + } + } + } + elif (editMode() == 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 = g_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]); + QString label = QString::number (dist0); + paint.setPen (textpen); + paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label); + + if (m_drawedVerts.size() >= 2) + { + label = QString::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] && editMode() != ESelectMode) + continue; + + paint.drawPixmap (info.destRect, *info.img, info.srcRect); + } + + QString formatstr = tr ("%1 Camera"); + + // Draw a label for the current camera in the bottom left corner + { + const int margin = 4; + + QString label; + label = format (formatstr, 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 + { + QString label = format (formatstr, tr (g_CameraNames[m_toolTipCamera])); + QToolTip::showText (m_globalpos, label); + } + } + } + + // Message log + if (messageLog()) + { + int y = 0; + const int margin = 2; + QColor penColor = textpen.color(); + + for (const MessageManager::Line& line : messageLog()->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); +} + +// ============================================================================= +// +void GLRenderer::compileAllObjects() +{ + if (!document()) + return; + + // Compiling all is a big job, use a busy cursor + setCursor (Qt::BusyCursor); + + m_knownVerts.clear(); + + for (LDObject* obj : document()->objects()) + 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->type() != LDObject::ECondLine) ? obj->vertices() : 2; + + if (g_glInvert == false) + for (int i = 0; i < numverts; ++i) + compileVertex (obj->vertex (i)); + else + for (int i = numverts - 1; i >= 0; --i) + compileVertex (obj->vertex (i)); + + glEnd(); +} + +// ============================================================================= +// +void GLRenderer::compileList (LDObject* obj, const GLRenderer::ListType list) +{ + setObjectColor (obj, list); + + switch (obj->type()) + { + case LDObject::ELine: + { + compileSubObject (obj, GL_LINES); + } break; + + case LDObject::ECondLine: + { + // 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::ETriangle: + { + compileSubObject (obj, GL_TRIANGLES); + } break; + + case LDObject::EQuad: + { + compileSubObject (obj, GL_QUADS); + } break; + + case LDObject::ESubfile: + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + LDObjectList objs; + + objs = ref->inlineContents (LDSubfile::DeepCacheInline | LDSubfile::RendererInline); + bool oldinvert = g_glInvert; + + if (ref->transform().getDeterminant() < 0) + g_glInvert = !g_glInvert; + + LDObject* prev = ref->previous(); + + if (prev && prev->type() == LDObject::EBFC && static_cast<LDBFC*> (prev)->statement() == LDBFC::InvertNext) + g_glInvert = !g_glInvert; + + for (LDObject* obj : objs) + { + compileList (obj, list); + obj->destroy(); + } + + 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 (editMode() == EDrawMode) + { + 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 (editMode()) + { + 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 && editMode() != 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) +{ + 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) + { + LDObjectList 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(); + + 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]; + + // Read pixels from the color buffer. + glReadPixels (x0, m_height - 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); + assert (obj != null); + + // 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. + LDObjectList priorsel = selection(); + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : priorsel) + compileObject (obj); + + g_win->updateSelection(); + m_drawedVerts.clear(); + } break; + } + + g_win->updateEditModeActions(); + update(); +} + +// ============================================================================= +// +void GLRenderer::setDocument (LDDocument* const& a) +{ + m_document = a; + + if (a != null) + { + initOverlaysFromObjects(); + + if (currentDocumentData().init == false) + { + resetAllAngles(); + currentDocumentData().init = true; + } + } +} + +// ============================================================================= +// +Matrix GLRenderer::getCircleDrawMatrix (double scale) +{ + Matrix transform = g_circleDrawMatrixTemplates[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; + LDObjectList objs; + + switch (editMode()) + { + 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 = g_lores, divs = g_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.findRings (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, g_lores, g_lores, cmp.num))) == null) + { + refFile = generatePrimitive (::Ring, g_lores, g_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) + { + for (LDObject* obj : objs) + { + document()->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->vertex (i); + } elif (obj->type() == LDObject::ESubfile) + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline); + + for (LDObject* obj : objs) + { + verts << getVertices (obj); + obj->destroy(); + } + } + + 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->setGLInit (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->isGLInit()) + return; + + for (const GL::ListType listType : g_glListTypes) + glDeleteLists (obj->glLists[listType], 1); + + obj->setGLInit (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, QString file, int x, int y, int w, int h) +{ + QImage* img = new QImage (QImage (file).convertToFormat (QImage::Format_ARGB32)); + 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 = currentDocumentData().overlays[camera()]; + delete info.img; + info.img = null; + + updateOverlayObjects(); +} + +// ============================================================================= +// +void GLRenderer::setDepthValue (double depth) +{ + assert (camera() < EFreeCamera); + currentDocumentData().depthValues[camera()] = depth; +} + +// ============================================================================= +// +double GLRenderer::getDepthValue() const +{ + assert (camera() < EFreeCamera); + return currentDocumentData().depthValues[camera()]; +} + +// ============================================================================= +// +const char* GLRenderer::getCameraName() const +{ + return g_CameraNames[camera()]; +} + +// ============================================================================= +// +LDGLOverlay& GLRenderer::getOverlay (int newcam) +{ + return currentDocumentData().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 (document() == 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; + int runaway = 50; + + 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); + + while (--runaway) + { + 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; ++i) + { + if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white) + { + filled = true; + goto endOfLoop; + } + } + + // Left and right edges + for (int i = 0; i < h; ++i) + { + if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white) + { + filled = true; + goto endOfLoop; + } + } + +endOfLoop: + + 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) || editMode() != ESelectMode) + return; + + pick (ev->x(), ev->y()); + + if (selection().isEmpty()) + return; + + LDObject* obj = selection().first(); + AddObjectDialog::staticDialog (obj->type(), obj); + g_win->endAction(); + ev->accept(); +} + +// ============================================================================= +// +LDOverlay* GLRenderer::findOverlayObject (EFixedCamera cam) +{ + LDOverlay* ovlobj = null; + + for (LDObject* obj : document()->objects()) + { + if (obj->type() == LDObject::EOverlay && static_cast<LDOverlay*> (obj)->camera() == 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 = currentDocumentData().overlays[cam]; + LDOverlay* ovlobj = findOverlayObject (cam); + + if (!ovlobj && meta.img) + { + delete meta.img; + meta.img = null; + } + elif (ovlobj && (!meta.img || meta.fname != ovlobj->fileName())) + setupOverlay (cam, ovlobj->fileName(), ovlobj->x(), + ovlobj->y(), ovlobj->width(), ovlobj->height()); + } +} + +// ============================================================================= +// +void GLRenderer::updateOverlayObjects() +{ + for (EFixedCamera cam : g_Cameras) + { + if (cam == EFreeCamera) + continue; + + LDGLOverlay& meta = currentDocumentData().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->type() == LDObject::EEmpty) + nextobj->destroy(); + + // If the overlay object was there and the overlay itself is + // not, remove the object. + ovlobj->destroy(); + } 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 < document()->getObjectCount(); ++i) + { + LDObject* obj = document()->getObject (i); + + if (obj->isScemantic()) + { + found = true; + break; + } + + if (obj->type() == LDObject::EOverlay) + lastOverlay = i; + } + + if (lastOverlay != -1) + document()->insertObj (lastOverlay + 1, ovlobj); + else + { + document()->insertObj (i, ovlobj); + + if (found) + document()->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/glRenderer.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,311 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QGLWidget> +#include "main.h" +#include "macros.h" +#include "ldObject.h" +#include "ldDocument.h" + +class MessageManager; +class QDialogButtonBox; +class RadioGroup; +class QDoubleSpinBox; +class QSpinBox; +class QLineEdit; +class QTimer; + +enum EditMode +{ + ESelectMode, + EDrawMode, + ECircleMode, +}; + +// Meta for overlays +struct LDGLOverlay +{ + Vertex v0, + v1; + int ox, + oy; + double lw, + lh; + QString fname; + QImage* img; +}; + +struct LDFixedCameraInfo +{ + const char glrotate[3]; + const Axis axisX, + axisY; + const bool negX, + negY; +}; + +// ============================================================================= +// Document-specific data +// +struct LDGLData +{ + double rotX, + rotY, + rotZ, + panX[7], + panY[7], + zoom[7]; + double depthValues[6]; + LDGLOverlay overlays[6]; + bool init; + + LDGLData() + { + for (int i = 0; i < 6; ++i) + { + overlays[i].img = null; + depthValues[i] = 0.0f; + } + + init = false; + } +}; + +// ============================================================================= +// The main renderer object, draws the brick on the screen, manages the camera +// and selection picking. The instance of GLRenderer is accessible as +// g_win->R() +// +class GLRenderer : public QGLWidget +{ + public: + enum EFixedCamera + { + ETopCamera, + EFrontCamera, + ELeftCamera, + EBottomCamera, + EBackCamera, + ERightCamera, + EFreeCamera + }; + + enum ListType + { + NormalList, + PickList, + BFCFrontList, + BFCBackList + }; + + // CameraIcon::img is a heap-allocated QPixmap because otherwise it gets + // initialized before program gets to main() and constructs a QApplication + // and Qt doesn't like that. + struct CameraIcon + { + QPixmap* img; + QRect srcRect, + destRect, + selRect; + EFixedCamera cam; + }; + + Q_OBJECT + PROPERTY (public, bool, isDrawOnly, setDrawOnly, STOCK_WRITE) + PROPERTY (public, MessageManager*, messageLog, setMessageLog, STOCK_WRITE) + PROPERTY (private, bool, isPicking, setPicking, STOCK_WRITE) + PROPERTY (public, LDDocument*, document, setDocument, CUSTOM_WRITE) + PROPERTY (public, EditMode, editMode, setEditMode, CUSTOM_WRITE) + + public: + GLRenderer (QWidget* parent = null); + ~GLRenderer(); + + inline EFixedCamera camera() const + { + return m_camera; + } + + void clearOverlay(); + void compileObject (LDObject* obj); + void compileAllObjects(); + void drawGLScene(); + void endDraw (bool accept); + Axis getCameraAxis (bool y, EFixedCamera camid = (EFixedCamera) - 1); + const char* getCameraName() const; + double getDepthValue() const; + QColor getMainColor(); + LDGLOverlay& getOverlay (int newcam); + uchar* getScreencap (int& w, int& h); + void hardRefresh(); + void initGLData(); + void initOverlaysFromObjects(); + void refresh(); + void resetAngles(); + void resetAllAngles(); + void setBackground(); + void setCamera (const EFixedCamera cam); + void setDepthValue (double depth); + bool setupOverlay (EFixedCamera cam, QString file, int x, int y, int w, int h); + void updateOverlayObjects(); + void zoomNotch (bool inward); + void zoomToFit(); + void zoomAllToFit(); + + static void deleteLists (LDObject* obj); + + protected: + void contextMenuEvent (QContextMenuEvent* ev); + void initializeGL(); + void keyPressEvent (QKeyEvent* ev); + void keyReleaseEvent (QKeyEvent* ev); + void leaveEvent (QEvent* ev); + void mouseDoubleClickEvent (QMouseEvent* ev); + void mousePressEvent (QMouseEvent* ev); + void mouseMoveEvent (QMouseEvent* ev); + void mouseReleaseEvent (QMouseEvent* ev); + void paintEvent (QPaintEvent* ev); + void resizeGL (int w, int h); + void wheelEvent (QWheelEvent* ev); + + private: + CameraIcon m_cameraIcons[7]; + QTimer* m_toolTipTimer; + Qt::MouseButtons m_lastButtons; + Qt::KeyboardModifiers m_keymods; + Vertex m_hoverpos; + double m_virtWidth, + m_virtHeight; + bool m_darkbg, + m_rangepick, + m_addpick, + m_drawToolTip, + m_screencap, + m_panning; + QPoint m_pos, + m_globalpos, + m_rangeStart; + QPen m_thickBorderPen, + m_thinBorderPen; + EFixedCamera m_camera, + m_toolTipCamera; + GLuint m_axeslist; + int m_width, + m_height, + m_totalmove; + QList<Vertex> m_drawedVerts; + bool m_rectdraw; + Vertex m_rectverts[4]; + QColor m_bgcolor; + QList<Vertex> m_knownVerts; + + void addDrawnVertex (Vertex m_hoverpos); + LDOverlay* findOverlayObject (EFixedCamera cam); + void updateRectVerts(); + void getRelativeAxes (Axis& relX, Axis& relY) const; + Matrix getCircleDrawMatrix (double scale); + void drawBlip (QPainter& paint, QPoint pos) const; + + // Compute geometry for camera icons + void calcCameraIcons(); + + // How large is the circle we're drawing right now? + double getCircleDrawDist (int pos) const; + + // Clamps an angle to [0, 360] + void clampAngle (double& angle) const; + + // Compile one of the lists of an object + void compileList (LDObject* obj, const ListType list); + + // Sub-routine for object compiling + void compileSubObject (LDObject* obj, const GLenum gltype); + + // Compile a single vertex to a list + void compileVertex (const Vertex& vrt); + + // Convert a 2D point to a 3D point + Vertex coordconv2_3 (const QPoint& pos2d, bool snap) const; + + // Convert a 3D point to a 2D point + QPoint coordconv3_2 (const Vertex& pos3d) const; + + // Perform object selection + void pick (int mouseX, int mouseY); + + // Set the color to an object list + void setObjectColor (LDObject* obj, const ListType list); + + LDGLData& currentDocumentData() const + { + return *document()->getGLData(); + } + + // Get a rotation value + inline double& rot (Axis ax) + { + return + (ax == X) ? currentDocumentData().rotX : + (ax == Y) ? currentDocumentData().rotY : + currentDocumentData().rotZ; + } + + // Get a panning value + inline double& pan (Axis ax) + { + return (ax == X) ? currentDocumentData().panX[camera()] : + currentDocumentData().panY[camera()]; + } + + // Same except const (can be used in const methods) + inline const double& pan (Axis ax) const + { + return (ax == X) ? currentDocumentData().panX[camera()] : + currentDocumentData().panY[camera()]; + } + + // Get the zoom value + inline double& zoom() + { + return currentDocumentData().zoom[camera()]; + } + + template<typename... Args> + inline QString format (QString fmtstr, Args... args) + { + return ::format (fmtstr, args...); + } + + private slots: + void slot_toolTipTimer(); +}; + +// Alias for short namespaces +typedef GLRenderer GL; + +static const GLRenderer::ListType g_glListTypes[] = +{ + GL::NormalList, + GL::PickList, + GL::BFCFrontList, + GL::BFCBackList, +}; + +extern const GL::EFixedCamera g_Cameras[7]; +extern const char* g_CameraNames[7];
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldConfig.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,215 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 <QFile> +#include "ldDocument.h" +#include "ldConfig.h" +#include "mainWindow.h" +#include "miscallenous.h" +#include "colors.h" + +// ============================================================================= +// +// Helper function for parseLDConfig +// +static bool parseLDConfigTag (LDConfigParser& pars, char const* tag, QString& 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() +{ + QFile* fp = openLDrawFile ("LDConfig.ldr", false); + + if (!fp) + { + critical (QObject::tr ("Unable to open LDConfig.ldr for parsing.")); + return; + } + + // Read in the lines + while (fp->atEnd() == false) + { + QString line = QString::fromUtf8 (fp->readLine()); + + if (line.isEmpty() || line[0] != '0') + continue; // empty or illogical + + line.remove ('\r'); + line.remove ('\n'); + + // Parse the line + LDConfigParser pars (line, ' '); + + int code = 0, alpha = 255; + QString 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); + } + + fp->close(); + fp->deleteLater(); +} + +// ============================================================================= +// +LDConfigParser::LDConfigParser (QString 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 (QString& val, const int pos) +{ + if (pos >= m_tokens.size()) + return false; + + val = m_tokens[pos]; + return true; +} + +// ============================================================================= +// +bool LDConfigParser::getNextToken (QString& val) +{ + return getToken (val, ++m_pos); +} + +// ============================================================================= +// +bool LDConfigParser::peekNextToken (QString& 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) +{ + QString tok; + + if (!getToken (tok, inPos)) + return false; + + return (tok == sOther); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldConfig.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,53 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include "basics.h" +#include <QStringList> + +// ============================================================================ +// +// String parsing utility for parsing ldconfig.ldr +// +class LDConfigParser +{ + public: + LDConfigParser (QString inText, char sep); + + bool isAtEnd(); + bool isAtBeginning(); + bool getNextToken (QString& val); + bool peekNextToken (QString& val); + bool getToken (QString& val, const int pos); + bool findToken (int& result, char const* needle, int args); + int getSize(); + void rewind(); + void seek (int amount, bool rel); + bool tokenCompare (int inPos, const char* sOther); + + inline QString operator[] (const int idx) + { + return m_tokens[idx]; + } + + private: + QStringList m_tokens; + int m_pos; +}; + +void parseLDConfig();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldDocument.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,1437 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 <QMessageBox> +#include <QFileDialog> +#include <QDir> +#include <QApplication> +#include "main.h" +#include "configuration.h" +#include "ldDocument.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "editHistory.h" +#include "dialogs.h" +#include "glRenderer.h" +#include "misc/invokeLater.h" + +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 = 10; +static bool g_aborted = false; +static LDDocumentPointer g_logoedStud = null; +static LDDocumentPointer g_logoedStud2 = null; + +LDDocument* LDDocument::m_curdoc = null; + +const QStringList g_specialSubdirectories ({ "s", "48", "8" }); + +// ============================================================================= +// +namespace LDPaths +{ + static QString pathError; + + struct + { + QString LDConfigPath; + QString partsPath, primsPath; + } pathInfo; + + void initPaths() + { + if (!tryConfigure (io_ldpath)) + { + LDrawPathDialog dlg (false); + + if (!dlg.exec()) + exit (0); + + io_ldpath = dlg.filename(); + } + } + + bool tryConfigure (QString 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 = format ("%1" DIRSLASH "parts", path); + pathInfo.LDConfigPath = format ("%1" DIRSLASH "LDConfig.ldr", path); + pathInfo.primsPath = format ("%1" DIRSLASH "p", path); + + return true; + } + + // Accessors + QString getError() + { + return pathError; + } + + QString ldconfig() + { + return pathInfo.LDConfigPath; + } + + QString prims() + { + return pathInfo.primsPath; + } + + QString parts() + { + return pathInfo.partsPath; + } +} + +// ============================================================================= +// +LDDocument::LDDocument() : + m_gldata (new LDGLData) +{ + setImplicit (true); + setSavePosition (-1); + setTabIndex (-1); + setHistory (new History); + history()->setDocument (this); +} + +// ============================================================================= +// +LDDocument::~LDDocument() +{ + // Remove this file from the list of files. This MUST be done FIRST, otherwise + // a ton of other functions will think this file is still valid when it is not! + g_loadedFiles.removeOne (this); + + m_history->setIgnoring (true); + + // Clear everything from the model + for (LDObject* obj : objects()) + obj->destroy(); + + // Clear the cache as well + for (LDObject* obj : cache()) + obj->destroy(); + + delete m_history; + delete m_gldata; + + // 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(); + } + + if (this == g_logoedStud) + g_logoedStud = null; + elif (this == g_logoedStud2) + g_logoedStud2 = null; + + g_win->updateDocumentList(); + print ("Closed %1", name()); +} + +// ============================================================================= +// +LDDocument* findDocument (QString name) +{ + for (LDDocument * file : g_loadedFiles) + if (!file->name().isEmpty() && file->name() == name) + return file; + + return null; +} + +// ============================================================================= +// +QString dirname (QString 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 ""; +} + +// ============================================================================= +// +QString basename (QString path) +{ + long lastpos = path.lastIndexOf (DIRSLASH); + + if (lastpos != -1) + return path.mid (lastpos + 1); + + return path; +} + +// ============================================================================= +// +static QString findLDrawFilePath (QString relpath, bool subdirs) +{ + QString 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 + + // Try find it relative to other currently open documents. We want a file + // in the immediate vicinity of a current model to override stock LDraw stuff. + QString reltop = basename (dirname (relpath)); + + for (LDDocument* doc : g_loadedFiles) + { + if (doc->fullPath().isEmpty()) + continue; + + QString partpath = format ("%1/%2", dirname (doc->fullPath()), relpath); + QFile f (partpath); + + if (f.exists()) + { + // ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48 + QString proptop = basename (dirname (partpath)); + + bool bogus = false; + + for (QString s : g_specialSubdirectories) + { + if ((proptop == s && reltop != s) || (reltop == s && proptop != s)) + { + bogus = true; + break; + } + } + + if (!bogus) + return partpath; + } + } + + if (QFile::exists (relpath)) + return relpath; + + // Try with just the LDraw path first + fullPath = format ("%1" DIRSLASH "%2", io_ldpath, relpath); + + if (QFile::exists (fullPath)) + return fullPath; + + 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 QString& topdir : QList<QString> ({ io_ldpath, net_downloadpath })) + { + for (const QString& subdir : QList<QString> ({ "parts", "p" })) + { + fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); + + if (QFile::exists (fullPath)) + return fullPath; + } + } + } + + // Did not find the file. + return ""; +} + +QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer) +{ + print ("Opening %1...\n", relpath); + QString path = findLDrawFilePath (relpath, subdirs); + + if (pathpointer != null) + *pathpointer = path; + + if (path.isEmpty()) + return null; + + QFile* fp = new QFile (path); + + if (fp->open (QIODevice::ReadOnly)) + return fp; + + fp->deleteLater(); + 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 ldDocument.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 (lines().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) + obj->destroy(); + + m_objects.clear(); + setDone (true); + return; + } + + // Parse up to 300 lines per iteration + int max = i + 300; + + for (; i < max && i < (int) lines().size(); ++i) + { + QString line = lines()[i]; + + // Trim the trailing newline + QChar c; + + while (line.endsWith ("\n") || line.endsWith ("\r")) + line.chop (1); + + LDObject* obj = parseLine (line); + + // Check for parse errors and warn about tthem + if (obj->type() == LDObject::EError) + { + print ("Couldn't parse line #%1: %2", progress() + 1, static_cast<LDError*> (obj)->reason()); + + if (warnings() != null) + (*warnings())++; + } + + 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) lines().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); + } +} + +// ============================================================================= +// +void LDFileLoader::abort() +{ + setAborted (true); + + if (isOnForeground()) + g_aborted = true; +} + +// ============================================================================= +// +LDObjectList loadFileContents (QFile* fp, int* numWarnings, bool* ok) +{ + QStringList lines; + LDObjectList objs; + + if (numWarnings) + *numWarnings = 0; + + // Read in the lines + while (fp->atEnd() == false) + lines << QString::fromUtf8 (fp->readLine()); + + 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->objects(); + return objs; +} + +// ============================================================================= +// +LDDocument* openDocument (QString 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.. + QFile* fp; + QString fullpath; + + if (search) + fp = openLDrawFile (path.toLower(), true, &fullpath); + else + { + fp = new QFile (path); + fullpath = path; + + if (!fp->open (QIODevice::ReadOnly)) + { + delete fp; + return null; + } + } + + if (!fp) + return null; + + LDDocument* load = new LDDocument; + load->setFullPath (fullpath); + load->setName (LDDocument::shortenName (load->fullPath())); + dprint ("name: %1 (%2)", load->name(), load->fullPath()); + g_loadedFiles << load; + + // Don't take the file loading as actual edits to the file + load->history()->setIgnoring (true); + + int numWarnings; + bool ok; + LDObjectList objs = loadFileContents (fp, &numWarnings, &ok); + fp->close(); + fp->deleteLater(); + + if (!ok) + { + g_loadedFiles.removeOne (load); + delete load; + return null; + } + + load->addObjects (objs); + + if (g_loadingMainFile) + { + LDDocument::setCurrent (load); + g_win->R()->setDocument (load); + print (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); + } + + load->history()->setIgnoring (false); + 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()) + { + QString message = format (tr ("There are unsaved changes to %1. Should it be saved?"), + (name().length() > 0) ? name() : tr ("<anonymous>")); + + int button = msgbox::question (g_win, tr ("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 (name().length() == 0) + { + QString newpath = QFileDialog::getSaveFileName (g_win, tr ("Save As"), + getCurrentDocument()->name(), tr ("LDraw files (*.dat *.ldr)")); + + if (newpath.length() == 0) + return false; + + setName (newpath); + } + + if (!save()) + { + message = format (tr ("Failed to save %1 (%2)\nDo you still want to close?"), + name(), strerror (errno)); + + if (msgbox::critical (g_win, tr ("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()->setDocument (f); + g_win->doFullRefresh(); + g_win->updateTitle(); + g_win->updateActions(); +} + +// ============================================================================= +// +void addRecentFile (QString path) +{ + auto& rfiles = io_recentfiles; + 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 (QString path) +{ + g_loadingMainFile = true; + + // If there's already a file with the same name, this file must replace it. + LDDocument* documentToReplace = null; + QString shortName = LDDocument::shortenName (path); + + for (LDDocument* doc : g_loadedFiles) + { + if (doc->name() == shortName) + { + documentToReplace = doc; + break; + } + } + + // We cannot open this file if the document this would replace is not + // safe to close. + if (documentToReplace != null && documentToReplace->isSafeToClose() == false) + { + g_loadingMainFile = false; + return; + } + + 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 (format (QObject::tr ("Failed to open %1: %2"), path, strerror (errno))); + } + + g_loadingMainFile = false; + return; + } + + file->setImplicit (false); + + // Replace references to the old file with the new file. + if (documentToReplace != null) + { + for (LDDocumentPointer* ptr : documentToReplace->references()) + { dprint ("ptr: %1 (%2)\n", + ptr, ptr->pointer() ? ptr->pointer()->name() : "<null>"); + + *ptr = file; + } + + assert (documentToReplace->references().isEmpty()); + delete documentToReplace; + } + + // 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 (QString savepath) +{ + if (!savepath.length()) + savepath = fullPath(); + + QFile f (savepath); + + if (!f.open (QIODevice::WriteOnly)) + 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. + LDObject* nameObject = getObject (1); + + if (!isImplicit() && nameObject != null && nameObject->type() == LDObject::EComment) + { + LDComment* nameComment = static_cast<LDComment*> (nameObject); + + if (nameComment->text().left (6) == "Name: ") + { + QString newname = shortenName (savepath); + nameComment->setText (format ("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 : objects()) + f.write ((obj->asText() + "\r\n").toUtf8()); + + // File is saved, now clean up. + f.close(); + + // We have successfully saved, update the save position now. + setSavePosition (history()->position()); + setFullPath (savepath); + setName (shortenName (savepath)); + + g_win->updateDocumentListItem (this); + g_win->updateTitle(); + return true; +} + +// ============================================================================= +// +class LDParseError : public std::exception +{ + PROPERTY (private, QString, error, setError, STOCK_WRITE) + PROPERTY (private, QString, line, setLine, STOCK_WRITE) + + public: + LDParseError (QString line, QString a) : + m_error (a), + m_line (line) {} + + const char* what() const throw() + { + return qPrintable (error()); + } +}; + +// ============================================================================= +// +void checkTokenCount (QString line, const QStringList& tokens, int num) +{ + if (tokens.size() != num) + throw LDParseError (line, format ("Bad amount of tokens, expected %1, got %2", num, tokens.size())); +} + +// ============================================================================= +// +void checkTokenNumbers (QString line, const QStringList& tokens, int min, int max) +{ + bool ok; + + // Check scientific notation, e.g. 7.99361e-15 + QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+"); + + for (int i = min; i <= max; ++i) + { + tokens[i].toDouble (&ok); + + if (!ok && !scient.exactMatch (tokens[i])) + throw LDParseError (line, format ("Token #%1 was `%2`, expected a number (matched length: %3)", (i + 1), tokens[i], scient.matchedLength())); + } +} + +// ============================================================================= +// +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 (QString line) +{ + try + { + QStringList tokens = line.split (" ", QString::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) + throw LDParseError (line, "Illogical line code"); + + int num = tokens[0][0].digitValue(); + + switch (num) + { + case 0: + { + // Comment + QString 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 == format ("BFC %1", LDBFC::k_statementStrings [i])) + return new LDBFC ( (LDBFC::Statement) 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 + { + QString a; + LDBFC::Statement 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) + checkTokenCount (line, tokens, 7); + checkTokenNumbers (line, tokens, 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") + { + checkTokenCount (line, tokens, 9);; + checkTokenNumbers (line, tokens, 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->setText (comm); + return obj; + } + + case 1: + { + // Subfile + checkTokenCount (line, tokens, 15); + checkTokenNumbers (line, tokens, 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. Note we cannot use LDParseError + // here because the error object needs the document reference. + if (!load) + { + LDError* obj = new LDError (line, format ("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: + { + checkTokenCount (line, tokens, 8); + checkTokenNumbers (line, tokens, 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: + { + checkTokenCount (line, tokens, 11); + checkTokenNumbers (line, tokens, 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: + { + checkTokenCount (line, tokens, 14); + checkTokenNumbers (line, tokens, 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 + throw LDError (line, "Unknown line code number"); + } + } + catch (LDParseError& e) + { + return new LDError (e.line(), e.error()); + } +} + +// ============================================================================= +// +LDDocument* getDocument (QString 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()->objects()) + { + if (obj->type() == LDObject::ESubfile) + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + LDDocument* fileInfo = getDocument (ref->fileInfo()->name()); + + if (fileInfo) + ref->setFileInfo (fileInfo); + else + ref->replace (new LDError (ref->asText(), format ("Could not open %1", ref->fileInfo()->name()))); + } + + // Reparse gibberish files. It could be that they are invalid because + // of loading errors. Circumstances may be different now. + if (obj->type() == LDObject::EError) + obj->replace (parseLine (static_cast<LDError*> (obj)->contents())); + } +} + +// ============================================================================= +// +int LDDocument::addObject (LDObject* obj) +{ + history()->add (new AddHistory (objects().size(), obj)); + m_objects << obj; + + if (obj->type() == LDObject::EVertex) + m_vertices << obj; + +#ifdef DEBUG + if (!isImplicit()) + dprint ("Added object #%1 (%2)\n", obj->id(), obj->typeName()); +#endif + + obj->setDocument (this); + return getObjectCount() - 1; +} + +// ============================================================================= +// +void LDDocument::addObjects (const LDObjectList objs) +{ + for (LDObject* obj : objs) + if (obj) + addObject (obj); +} + +// ============================================================================= +// +void LDDocument::insertObj (int pos, LDObject* obj) +{ + history()->add (new AddHistory (pos, obj)); + m_objects.insert (pos, obj); + obj->setDocument (this); + +#ifdef DEBUG + if (!isImplicit()) + dprint ("Inserted object #%1 (%2) at %3\n", obj->id(), obj->typeName(), pos); +#endif +} + +// ============================================================================= +// +void LDDocument::forgetObject (LDObject* obj) +{ + int idx = obj->lineNumber(); + obj->unselect(); + assert (m_objects[idx] == obj); + + if (!history()->isIgnoring()) + history()->add (new DelHistory (idx, obj)); + + m_objects.removeAt (idx); + obj->setDocument (null); +} + +// ============================================================================= +// +bool safeToCloseAll() +{ + for (LDDocument* f : g_loadedFiles) + if (!f->isSafeToClose()) + return false; + + return true; +} + +// ============================================================================= +// +void LDDocument::setObject (int idx, LDObject* obj) +{ + assert (idx >= 0 && idx < m_objects.size()); + + // Mark this change to history + if (!m_history->isIgnoring()) + { + QString oldcode = getObject (idx)->asText(); + QString newcode = obj->asText(); + *m_history << new EditHistory (idx, oldcode, newcode); + } + + m_objects[idx]->unselect(); + m_objects[idx]->setDocument (null); + obj->setDocument (this); + m_objects[idx] = obj; +} + +// ============================================================================= +// +// Close all documents we don't need anymore +// +void LDDocument::closeUnused() +{ + for (LDDocument* file : g_loadedFiles) + if (file->isImplicit() && file->references().isEmpty()) + delete file; +} + +// ============================================================================= +// +LDObject* LDDocument::getObject (int pos) const +{ + if (m_objects.size() <= pos) + return null; + + return m_objects[pos]; +} + +// ============================================================================= +// +int LDDocument::getObjectCount() const +{ + return objects().size(); +} + +// ============================================================================= +// +bool LDDocument::hasUnsavedChanges() const +{ + return !isImplicit() && history()->position() != savePosition(); +} + +// ============================================================================= +// +QString LDDocument::getDisplayName() +{ + if (!name().isEmpty()) + return name(); + + if (!defaultName().isEmpty()) + return "[" + defaultName() + "]"; + + return tr ("<anonymous>"); +} + +// ============================================================================= +// +LDObjectList 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)) + { + // Ensure logoed studs are loaded first + loadLogoedStuds(); + + if (name() == "stud.dat" && g_logoedStud) + return g_logoedStud->inlineContents (flags); + elif (name() == "stud2.dat" && g_logoedStud2) + return g_logoedStud2->inlineContents (flags); + } + + LDObjectList objs, objcache; + + bool deep = flags & LDSubfile::DeepInline, + doCache = flags & LDSubfile::CacheInline; + + if (m_needsCache) + { + m_cache.clear(); + doCache = true; + } + + // If we have this cached, just create a copy of that + if (deep && cache().isEmpty() == false) + { + for (LDObject* obj : cache()) + objs << obj->createCopy(); + } + else + { + if (!deep) + doCache = false; + + for (LDObject* obj : objects()) + { + // 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->type() == LDObject::ESubfile) + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + + // We only want to cache immediate subfiles, so shed the caching + // flag when recursing deeper in hierarchy. + LDObjectList otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline)); + + for (LDObject* otherobj : otherobjs) + { + // Cache this object, if desired + if (doCache) + objcache << otherobj->createCopy(); + + objs << otherobj; + } + } + else + { + if (doCache) + objcache << obj->createCopy(); + + objs << obj->createCopy(); + } + } + + 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. ;) +// +// TODO: 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()->setDocument (f); + g_win->R()->repaint(); + print ("Changed file to %1", f->getDisplayName()); + } +} + +// ============================================================================= +// +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]->name().isEmpty() && + g_loadedFiles[1]->name().isEmpty() == false && + !g_loadedFiles[0]->hasUnsavedChanges() + ) + delete g_loadedFiles[0]; +} + +// ============================================================================= +// +void loadLogoedStuds() +{ + if (g_logoedStud && g_logoedStud2) + return; + + delete g_logoedStud; + delete g_logoedStud2; + + g_logoedStud = openDocument ("stud-logo.dat", true); + g_logoedStud2 = openDocument ("stud2-logo.dat", true); + + print (LDDocument::tr ("Logoed studs loaded.\n")); +} + +// ============================================================================= +// +void LDDocument::addToSelection (LDObject* obj) // [protected] +{ + if (obj->isSelected()) + return; + + assert (obj->document() == this); + m_sel << obj; + obj->setSelected (true); +} + +// ============================================================================= +// +void LDDocument::removeFromSelection (LDObject* obj) // [protected] +{ + if (!obj->isSelected()) + return; + + assert (obj->document() == this); + m_sel.removeOne (obj); + obj->setSelected (false); +} + +// ============================================================================= +// +void LDDocument::clearSelection() +{ + for (LDObject* obj : m_sel) + removeFromSelection (obj); + + assert (m_sel.isEmpty()); +} + +// ============================================================================= +// +const LDObjectList& LDDocument::getSelection() const +{ + return m_sel; +} + +// ============================================================================= +// +void LDDocument::swapObjects (LDObject* one, LDObject* other) +{ + int a = m_objects.indexOf (one); + int b = m_objects.indexOf (other); + assert (a != b && a != -1 && b != -1); + m_objects[b] = one; + m_objects[a] = other; + addToHistory (new SwapHistory (one->id(), other->id())); +} + +// ============================================================================= +// +QString LDDocument::shortenName (QString a) // [static] +{ + QString shortname = basename (a); + QString topdirname = basename (dirname (a)); + + if (g_specialSubdirectories.contains (topdirname)) + shortname.prepend (topdirname + "\\"); + + return shortname; +} + +// ============================================================================= +// +void LDDocument::addReference (LDDocumentPointer* ptr) +{ + m_references << ptr; +} + +// ============================================================================= +// +void LDDocument::removeReference (LDDocumentPointer* ptr) +{ + m_references.removeOne (ptr); + + if (references().isEmpty()) + invokeLater (closeUnused); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldDocument.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,240 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QObject> +#include "main.h" +#include "ldObject.h" +#include "editHistory.h" + +class History; +class OpenProgressDialog; +class LDDocumentPointer; +struct LDGLData; + +namespace LDPaths +{ + void initPaths(); + bool tryConfigure (QString path); + + QString ldconfig(); + QString prims(); + QString parts(); + QString getError(); +} + +// ============================================================================= +// +// This class stores a document either as a editable file for the user or for +// subfile caching. Its methods handle file input and output. +// +// A file is implicit when they are opened automatically for caching purposes +// and are hidden from the user. User-opened files are explicit (not implicit). +// +// The default name is a placeholder, initially suggested name for a file. The +// primitive generator uses this to give initial names to primitives. +// +class LDDocument : public QObject +{ + public: + using ReferenceList = QList<LDDocumentPointer*>; + + Q_OBJECT + PROPERTY (public, QString, name, setName, STOCK_WRITE) + PROPERTY (private, LDObjectList, objects, setObjects, STOCK_WRITE) + PROPERTY (private, LDObjectList, cache, setCache, STOCK_WRITE) + PROPERTY (private, History*, history, setHistory, STOCK_WRITE) + PROPERTY (private, LDObjectList, vertices, setVertices, STOCK_WRITE) + PROPERTY (private, ReferenceList, references, setReferences, STOCK_WRITE) + PROPERTY (public, QString, fullPath, setFullPath, STOCK_WRITE) + PROPERTY (public, QString, defaultName, setDefaultName, STOCK_WRITE) + PROPERTY (public, bool, isImplicit, setImplicit, STOCK_WRITE) + PROPERTY (public, long, savePosition, setSavePosition, STOCK_WRITE) + PROPERTY (public, int, tabIndex, setTabIndex, STOCK_WRITE) + + public: + LDDocument(); + ~LDDocument(); + + int addObject (LDObject* obj); // Adds an object to this file at the end of the file. + void addObjects (const LDObjectList objs); + void clearSelection(); + void forgetObject (LDObject* obj); // Deletes the given object from the object chain. + QString getDisplayName(); + const LDObjectList& getSelection() const; + bool hasUnsavedChanges() const; // Does this document have unsaved changes? + LDObjectList inlineContents (LDSubfile::InlineFlags flags); + void insertObj (int pos, LDObject* obj); + int getObjectCount() const; + LDObject* getObject (int pos) const; + bool save (QString path = ""); // Saves this file to disk. + void swapObjects (LDObject* one, LDObject* other); + bool isSafeToClose(); // Perform safety checks. Do this before closing any files! + void setObject (int idx, LDObject* obj); + void addReference (LDDocumentPointer* ptr); + void removeReference (LDDocumentPointer* ptr); + + inline LDDocument& operator<< (LDObject* obj) + { + addObject (obj); + return *this; + } + + inline void addHistoryStep() + { + history()->addStep(); + } + + inline void undo() + { + history()->undo(); + } + + inline void redo() + { + history()->redo(); + } + + inline void clearHistory() + { + history()->clear(); + } + + inline void addToHistory (AbstractHistoryEntry* entry) + { + *history() << entry; + } + + static void closeUnused(); + static LDDocument* current(); + static void setCurrent (LDDocument* f); + static void closeInitialFile(); + static int countExplicitFiles(); + + // Turns a full path into a relative path + static QString shortenName (QString a); + + protected: + void addToSelection (LDObject* obj); + void removeFromSelection (LDObject* obj); + + LDGLData* getGLData() + { + return m_gldata; + } + + friend class LDObject; + friend class GLRenderer; + + private: + LDObjectList m_sel; + LDGLData* m_gldata; + + // If set to true, next inline of this document discards the cache and + // re-builds it. + bool m_needsCache; + + static LDDocument* m_curdoc; +}; + +inline LDDocument* getCurrentDocument() +{ + return LDDocument::current(); +} + +// Close all current loaded files and start off blank. +void newFile(); + +// Opens the given file as the main file. Everything is closed first. +void openMainFile (QString path); + +// Finds an OpenFile by name or null if not open +LDDocument* findDocument (QString name); + +// Opens the given file and parses the LDraw code within. Returns a pointer +// to the opened file or null on error. +LDDocument* openDocument (QString path, bool search); + +// Opens the given file and returns a pointer to it, potentially looking in /parts and /p +QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer = null); + +// Close all open files, whether user-opened or subfile caches. +void closeAll(); + +// Parses a string line containing an LDraw object and returns the object parsed. +LDObject* parseLine (QString line); + +// Retrieves the pointer to the given document by file name. Document is loaded +// from file if necessary. Can return null if neither succeeds. +LDDocument* getDocument (QString filename); + +// Re-caches all subfiles. +void reloadAllSubfiles(); + +// Is it safe to close all files? +bool safeToCloseAll(); + +LDObjectList loadFileContents (QFile* f, int* numWarnings, bool* ok = null); + +extern QList<LDDocument*> g_loadedFiles; + +inline const LDObjectList& selection() +{ + return getCurrentDocument()->getSelection(); +} + +void addRecentFile (QString path); +void loadLogoedStuds(); +QString basename (QString path); +QString dirname (QString path); + +extern QList<LDDocument*> g_loadedFiles; // Vector of all currently opened files. + +// ============================================================================= +// +// LDFileLoader +// +// Loads the given file and parses it to LDObjects using parseLine. It's a +// separate class so as to be able to do the work progressively through the +// event loop, allowing the program to maintain responsivity during loading. +// +class LDFileLoader : public QObject +{ + Q_OBJECT + PROPERTY (private, LDObjectList, objects, setObjects, STOCK_WRITE) + PROPERTY (private, bool, isDone, setDone, STOCK_WRITE) + PROPERTY (private, int, progress, setProgress, STOCK_WRITE) + PROPERTY (private, bool, isAborted, setAborted, STOCK_WRITE) + PROPERTY (public, QStringList, lines, setLines, STOCK_WRITE) + PROPERTY (public, int*, warnings, setWarnings, STOCK_WRITE) + PROPERTY (public, bool, isOnForeground, setOnForeground, STOCK_WRITE) + + public slots: + void start(); + void abort(); + + private: + OpenProgressDialog* dlg; + + private slots: + void work (int i); + + signals: + void progressUpdate (int progress); + void workDone(); +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldObject.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,825 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "ldObject.h" +#include "ldDocument.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "editHistory.h" +#include "glRenderer.h" +#include "colors.h" + +cfg (String, ld_defaultname, ""); +cfg (String, ld_defaultuser, ""); +cfg (Int, ld_defaultlicense, 0); + +// List of all LDObjects +static LDObjectList g_LDObjects; + +// ============================================================================= +// LDObject constructors +// +LDObject::LDObject() : + m_isHidden (false), + m_isSelected (false), + m_parent (null), + m_document (null), + m_isGLInit (false), + qObjListEntry (null) +{ + memset (m_coords, 0, sizeof m_coords); + chooseID(); + g_LDObjects << this; +} + +// ============================================================================= +// +void LDObject::chooseID() +{ + int32 id = 1; // 0 shalt be null + + for (LDObject* obj : g_LDObjects) + { + assert (obj != this); + + if (obj->id() >= id) + id = obj->id() + 1; + } + + setID (id); +} + +// ============================================================================= +// +void LDObject::setVertexCoord (int i, Axis ax, double value) +{ + Vertex v = vertex (i); + v[ax] = value; + setVertex (i, v); +} + +LDError::LDError() {} + +// ============================================================================= +// +QString LDComment::asText() const +{ + return format ("0 %1", text()); +} + +// ============================================================================= +// +QString LDSubfile::asText() const +{ + QString val = format ("1 %1 %2 ", color(), position()); + val += transform().toString(); + val += ' '; + val += fileInfo()->name(); + return val; +} + +// ============================================================================= +// +QString LDLine::asText() const +{ + QString val = format ("2 %1", color()); + + for (int i = 0; i < 2; ++i) + val += format (" %1", vertex (i)); + + return val; +} + +// ============================================================================= +// +QString LDTriangle::asText() const +{ + QString val = format ("3 %1", color()); + + for (int i = 0; i < 3; ++i) + val += format (" %1", vertex (i)); + + return val; +} + +// ============================================================================= +// +QString LDQuad::asText() const +{ + QString val = format ("4 %1", color()); + + for (int i = 0; i < 4; ++i) + val += format (" %1", vertex (i)); + + return val; +} + +// ============================================================================= +// +QString LDCondLine::asText() const +{ + QString val = format ("5 %1", color()); + + // Add the coordinates + for (int i = 0; i < 4; ++i) + val += format (" %1", vertex (i)); + + return val; +} + +// ============================================================================= +// +QString LDError::asText() const +{ + return contents(); +} + +// ============================================================================= +// +QString LDVertex::asText() const +{ + return format ("0 !LDFORGE VERTEX %1 %2", color(), pos); +} + +// ============================================================================= +// +QString LDEmpty::asText() const +{ + return ""; +} + +// ============================================================================= +// +const char* LDBFC::k_statementStrings[] = +{ + "CERTIFY CCW", + "CCW", + "CERTIFY CW", + "CW", + "NOCERTIFY", + "INVERTNEXT", + "CLIP", + "CLIP CCW", + "CLIP CW", + "NOCLIP", +}; + +QString LDBFC::asText() const +{ + return format ("0 BFC %1", LDBFC::k_statementStrings[m_statement]); +} + +// ============================================================================= +// +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 (vertex (0), vertex (1), vertex (3)); + LDTriangle* tri2 = new LDTriangle (vertex (1), vertex (2), vertex (3)); + + // The triangles also inherit the quad's color + tri1->setColor (color()); + tri2->setColor (color()); + + QList<LDTriangle*> triangles; + triangles << tri1; + triangles << tri2; + return triangles; +} + +// ============================================================================= +// +void LDObject::replace (LDObject* other) +{ + long idx = lineNumber(); + assert (idx != -1); + + // Replace the instance of the old object with the new object + document()->setObject (idx, other); + + // Remove the old object + destroy(); +} + +// ============================================================================= +// +void LDObject::swap (LDObject* other) +{ + assert (document() == other->document()); + document()->swapObjects (this, other); +} + +// ============================================================================= +// +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() {} + +// ============================================================================= +// +LDSubfile::~LDSubfile() {} + +// ============================================================================= +// +void LDObject::destroy() +{ + // 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 (document()) + document()->forgetObject (this); + + // Delete the GL lists + GL::deleteLists (this); + + // Remove this object from the list of LDObjects + g_LDObjects.removeOne (this); + + delete this; +} + +// ============================================================================= +// +static void transformObject (LDObject* obj, Matrix transform, Vertex pos, int parentcolor) +{ + switch (obj->type()) + { + case LDObject::ELine: + case LDObject::ECondLine: + case LDObject::ETriangle: + case LDObject::EQuad: + + for (int i = 0; i < obj->vertices(); ++i) + { + Vertex v = obj->vertex (i); + v.transform (transform, pos); + obj->setVertex (i, v); + } + + break; + + case LDObject::ESubfile: + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + Matrix newMatrix = transform * ref->transform(); + Vertex newpos = ref->position(); + + newpos.transform (transform, pos); + ref->setPosition (newpos); + ref->setTransform (newMatrix); + } + break; + + default: + break; + } + + if (obj->color() == maincolor) + obj->setColor (parentcolor); +} + +// ============================================================================= +// +LDObjectList LDSubfile::inlineContents (InlineFlags flags) +{ + LDObjectList objs = fileInfo()->inlineContents (flags); + + // Transform the objects + for (LDObject* obj : objs) + { + // Set the parent now so we know what inlined the object. + obj->setParent (this); + transformObject (obj, transform(), position(), color()); + } + + return objs; +} + +// ============================================================================= +// +long LDObject::lineNumber() const +{ + assert (document() != null); + + for (int i = 0; i < document()->getObjectCount(); ++i) + if (document()->getObject (i) == this) + return i; + + return -1; +} + +// ============================================================================= +// +void LDObject::moveObjects (LDObjectList 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; + LDObjectList objsToCompile; + LDDocument* file = objs[0]->document(); + + for (long i = start; i != end; i += incr) + { + LDObject* obj = objs[i]; + + const long idx = obj->lineNumber(), + target = idx + (up ? -1 : 1); + + if ((up && idx == 0) || (!up && idx == (long) (file->objects().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); +} + +// ============================================================================= +// +QString LDObject::typeName (LDObject::Type type) +{ + LDObject* obj = LDObject::getDefault (type); + QString name = obj->typeName(); + obj->destroy(); + return name; +} + +// ============================================================================= +// +QString LDObject::describeObjects (const LDObjectList& objs) +{ + bool firstDetails = true; + QString text = ""; + + if (objs.isEmpty()) + return "nothing"; // :) + + for (long i = 0; i < ENumTypes; ++i) + { + Type objType = (Type) i; + int count = 0; + + for (LDObject * obj : objs) + if (obj->type() == objType) + count++; + + if (count == 0) + continue; + + if (!firstDetails) + text += ", "; + + QString noun = format ("%1%2", typeName (objType), plural (count)); + + // Plural of "vertex" is "vertices", correct that + if (objType == EVertex && count != 1) + noun = "vertices"; + + text += format ("%1 %2", count, noun); + firstDetails = false; + } + + return text; +} + +// ============================================================================= +// +LDObject* LDObject::topLevelParent() +{ + if (parent() == null) + return this; + + LDObject* it = this; + + while (it->parent() != null) + it = it->parent(); + + return it; +} + +// ============================================================================= +// +LDObject* LDObject::next() const +{ + long idx = lineNumber(); + assert (idx != -1); + + if (idx == (long) document()->getObjectCount() - 1) + return null; + + return document()->getObject (idx + 1); +} + +// ============================================================================= +// +LDObject* LDObject::previous() const +{ + long idx = lineNumber(); + assert (idx != -1); + + if (idx == 0) + return null; + + return document()->getObject (idx - 1); +} + +// ============================================================================= +// +void LDObject::move (Vertex vect) +{ + if (hasMatrix()) + { + LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (this); + mo->setPosition (mo->position() + vect); + } + elif (type() == LDObject::EVertex) + { + // ugh + static_cast<LDVertex*> (this)->pos += vect; + } + else + { + for (int i = 0; i < vertices(); ++i) + setVertex (i, vertex (i) + vect); + } +} + +// ============================================================================= +// +#define CHECK_FOR_OBJ(N) \ + if (type == LDObject::E##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 = vertex (1); + setVertex (1, vertex (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 = vertex (1); + setVertex (1, vertex (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 = lineNumber(); + + if (idx > 0) + { + LDBFC* bfc = dynamic_cast<LDBFC*> (previous()); + + if (bfc && bfc->statement() == LDBFC::InvertNext) + { + // This is prefixed with an invertnext, thus remove it. + bfc->destroy(); + return; + } + } + + // Not inverted, thus prefix it with a new invertnext. + LDBFC* bfc = new LDBFC (LDBFC::InvertNext); + document()->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->vertex (0); + line->setVertex (0, line->vertex (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, vertex (i)); + + repl->setColor (color()); + + replace (repl); + return repl; +} + +// ============================================================================= +// +LDObject* LDObject::fromID (int id) +{ + for (LDObject* obj : g_LDObjects) + if (obj->id() == id) + return obj; + + return null; +} + +// ============================================================================= +// +QString LDOverlay::asText() const +{ + return format ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6", + fileName(), camera(), x(), y(), width(), height()); +} + +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->document() && (idx = obj->lineNumber()) != -1) + { + QString before = obj->asText(); + *ptr = val; + QString after = obj->asText(); + + if (before != after) + obj->document()->addToHistory (new EditHistory (idx, before, after)); + } + else + *ptr = val; +} + +// ============================================================================= +// +void LDObject::setColor (const int& val) +{ + changeProperty (this, &m_color, val); +} + +// ============================================================================= +// +const Vertex& LDObject::vertex (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 (linkPointer(), &m_position, LDSharedVertex::getSharedVertex (a)); +} + +// ============================================================================= +// +void LDMatrixObject::setTransform (const Matrix& val) +{ + changeProperty (linkPointer(), &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() +{ + assert (document() != null); + document()->addToSelection (this); +} + +// ============================================================================= +// +void LDObject::unselect() +{ + assert (document() != null); + document()->removeFromSelection (this); +} + +// ============================================================================= +// +QString getLicenseText (int id) +{ + switch (id) + { + case 0: + return g_CALicense; + + case 1: + return g_nonCALicense; + + case 2: + return ""; + } + + assert (false); + return ""; +} + +// ============================================================================= +// +LDObject* LDObject::createCopy() const +{ + /* + LDObject* copy = clone(); + copy->setFile (null); + copy->setGLInit (false); + copy->chooseID(); + copy->setSelected (false); + */ + + /* + LDObject* copy = getDefault (getType()); + copy->setColor (color()); + + if (hasMatrix()) + { + LDMatrixObject* copyMo = static_cast<LDMatrixObject*> (copy); + const LDMatrixObject* mo = static_cast<const LDMatrixObject*> (this); + copyMo->setPosition (mo->getPosition()); + copyMo->setTransform (mo->transform()); + } + else + { + for (int i = 0; i < vertices(); ++i) + copy->setVertex (getVertex (i)); + } + + switch (getType()) + { + case Subfile: + { + LDSubfile* copyRef = static_cast<LDSubfile*> (copy); + const LDSubfile* ref = static_cast<const LDSubfile*> (this); + + copyRef->setFileInfo (ref->fileInfo()); + } + } + */ + + LDObject* copy = parseLine (asText()); + return copy; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldObject.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,553 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include "main.h" +#include "basics.h" +#include "misc/documentPointer.h" + +#define LDOBJ(T) \ +protected: \ + virtual LD##T* clone() override \ + { \ + return new LD##T (*this); \ + } \ + \ +public: \ + virtual LDObject::Type type() const override \ + { \ + return LDObject::E##T; \ + } \ + virtual QString asText() const override; \ + virtual void invert() override; + +#define LDOBJ_NAME(N) virtual QString typeName() const override { return #N; } +#define LDOBJ_VERTICES(V) virtual int vertices() const override { return V; } +#define LDOBJ_SETCOLORED(V) virtual bool isColored() const override { return V; } +#define LDOBJ_COLORED LDOBJ_SETCOLORED (true) +#define LDOBJ_UNCOLORED LDOBJ_SETCOLORED (false) + +#define LDOBJ_CUSTOM_SCEMANTIC virtual bool isScemantic() const override +#define LDOBJ_SCEMANTIC LDOBJ_CUSTOM_SCEMANTIC { return true; } +#define LDOBJ_NON_SCEMANTIC LDOBJ_CUSTOM_SCEMANTIC { return false; } + +#define LDOBJ_SETMATRIX(V) virtual bool hasMatrix() const override { return V; } +#define LDOBJ_HAS_MATRIX LDOBJ_SETMATRIX (true) +#define LDOBJ_NO_MATRIX LDOBJ_SETMATRIX (false) + +class QListWidgetItem; +class LDSubfile; +class LDDocument; +class LDSharedVertex; + +// ============================================================================= +// LDObject +// +// Base class object for all object types. Each LDObject represents a single line +// in the LDraw code file. The virtual method getType returns an enumerator +// which is a token of the object's type. The object can be casted into +// sub-classes based on this enumerator. +// ============================================================================= +class LDObject +{ + PROPERTY (public, bool, isHidden, setHidden, STOCK_WRITE) + PROPERTY (public, bool, isSelected, setSelected, STOCK_WRITE) + PROPERTY (public, LDObject*, parent, setParent, STOCK_WRITE) + PROPERTY (public, LDDocument*, document, setDocument, STOCK_WRITE) + PROPERTY (private, int, id, setID, STOCK_WRITE) + PROPERTY (public, int, color, setColor, CUSTOM_WRITE) + PROPERTY (public, bool, isGLInit, setGLInit, STOCK_WRITE) + + public: + // Object type codes. + enum Type + { + ESubfile, // Object represents a sub-file reference + EQuad, // Object represents a quadrilateral + ETriangle, // Object represents a triangle + ELine, // Object represents a line + ECondLine, // Object represents a conditional line + EVertex, // Object is a vertex, LDForge extension object + EBFC, // Object represents a BFC statement + EOverlay, // Object contains meta-info about an overlay image. + EComment, // Object represents a comment + EError, // Object is the result of failed parsing + EEmpty, // Object represents an empty line + EUnidentified, // Unknown object type (some functions return this; TODO: they probably should not) + ENumTypes // Amount of object types + }; + + LDObject(); + + // Makes a copy of this object + LDObject* createCopy() const; + + // Deletes this object + void destroy(); + + // Index (i.e. line number) of this object + long lineNumber() const; + + // Type enumerator of this object + virtual Type type() const = 0; + + // Get a vertex by index + const Vertex& vertex (int i) const; + + // Type name of this object + virtual QString typeName() const = 0; + + // Does this object have a matrix and position? (see LDMatrixObject) + virtual bool hasMatrix() const = 0; + + // Inverts this object (winding is reversed) + virtual void invert() = 0; + + // Is this object colored? + virtual bool isColored() const = 0; + + // Does this object have meaning in the part model? + virtual bool isScemantic() const = 0; + + // Moves this object using the given vertex as a movement List + void move (Vertex vect); + + // Object after this in the current file + LDObject* next() const; + + // Object prior to this in the current file + LDObject* previous() const; + + // This object as LDraw code + virtual QString asText() const = 0; + + // Replace this LDObject with another LDObject. Object is deleted in the process. + void replace (LDObject* other); + + // Selects this object. + void select(); + + // Set a vertex to the given value + void setVertex (int i, const Vertex& vert); + + // Set a single coordinate of a vertex + void setVertexCoord (int i, Axis ax, double value); + + // Swap this object with another. + void swap (LDObject* other); + + // What object in the current file ultimately references this? + LDObject* topLevelParent(); + + // Removes this object from selection // TODO: rename to deselect? + void unselect(); + + // Number of vertices this object has // TODO: rename to getNumVertices + virtual int vertices() const = 0; + + // Get type name by enumerator + static QString typeName (LDObject::Type type); + + // Returns a default-constructed LDObject by the given type + static LDObject* getDefault (const LDObject::Type type); + + // TODO: move this to LDDocument? + static void moveObjects (LDObjectList objs, const bool up); + + // Get a description of a list of LDObjects + static QString describeObjects (const LDObjectList& objs); + static LDObject* fromID (int id); + + // TODO: make these private! + // OpenGL list for this object + uint glLists[4]; + + // Object list entry for this object + QListWidgetItem* qObjListEntry; + + protected: + // LDObjects are to be deleted with the deleteSelf() method, not with + // operator delete. This is because it seems virtual functions cannot + // be properly called from the destructor, thus a normal method must + // be used instead. The destructor also doesn't seem to be able to + // be private without causing a truckload of problems so it's protected + // instead. + virtual ~LDObject(); + void chooseID(); + + private: + virtual LDObject* clone() = 0; + LDSharedVertex* m_coords[4]; +}; + +// ============================================================================= +// LDSharedVertex +// +// For use as coordinates of LDObjects. Keeps count of references. +// ============================================================================= +class LDSharedVertex +{ + public: + inline const Vertex& data() const + { + return m_data; + } + + inline operator const Vertex&() const + { + return m_data; + } + + void addRef (LDObject* a); + void delRef (LDObject* a); + + static LDSharedVertex* getSharedVertex (const Vertex& a); + + protected: + LDSharedVertex (const Vertex& a) : m_data (a) {} + + private: + LDObjectList m_refs; + Vertex m_data; +}; + +// ============================================================================= +// +// Common code for objects with matrices. This class is multiple-derived in +// and thus not used directly other than as a common storage point for matrices +// and vertices. +// +// The link pointer is a pointer to this object's LDObject self - since this is +// multiple-derived in, static_cast or dynamic_cast won't budge here. +// +// In 0.1-alpha, there was a separate 'radial' type which had a position and +// matrix as well. Even though right now only LDSubfile uses this, I'm keeping +// this class distinct in case I get new extension ideas. :) +// +class LDMatrixObject +{ + PROPERTY (public, LDObject*, linkPointer, setLinkPointer, STOCK_WRITE) + PROPERTY (public, Matrix, transform, setTransform, CUSTOM_WRITE) + + public: + LDMatrixObject() : + m_position (LDSharedVertex::getSharedVertex (g_origin)) {} + + LDMatrixObject (const Matrix& transform, const Vertex& pos) : + m_transform (transform), + m_position (LDSharedVertex::getSharedVertex (pos)) {} + + inline const Vertex& position() const + { + return m_position->data(); + } + + void setCoordinate (const Axis ax, double value) + { + Vertex v = position(); + v[ax] = value; + setPosition (v); + } + + void setPosition (const Vertex& a); + + private: + LDSharedVertex* m_position; +}; + +// ============================================================================= +// +// Represents a line in the LDraw file that could not be properly parsed. It is +// represented by a (!) ERROR in the code view. It exists for the purpose of +// allowing garbage lines be debugged and corrected within LDForge. +// +class LDError : public LDObject +{ + LDOBJ (Error) + LDOBJ_NAME (error) + LDOBJ_VERTICES (0) + LDOBJ_UNCOLORED + LDOBJ_SCEMANTIC + LDOBJ_NO_MATRIX + PROPERTY (public, QString, fileReferenced, setFileReferenced, STOCK_WRITE) + PROPERTY (private, QString, contents, setContents, STOCK_WRITE) + PROPERTY (private, QString, reason, setReason, STOCK_WRITE) + + public: + LDError(); + LDError (QString contents, QString reason) : + m_contents (contents), + m_reason (reason) {} +}; + +// ============================================================================= +// +// Represents an empty line in the LDraw code file. +// +class LDEmpty : public LDObject +{ + LDOBJ (Empty) + LDOBJ_NAME (empty) + LDOBJ_VERTICES (0) + LDOBJ_UNCOLORED + LDOBJ_NON_SCEMANTIC + LDOBJ_NO_MATRIX +}; + +// ============================================================================= +// +// Represents a code-0 comment in the LDraw code file. +// +class LDComment : public LDObject +{ + PROPERTY (public, QString, text, setText, STOCK_WRITE) + LDOBJ (Comment) + LDOBJ_NAME (comment) + LDOBJ_VERTICES (0) + LDOBJ_UNCOLORED + LDOBJ_NON_SCEMANTIC + LDOBJ_NO_MATRIX + + public: + LDComment() {} + LDComment (QString text) : m_text (text) {} +}; + +// ============================================================================= +// +// Represents a 0 BFC statement in the LDraw code. eStatement contains the type +// of this statement. +// +class LDBFC : public LDObject +{ + public: + enum Statement + { + CertifyCCW, + CCW, + CertifyCW, + CW, + NoCertify, + InvertNext, + Clip, + ClipCCW, + ClipCW, + NoClip, + NumStatements + }; + + LDOBJ (BFC) + LDOBJ_NAME (bfc) + LDOBJ_VERTICES (0) + LDOBJ_UNCOLORED + LDOBJ_CUSTOM_SCEMANTIC { return (statement() == InvertNext); } + LDOBJ_NO_MATRIX + PROPERTY (public, Statement, statement, setStatement, STOCK_WRITE) + + public: + LDBFC() {} + LDBFC (const LDBFC::Statement type) : + m_statement (type) {} + + // Statement strings + static const char* k_statementStrings[]; +}; + +// ============================================================================= +// LDSubfile +// +// Represents a single code-1 subfile reference. +// ============================================================================= +class LDSubfile : public LDObject, public LDMatrixObject +{ + LDOBJ (Subfile) + LDOBJ_NAME (subfile) + LDOBJ_VERTICES (0) + LDOBJ_COLORED + LDOBJ_SCEMANTIC + LDOBJ_HAS_MATRIX + PROPERTY (public, LDDocumentPointer, fileInfo, setFileInfo, STOCK_WRITE) + + public: + enum InlineFlag + { + DeepInline = (1 << 0), + CacheInline = (1 << 1), + RendererInline = (1 << 2), + DeepCacheInline = (DeepInline | CacheInline), + }; + + Q_DECLARE_FLAGS (InlineFlags, InlineFlag) + + LDSubfile() + { + setLinkPointer (this); + } + + // Inlines this subfile. Note that return type is an array of heap-allocated + // LDObject copies, they must be deleted manually. + LDObjectList inlineContents (InlineFlags flags); + + protected: + ~LDSubfile(); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS (LDSubfile::InlineFlags) + +// ============================================================================= +// LDLine +// +// Represents a single code-2 line in the LDraw code file. v0 and v1 are the end +// points of the line. The line is colored with dColor unless uncolored mode is +// set. +// ============================================================================= +class LDLine : public LDObject +{ + LDOBJ (Line) + LDOBJ_NAME (line) + LDOBJ_VERTICES (2) + LDOBJ_COLORED + LDOBJ_SCEMANTIC + LDOBJ_NO_MATRIX + + public: + LDLine() {} + LDLine (Vertex v1, Vertex v2); +}; + +// ============================================================================= +// LDCondLine +// +// Represents a single code-5 conditional line. The end-points v0 and v1 are +// inherited from LDLine, c0 and c1 are the control points of this line. +// ============================================================================= +class LDCondLine : public LDLine +{ + LDOBJ (CondLine) + LDOBJ_NAME (condline) + LDOBJ_VERTICES (4) + LDOBJ_COLORED + LDOBJ_SCEMANTIC + LDOBJ_NO_MATRIX + + public: + LDCondLine() {} + LDLine* demote(); +}; + +// ============================================================================= +// LDTriangle +// +// Represents a single code-3 triangle in the LDraw code file. Vertices v0, v1 +// and v2 contain the end-points of this triangle. dColor is the color the +// triangle is colored with. +// ============================================================================= +class LDTriangle : public LDObject +{ + LDOBJ (Triangle) + LDOBJ_NAME (triangle) + LDOBJ_VERTICES (3) + LDOBJ_COLORED + LDOBJ_SCEMANTIC + LDOBJ_NO_MATRIX + + public: + LDTriangle() {} + LDTriangle (Vertex v0, Vertex v1, Vertex v2) + { + setVertex (0, v0); + setVertex (1, v1); + setVertex (2, v2); + } +}; + +// ============================================================================= +// LDQuad +// +// Represents a single code-4 quadrilateral. v0, v1, v2 and v3 are the end points +// of the quad, dColor is the color used for the quad. +// ============================================================================= +class LDQuad : public LDObject +{ + LDOBJ (Quad) + LDOBJ_NAME (quad) + LDOBJ_VERTICES (4) + LDOBJ_COLORED + LDOBJ_SCEMANTIC + LDOBJ_NO_MATRIX + + public: + LDQuad() {} + LDQuad (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3); + + // Split this quad into two triangles (note: heap-allocated) + QList<LDTriangle*> splitToTriangles(); +}; + +// ============================================================================= +// LDVertex +// +// The vertex is an LDForce-specific extension which represents a single +// vertex which can be used as a parameter to tools or to store coordinates +// with. Vertices are a part authoring tool and they should not appear in +// finished parts. +// ============================================================================= +class LDVertex : public LDObject +{ + LDOBJ (Vertex) + LDOBJ_NAME (vertex) + LDOBJ_VERTICES (0) // TODO: move pos to vaCoords[0] + LDOBJ_COLORED + LDOBJ_NON_SCEMANTIC + LDOBJ_NO_MATRIX + + public: + LDVertex() {} + + Vertex pos; +}; + +// ============================================================================= +// LDOverlay +// +// Overlay image meta, stored in the header of parts so as to preserve overlay +// information. +// ============================================================================= +class LDOverlay : public LDObject +{ + LDOBJ (Overlay) + LDOBJ_NAME (overlay) + LDOBJ_VERTICES (0) + LDOBJ_UNCOLORED + LDOBJ_NON_SCEMANTIC + LDOBJ_NO_MATRIX + PROPERTY (public, int, camera, setCamera, STOCK_WRITE) + PROPERTY (public, int, x, setX, STOCK_WRITE) + PROPERTY (public, int, y, setY, STOCK_WRITE) + PROPERTY (public, int, width, setWidth, STOCK_WRITE) + PROPERTY (public, int, height, setHeight, STOCK_WRITE) + PROPERTY (public, QString, fileName, setFileName, STOCK_WRITE) +}; + +// Other common LDraw stuff +static const QString g_CALicense ("!LICENSE Redistributable under CCAL version 2.0 : see CAreadme.txt"); +static const QString g_nonCALicense ("!LICENSE Not redistributable : see NonCAreadme.txt"); +static const int g_lores = 16; +static const int g_hires = 48; + +QString getLicenseText (int id);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/macros.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,104 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once + +#ifndef __GNUC__ +# define __attribute__(X) +#endif + +// ============================================================================= +// +#define PROPERTY(ACCESS, TYPE, READ, WRITE, WRITETYPE) \ +private: \ + TYPE m_##READ; \ + \ +public: \ + inline TYPE const& READ() const \ + { \ + return m_##READ; \ + } \ + \ +ACCESS: \ + void WRITE (TYPE const& a) PROPERTY_##WRITETYPE (READ) \ + +#define PROPERTY_STOCK_WRITE(READ) \ + { \ + m_##READ = a; \ + } + +#define PROPERTY_CUSTOM_WRITE(READ) \ + ; + +// ============================================================================= +// +#define elif(A) else if (A) + +// ============================================================================= +// +#ifdef WIN32 +# define DIRSLASH "\\" +# define DIRSLASH_CHAR '\\' +#else // WIN32 +# define DIRSLASH "/" +# define DIRSLASH_CHAR '/' +#endif // WIN32 + +// ============================================================================= +// +#ifdef __GNUC__ +#define FUNCNAME __PRETTY_FUNCTION__ +#else +#define FUNCNAME __func__ +#endif // __GNUC__ + +// ============================================================================= +// +#define dvalof(A) dprint ("value of '%1' = %2\n", #A, A) + +// ============================================================================= +// +// Replace assert with a version that shows a GUI dialog if possible. +// On Windows I just can't get the actual error messages otherwise. +// +#undef assert + +#ifdef DEBUG +# define assert(N) { ((N) ? (void) 0 : assertionFailure (__FILE__, __LINE__, FUNCNAME, #N)); } +#else +# define assert(N) {} +#endif // DEBUG + +#define for_axes(AX) for (const Axis AX : std::initializer_list<const Axis> ({X, Y, Z})) + +// ============================================================================= +#ifdef IN_IDE_PARSER // KDevelop workarounds: +# error IN_IDE_PARSER is defined (this code is only for KDevelop workarounds) +# define COMPILE_DATE "14-01-10 10:31:09" + +# ifndef va_start +# define va_start(va, arg) +# endif // va_start + +# ifndef va_end +# define va_end(va) +# endif // va_end + +static const char* __func__ = ""; // Current function name +typedef void FILE; // :| +#endif // IN_IDE_PARSER \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,84 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 <QDir> +#include "mainWindow.h" +#include "ldDocument.h" +#include "miscallenous.h" +#include "configuration.h" +#include "colors.h" +#include "basics.h" +#include "primitives.h" +#include "glRenderer.h" +#include "configDialog.h" +#include "dialogs.h" +#include "crashCatcher.h" + +QList<LDDocument*> g_loadedFiles; +MainWindow* g_win = null; +static QString 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); + initCrashCatcher(); + LDDocument::setCurrent (null); + + // Load or create the configuration + if (!Config::load()) + { + print ("Creating configuration file...\n"); + + if (Config::save()) + print ("Configuration file successfully created.\n"); + else + critical ("Failed to create configuration file!\n"); + } + + LDPaths::initPaths(); + initColors(); + MainWindow* win = new MainWindow; + 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(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,42 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +// ============================================================================= +// This file is included one way or another in every source file of LDForge. +// Stuff defined and included here is universally included. + +#pragma once +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdarg.h> +#include <QString> +#include <QTextFormat> +#include "macros.h" +#include "version.h" +#include "configuration.h" +#include "format.h" + +// Null pointer +static const std::nullptr_t null = nullptr; + +void assertionFailure (const char* file, int line, const char* funcname, const char* expr); + +// Version string identifier. These are defined in Version.cc. +const char* versionString(); +const char* fullVersionString();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mainWindow.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,1034 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 <QMetaMethod> +#include "main.h" +#include "glRenderer.h" +#include "mainWindow.h" +#include "ldDocument.h" +#include "configuration.h" +#include "miscallenous.h" +#include "colors.h" +#include "editHistory.h" +#include "radioGroup.h" +#include "addObjectDialog.h" +#include "messageLog.h" +#include "configuration.h" +#include "ui_ldforge.h" + +static bool g_isSelectionLocked = false; + +cfg (Bool, lv_colorize, true); +cfg (String, gui_colortoolbar, "16:24:|:4:25:14:27:2:3:11:1:22:|:0:72:71:15"); +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); +extern_cfg (Bool, gl_drawangles); + +// ============================================================================= +// +MainWindow::MainWindow (QWidget* parent, Qt::WindowFlags flags) : + QMainWindow (parent, flags) +{ + g_win = this; + ui = new Ui_LDForgeUI; + ui->setupUi (this); + m_updatingTabs = false; + m_renderer = new GLRenderer (this); + m_tabs = new QTabBar; + ui->verticalLayout->insertWidget (0, m_tabs); + + // 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 (m_tabs, SIGNAL (currentChanged(int)), 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); + + // Make certain actions checkable + ui->actionAxes->setChecked (gl_axes); + ui->actionWireframe->setChecked (gl_wireframe); + ui->actionBFCView->setChecked (gl_colorbfc); + updateGridToolBar(); + updateEditModeActions(); + updateRecentFilesMenu(); + updateColorToolbar(); + updateTitle(); + updateActionShortcuts(); + + setMinimumSize (300, 200); + + connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup())); + + // Connect all actions + for (QAction* act : findChildren<QAction*>()) + if (!act->objectName().isEmpty()) + connect (act, SIGNAL (triggered()), this, SLOT (slot_action())); +} + +// ============================================================================= +// +KeySequenceConfig* MainWindow::shortcutForAction (QAction* action) +{ + QString keycfgname = format ("key_%1", action->objectName()); + return KeySequenceConfig::getByName (keycfgname); +} + +// ============================================================================= +// +void MainWindow::updateActionShortcuts() +{ + for (QAction* act : findChildren<QAction*>()) + { + KeySequenceConfig* cfg = shortcutForAction (act); + + if (cfg) + act->setShortcut (cfg->getValue()); + } +} + +// ============================================================================= +// +void MainWindow::slot_action() +{ + // Get the name of the sender object and use it to compose the slot name, + // then invoke this slot to call the action. + QMetaObject::invokeMethod (this, + qPrintable (format ("slot_%1", sender()->objectName())), Qt::DirectConnection); + endAction(); +} + +// ============================================================================= +// +void MainWindow::endAction() +{ + // Add a step in 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()); +} + +// ============================================================================= +// +void MainWindow::slot_lastSecondCleanup() +{ + delete m_renderer; + delete ui; +} + +// ============================================================================= +// +void MainWindow::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) + { + QString 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 (QString colorname : gui_colortoolbar.split (":")) + { + if (colorname == "|") + colors << LDQuickColor::getSeparator(); + else + { + LDColor* col = getColor (colorname.toLong()); + + if (col != null) + colors << LDQuickColor (col, null); + } + } + + return colors; +} + +// ============================================================================= +// +void MainWindow::updateColorToolbar() +{ + 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.color(), 22)); + colorButton->setIconSize (QSize (22, 22)); + colorButton->setToolTip (entry.color()->name); + + connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor())); + ui->colorToolbar->addWidget (colorButton); + m_colorButtons << colorButton; + + entry.setToolButton (colorButton); + } + } + + updateGridToolBar(); +} + +// ============================================================================= +// +void MainWindow::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 MainWindow::updateTitle() +{ + QString title = format (APPNAME " %1", fullVersionString()); + + // Append our current file if we have one + if (getCurrentDocument()) + { + if (getCurrentDocument()->name().length() > 0) + title += format (": %1", basename (getCurrentDocument()->name())); + else + title += format (": <anonymous>"); + + if (getCurrentDocument()->getObjectCount() > 0 && + getCurrentDocument()->getObject (0)->type() == LDObject::EComment) + { + // Append title + LDComment* comm = static_cast<LDComment*> (getCurrentDocument()->getObject (0)); + title += format (": %1", comm->text()); + } + + if (getCurrentDocument()->hasUnsavedChanges()) + title += '*'; + } + +#ifdef DEBUG + title += " [debug build]"; +#elif BUILD_ID != BUILD_RELEASE + title += " [pre-release build]"; +#endif // DEBUG + +#ifdef COMPILE_DATE + title += " (built " COMPILE_DATE ")"; +#endif // COMPILE_DATE + + setWindowTitle (title); +} + +// ============================================================================= +// +int MainWindow::deleteSelection() +{ + if (selection().isEmpty()) + return 0; + + LDObjectList selCopy = selection(); + + // Delete the objects that were being selected + for (LDObject* obj : selCopy) + obj->destroy(); + + refresh(); + return selCopy.size(); +} + +// ============================================================================= +// +void MainWindow::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()->objects()) + { + QString descr; + + switch (obj->type()) + { + case LDObject::EComment: + { + descr = static_cast<LDComment*> (obj)->text(); + + // Remove leading whitespace + while (descr[0] == ' ') + descr.remove (0, 1); + + break; + } + + case LDObject::EEmpty: + break; // leave it empty + + case LDObject::ELine: + case LDObject::ETriangle: + case LDObject::EQuad: + case LDObject::ECondLine: + { + for (int i = 0; i < obj->vertices(); ++i) + { + if (i != 0) + descr += ", "; + + descr += obj->vertex (i).toString (true); + } + break; + } + + case LDObject::EError: + { + descr = format ("ERROR: %1", obj->asText()); + break; + } + + case LDObject::EVertex: + { + descr = static_cast<LDVertex*> (obj)->pos.toString (true); + break; + } + + case LDObject::ESubfile: + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + + descr = format ("%1 %2, (", ref->fileInfo()->getDisplayName(), ref->position().toString (true)); + + for (int i = 0; i < 9; ++i) + descr += format ("%1%2", ref->transform()[i], (i != 8) ? " " : ""); + + descr += ')'; + break; + } + + case LDObject::EBFC: + { + descr = LDBFC::k_statementStrings[static_cast<LDBFC*> (obj)->statement()]; + break; + } + + case LDObject::EOverlay: + { + LDOverlay* ovl = static_cast<LDOverlay*> (obj); + descr = format ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->camera()], + basename (ovl->fileName()), ovl->x(), ovl->y(), + ovl->width(), ovl->height()); + break; + } + + default: + { + descr = obj->typeName(); + break; + } + } + + QListWidgetItem* item = new QListWidgetItem (descr); + item->setIcon (getIcon (obj->typeName())); + + // 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->type() == LDObject::EError) + { + item->setBackground (QColor ("#AA0000")); + item->setForeground (QColor ("#FFAA00")); + } + elif (lv_colorize && obj->isColored() && obj->color() != maincolor && obj->color() != edgecolor) + { + // If the object isn't in the main or edge color, draw this + // list entry in said color. + LDColor* col = getColor (obj->color()); + + if (col) + item->setForeground (col->faceColor); + } + + obj->qObjListEntry = item; + ui->objectList->insertItem (ui->objectList->count(), item); + } + + g_isSelectionLocked = false; + updateSelection(); + scrollToSelection(); +} + +// ============================================================================= +// +void MainWindow::scrollToSelection() +{ + if (selection().isEmpty()) + return; + + LDObject* obj = selection().last(); + ui->objectList->scrollToItem (obj->qObjListEntry); +} + +// ============================================================================= +// +void MainWindow::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; + + LDObjectList priorSelection = selection(); + + // Get the objects from the object list selection + getCurrentDocument()->clearSelection(); + const QList<QListWidgetItem*> items = ui->objectList->selectedItems(); + + for (LDObject* obj : getCurrentDocument()->objects()) + { + for (QListWidgetItem* item : items) + { + if (item == obj->qObjListEntry) + { + obj->select(); + break; + } + } + } + + // Update the GL renderer + LDObjectList compound = priorSelection + selection(); + removeDuplicates (compound); + + for (LDObject* obj : compound) + m_renderer->compileObject (obj); + + m_renderer->update(); +} + +// ============================================================================= +// +void MainWindow::slot_recentFile() +{ + QAction* qAct = static_cast<QAction*> (sender()); + openMainFile (qAct->text()); +} + +// ============================================================================= +// +void MainWindow::slot_quickColor() +{ + QToolButton* button = static_cast<QToolButton*> (sender()); + LDColor* col = null; + + for (const LDQuickColor& entry : m_quickColors) + { + if (entry.toolButton() == button) + { + col = entry.color(); + 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); + } + + endAction(); + refresh(); +} + +// ============================================================================= +// +int MainWindow::getInsertionPoint() +{ + // If we have a selection, put the item after it. + if (!selection().isEmpty()) + return selection().last()->lineNumber() + 1; + + // Otherwise place the object at the end. + return getCurrentDocument()->getObjectCount(); +} + +// ============================================================================= +// +void MainWindow::doFullRefresh() +{ + buildObjList(); + m_renderer->hardRefresh(); +} + +// ============================================================================= +// +void MainWindow::refresh() +{ + buildObjList(); + m_renderer->update(); +} + +// ============================================================================= +// +void MainWindow::updateSelection() +{ + g_isSelectionLocked = true; + + for (LDObject* obj : getCurrentDocument()->objects()) + 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 MainWindow::getSelectedColor() +{ + int result = -1; + + for (LDObject* obj : selection()) + { + if (obj->isColored() == false) + continue; // doesn't use color + + if (result != -1 && obj->color() != result) + return -1; // No consensus in object color + + if (result == -1) + result = obj->color(); + } + + return result; +} + +// ============================================================================= +// +LDObject::Type MainWindow::getUniformSelectedType() +{ + LDObject::Type result = LDObject::EUnidentified; + + for (LDObject* obj : selection()) + { + if (result != LDObject::EUnidentified && obj->color() != result) + return LDObject::EUnidentified; + + if (result == LDObject::EUnidentified) + result = obj->type(); + } + + return result; +} + +// ============================================================================= +// +void MainWindow::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 MainWindow::spawnContextMenu (const QPoint pos) +{ + const bool single = (selection().size() == 1); + LDObject* singleObj = (single) ? selection()[0] : null; + + QMenu* contextMenu = new QMenu; + + if (single && singleObj->type() != LDObject::EEmpty) + { + 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 (selection().size() > 0) + { + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionSubfileSelection); + } + + if (R()->camera() != GL::EFreeCamera) + { + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionSetDrawDepth); + } + + contextMenu->exec (pos); +} + +// ============================================================================= +// +void MainWindow::deleteByColor (int colnum) +{ + LDObjectList objs; + + for (LDObject* obj : getCurrentDocument()->objects()) + { + if (!obj->isColored() || obj->color() != colnum) + continue; + + objs << obj; + } + + for (LDObject* obj : objs) + obj->destroy(); +} + +// ============================================================================= +// +void MainWindow::updateEditModeActions() +{ + const EditMode mode = R()->editMode(); + ui->actionModeSelect->setChecked (mode == ESelectMode); + ui->actionModeDraw->setChecked (mode == EDrawMode); + ui->actionModeCircle->setChecked (mode == ECircleMode); +} + +// ============================================================================= +// +void MainWindow::slot_editObject (QListWidgetItem* listitem) +{ + LDObject* obj = null; + + for (LDObject* it : getCurrentDocument()->objects()) + { + if (it->qObjListEntry == listitem) + { + obj = it; + break; + } + } + + AddObjectDialog::staticDialog (obj->type(), obj); +} + +// ============================================================================= +// +bool MainWindow::save (LDDocument* doc, bool saveAs) +{ + QString path = doc->fullPath(); + + if (saveAs || path.isEmpty()) + { + QString name = doc->defaultName(); + + if (!doc->fullPath().isEmpty()) + name = doc->fullPath(); + elif (!doc->name().isEmpty()) + name = doc->name(); + + name.replace ("\\", "/"); + path = QFileDialog::getSaveFileName (g_win, tr ("Save As"), + name, tr ("LDraw files (*.dat *.ldr)")); + + if (path.isEmpty()) + { + // User didn't give a file name, abort. + return false; + } + } + + if (doc->save (path)) + { + if (doc == getCurrentDocument()) + updateTitle(); + + print ("Saved to %1.", path); + + // Add it to recent files + addRecentFile (path); + return true; + } + + QString message = format (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) + return save (doc, true); // yay recursion! + + return false; +} + +void MainWindow::addMessage (QString msg) +{ + m_msglog->addLine (msg); +} + +// ============================================================================ +void ObjectList::contextMenuEvent (QContextMenuEvent* ev) +{ + g_win->spawnContextMenu (ev->globalPos()); +} + +// ============================================================================= +// +QPixmap getIcon (QString iconName) +{ + return (QPixmap (format (":/icons/%1.png", iconName))); +} + +// ============================================================================= +// +bool confirm (const QString& message) +{ + return confirm (MainWindow::tr ("Confirm"), message); +} + +// ============================================================================= +// +bool confirm (const QString& title, const QString& message) +{ + return QMessageBox::question (g_win, title, message, + (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes; +} + +// ============================================================================= +// +void critical (const QString& message) +{ + QMessageBox::critical (g_win, MainWindow::tr ("Error"), message, + (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; + 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()->objects()) + { + if (!obj->isColored()) + continue; + + if (counts.find (obj->color()) == counts.end()) + counts[obj->color()] = 1; + else + counts[obj->color()]++; + } + + 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, format ("[%1] %2 (%3 object%4)", + pair.first, col->name, pair.second, plural (pair.second))); + box->setItemData (row, pair.first); + + ++row; + } +} + +// ============================================================================= +// +void MainWindow::updateDocumentList() +{ + m_updatingTabs = true; + + while (m_tabs->count() > 0) + m_tabs->removeTab (0); + + 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 the tab index + // in the document so we can find documents by tab index. + f->setTabIndex (m_tabs->addTab ("")); + updateDocumentListItem (f); + } + + m_updatingTabs = false; +} + +// ============================================================================= +// +void MainWindow::updateDocumentListItem (LDDocument* doc) +{ + bool oldUpdatingTabs = m_updatingTabs; + m_updatingTabs = true; + + if (doc->tabIndex() == -1) + { + // 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 (doc == getCurrentDocument()) + m_tabs->setCurrentIndex (doc->tabIndex()); + + m_tabs->setTabText (doc->tabIndex(), doc->getDisplayName()); + + // If the document.has unsaved changes, draw a little icon next to it to mark that. + m_tabs->setTabIcon (doc->tabIndex(), doc->hasUnsavedChanges() ? getIcon ("file-save") : QIcon()); + m_updatingTabs = oldUpdatingTabs; +} + +// ============================================================================= +// +// 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 MainWindow::changeCurrentFile() +{ + if (m_updatingTabs) + return; + + LDDocument* f = null; + int tabIndex = m_tabs->currentIndex(); + + // Find the file pointer of the item that was selected. + for (LDDocument* it : g_loadedFiles) + { + if (it->tabIndex() == tabIndex) + { + f = it; + break; + } + } + + // If we picked the same file we're currently on, we don't need to do + // anything. + if (f == null || f == getCurrentDocument()) + return; + + LDDocument::setCurrent (f); +} + +// ============================================================================= +// +void MainWindow::refreshObjectList() +{ +#if 0 + ui->objectList->clear(); + LDDocument* f = getCurrentDocument(); + +for (LDObject* obj : *f) + ui->objectList->addItem (obj->qObjListEntry); + +#endif + + buildObjList(); +} + +// ============================================================================= +// +void MainWindow::updateActions() +{ + History* his = getCurrentDocument()->history(); + int pos = his->position(); + ui->actionUndo->setEnabled (pos != -1); + ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1); + ui->actionAxes->setChecked (gl_axes); + ui->actionBFCView->setChecked (gl_colorbfc); + ui->actionDrawAngles->setChecked (gl_drawangles); +} + +// ============================================================================= +// +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 color() == null; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mainWindow.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,378 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QMainWindow> +#include <QAction> +#include <QListWidget> +#include <QRadioButton> +#include "configuration.h" +#include "ldObject.h" +#include "ui_ldforge.h" + +class MessageManager; +class MainWindow; +class LDColor; +class QToolButton; +class QDialogButtonBox; +class GLRenderer; +class QComboBox; +class QProgressBar; +class Ui_LDForgeUI; + +// Stuff for dialogs +#define IMPLEMENT_DIALOG_BUTTONS \ + bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); \ + connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept())); \ + connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); \ + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +#define DEFINE_ACTION(NAME, DEFSHORTCUT) \ + cfg (KeySequence, key_action##NAME, DEFSHORTCUT); \ + void MainWindow::slot_action##NAME() + +// Convenience macros for key sequences. +#define KEY(N) (Qt::Key_##N) +#define CTRL(N) (Qt::CTRL | Qt::Key_##N) +#define SHIFT(N) (Qt::SHIFT | Qt::Key_##N) +#define CTRL_SHIFT(N) (Qt::CTRL | Qt::SHIFT | Qt::Key_##N) + +// ============================================================================= +class LDQuickColor +{ + PROPERTY (public, LDColor*, color, setColor, STOCK_WRITE) + PROPERTY (public, QToolButton*, toolButton, setToolButton, STOCK_WRITE) + + public: + LDQuickColor (LDColor* color, QToolButton* toolButton); + bool isSeparator() const; + + static LDQuickColor getSeparator(); +}; + +//! +//! Object list class for MainWindow +//! +class ObjectList : public QListWidget +{ + Q_OBJECT + + protected: + void contextMenuEvent (QContextMenuEvent* ev); +}; + +//! +//! \brief The main window class. +//! +//! The MainWindow is LDForge's main GUI. It hosts the renderer, the object list, +//! the message log, etc. Contains \c slot_action(), which is what all actions +//! connect to. +//! +class MainWindow : public QMainWindow +{ + Q_OBJECT + + public: + //! Constructs the main window + explicit MainWindow (QWidget* parent = null, Qt::WindowFlags flags = 0); + + //! Rebuilds the object list, located to the right of the GUI. + void buildObjList(); + + //! Updates the window title. + void updateTitle(); + + //! Builds the object list and tells the GL renderer to init a full + //! refresh. + void doFullRefresh(); + + //! Builds the object list and tells the GL renderer to do a soft update. + void refresh(); + + //! \returns the suggested position to place a new object at. + int getInsertionPoint(); + + //! Updates the quick color toolbar + void updateColorToolbar(); + + //! Rebuilds the recent files submenu + void updateRecentFilesMenu(); + + //! Sets the selection based on what's selected in the object list. + void updateSelection(); + + //! Updates the grids, selects the selected grid and deselects others. + void updateGridToolBar(); + + //! Updates the edit modes, current one is selected and others are deselected. + void updateEditModeActions(); + + //! Rebuilds the document tab list. + void updateDocumentList(); + + //! Updates the document tab for \c doc. If no such tab exists, the + //! document list is rebuilt instead. + void updateDocumentListItem (LDDocument* doc); + + //! \returns the uniform selected color (i.e. 4 if everything selected is + //! red), -1 if there is no such consensus. + int getSelectedColor(); + + //! \returns the uniform selected type (i.e. \c LDObject::ELine if everything + //! selected is a line), \c LDObject::EUnidentified if there is no such + //! consensus. + LDObject::Type getUniformSelectedType(); + + //! Automatically scrolls the object list so that it points to the first + //! selected object. + void scrollToSelection(); + + //! Spawns the context menu at the given position. + void spawnContextMenu (const QPoint pos); + + //! Deletes all selected objects. + //! \returns the count of deleted objects. + int deleteSelection(); + + //! Deletes all objects by the given color number. + void deleteByColor (int colnum); + + //! Tries to save the given document. + //! \param doc the document to save + //! \param saveAs if true, always ask for a file path + //! \returns whether the save was successful + bool save (LDDocument* doc, bool saveAs); + + //! Updates various actions, undo/redo are set enabled/disabled where + //! appropriate, togglable actions are updated based on configuration, + //! etc. + void updateActions(); + + //! \returns a pointer to the renderer + inline GLRenderer* R() + { + return m_renderer; + } + + //! Sets the quick color list to the given list of colors. + inline void setQuickColors (const QList<LDQuickColor>& colors) + { + m_quickColors = colors; + updateColorToolbar(); + } + + //! Adds a message to the renderer's message manager. + void addMessage (QString msg); + + //! Updates the object list. Right now this just rebuilds it. + void refreshObjectList(); + + //! Updates all actions to ensure they have the correct shortcut as + //! defined in the configuration entries. + void updateActionShortcuts(); + + //! Gets the shortcut configuration for the given \c action + KeySequenceConfig* shortcutForAction (QAction* action); + + void endAction(); + + public slots: + void changeCurrentFile(); + void slot_action(); + void slot_actionNew(); + void slot_actionNewFile(); + void slot_actionOpen(); + void slot_actionDownloadFrom(); + void slot_actionSave(); + void slot_actionSaveAs(); + void slot_actionSaveAll(); + void slot_actionClose(); + void slot_actionCloseAll(); + void slot_actionInsertFrom(); + void slot_actionExportTo(); + void slot_actionSettings(); + void slot_actionSetLDrawPath(); + void slot_actionScanPrimitives(); + void slot_actionExit(); + void slot_actionResetView(); + void slot_actionAxes(); + void slot_actionWireframe(); + void slot_actionBFCView(); + void slot_actionSetOverlay(); + void slot_actionClearOverlay(); + void slot_actionScreenshot(); + void slot_actionInsertRaw(); + void slot_actionNewSubfile(); + void slot_actionNewLine(); + void slot_actionNewTriangle(); + void slot_actionNewQuad(); + void slot_actionNewCLine(); + void slot_actionNewComment(); + void slot_actionNewBFC(); + void slot_actionNewVertex(); + void slot_actionUndo(); + void slot_actionRedo(); + void slot_actionCut(); + void slot_actionCopy(); + void slot_actionPaste(); + void slot_actionDelete(); + void slot_actionSelectAll(); + void slot_actionSelectByColor(); + void slot_actionSelectByType(); + void slot_actionModeDraw(); + void slot_actionModeSelect(); + void slot_actionModeCircle(); + void slot_actionSetDrawDepth(); + void slot_actionSetColor(); + void slot_actionAutocolor(); + void slot_actionUncolorize(); + void slot_actionInline(); + void slot_actionInlineDeep(); + void slot_actionInvert(); + void slot_actionMakePrimitive(); + void slot_actionSplitQuads(); + void slot_actionEditRaw(); + void slot_actionBorders(); + void slot_actionCornerVerts(); + void slot_actionRoundCoordinates(); + void slot_actionVisibilityHide(); + void slot_actionVisibilityReveal(); + void slot_actionVisibilityToggle(); + void slot_actionReplaceCoords(); + void slot_actionFlip(); + void slot_actionDemote(); + void slot_actionYtruder(); + void slot_actionRectifier(); + void slot_actionIntersector(); + void slot_actionIsecalc(); + void slot_actionCoverer(); + void slot_actionEdger2(); + void slot_actionHelp(); + void slot_actionAbout(); + void slot_actionAboutQt(); + void slot_actionGridCoarse(); + void slot_actionGridMedium(); + void slot_actionGridFine(); + void slot_actionEdit(); + void slot_actionMoveUp(); + void slot_actionMoveDown(); + void slot_actionMoveXNeg(); + void slot_actionMoveXPos(); + void slot_actionMoveYNeg(); + void slot_actionMoveYPos(); + void slot_actionMoveZNeg(); + void slot_actionMoveZPos(); + void slot_actionRotateXNeg(); + void slot_actionRotateXPos(); + void slot_actionRotateYNeg(); + void slot_actionRotateYPos(); + void slot_actionRotateZNeg(); + void slot_actionRotateZPos(); + void slot_actionRotationPoint(); + void slot_actionAddHistoryLine(); + void slot_actionJumpTo(); + void slot_actionSubfileSelection(); + void slot_actionDrawAngles(); + + protected: + void closeEvent (QCloseEvent* ev); + + private: + GLRenderer* m_renderer; + LDObjectList m_sel; + QList<LDQuickColor> m_quickColors; + QList<QToolButton*> m_colorButtons; + QList<QAction*> m_recentFiles; + MessageManager* m_msglog; + Ui_LDForgeUI* ui; + QTabBar* m_tabs; + bool m_updatingTabs; + + private slots: + void slot_selectionChanged(); + void slot_recentFile(); + void slot_quickColor(); + void slot_lastSecondCleanup(); + void slot_editObject (QListWidgetItem* listitem); +}; + +//! Pointer to the instance of MainWindow. +extern MainWindow* g_win; + +//! Get an icon by name from the resources directory. +QPixmap getIcon (QString iconName); + +//! \returns a list of quick colors based on the configuration entry. +QList<LDQuickColor> quickColorsFromConfig(); + +//! Asks the user a yes/no question with the given \c message and the given +//! window \c title. +//! \returns true if the user answered yes, false if no. +bool confirm (const QString& title, const QString& message); // Generic confirm prompt + +//! An overload of \c confirm(), this asks the user a yes/no question with the +//! given \c message. +//! \returns true if the user answered yes, false if no. +bool confirm (const QString& message); + +//! Displays an error prompt with the given \c message +void critical (const QString& message); + +//! Makes an icon of \c size x \c size pixels to represent \c colinfo +QIcon makeColorIcon (LDColor* colinfo, const int size); + +//! Fills the given combo-box with color information +void makeColorComboBox (QComboBox* box); + +//! \returns a QImage from the given raw GL \c data +QImage imageFromScreencap (uchar* data, int w, int h); + +//! +//! Takes in pairs of radio buttons and respective values and finds the first +//! selected one. +//! \returns returns the value of the first found radio button that was checked +//! \returns by the user. +//! +template<class T> +T radioSwitch (const T& defval, QList<Pair<QRadioButton*, T>> haystack) +{ + for (Pair<QRadioButton*, const T&> i : haystack) + if (i.first->isChecked()) + return i.second; + + return defval; +} + +//! +//! Takes in pairs of radio buttons and respective values and checks the first +//! found radio button whose respsective value matches \c expr have the given value. +//! +template<class T> +void radioDefault (const T& expr, QList<Pair<QRadioButton*, T>> haystack) +{ + for (Pair<QRadioButton*, const T&> i : haystack) + { + if (i.second == expr) + { + i.first->setChecked (true); + return; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/messageLog.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,131 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "glRenderer.h" +#include "mainWindow.h" + +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 (QString text) : + text (text), + alpha (1.0f), + expiry (QDateTime::currentDateTime().addSecs (g_expiry)) {} + +// ============================================================================= +// +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 (QString 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 (renderer()) + renderer()->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 && renderer()) + renderer()->update(); +} + +// ============================================================================= +// +const QList<MessageManager::Line>& MessageManager::getLines() const +{ + return m_lines; +} + +// ============================================================================= +// +void printToLog (const QString& msg) +{ + for (QString& a : msg.split ("\n", QString::SkipEmptyParts)) + { + if (g_win != null) + 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/messageLog.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,84 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QObject> +#include <QDate> +#include "main.h" +#include "basics.h" + +class GLRenderer; +class QTimer; + +//! +//! \brief Manages the list of messages at the top-left of the renderer. +//! +//! The message manager is an object which keeps track of messages that appear +//! on the renderer's screen. Each line is contained in a separate object which +//! contains the text, expiry time and alpha. The message manager is doubly +//! linked to its corresponding renderer. +//! +//! Message manager calls its \c tick() function regularly to update the messages, +//! where each line's expiry is checked for. Lines begin to fade out when nearing +//! their expiry. If the message manager's lines change, the renderer undergoes +//! repainting. +//! +class MessageManager : public QObject +{ + Q_OBJECT + PROPERTY (public, GLRenderer*, renderer, setRenderer, STOCK_WRITE) + + public: + //! \class MessageManager::Line + //! A single line of the message log. + class Line + { + public: + //! Constructs a line with the given \c text + Line (QString text); + + //! Check this line's expiry and update alpha accordingly. + //! \c changed is updated to whether the line has somehow + //! changed since the last update. + //! \returns true if the line is to still stick around, false + //! \returns if it expired. + bool update (bool& changed); + + QString text; + float alpha; + QDateTime expiry; + }; + + //! Constructs the message manager. + explicit MessageManager (QObject* parent = null); + + //! Adds a line with the given \c text to the message manager. + void addLine (QString line); + + //! \returns all active lines in the message manager. + const QList<Line>& getLines() const; + + private: + QList<Line> m_lines; + QTimer* m_ticker; + + private slots: + //! Ticks the manager. This is called by the timer to update + //! the messages. + void tick(); +};
--- a/src/misc/DocumentPointer.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "DocumentPointer.h" -#include "../Document.h" -#include "../Misc.h" - -LDDocumentPointer::LDDocumentPointer() : m_pointer (null) {} - -// ============================================================================= -// -LDDocumentPointer::LDDocumentPointer (LDDocument* ptr) : - m_pointer (ptr) -{ - addReference (); -} - -// ============================================================================= -// -LDDocumentPointer::LDDocumentPointer (const LDDocumentPointer& other) : - m_pointer (other.pointer()) -{ - addReference (); -} - -// ============================================================================= -// -LDDocumentPointer::~LDDocumentPointer() -{ - removeReference(); -} - -// ============================================================================= -// -void LDDocumentPointer::addReference() -{ - if (pointer() != null) - pointer()->addReference (this); -} - -// ============================================================================= -// -void LDDocumentPointer::removeReference() -{ - if (pointer() != null) - pointer()->removeReference (this); -} - -// ============================================================================= -// -LDDocumentPointer& LDDocumentPointer::operator= (LDDocument* ptr) -{ - if (ptr != pointer()) - { - removeReference(); - setPointer (ptr); - addReference(); - } - - return *this; -} \ No newline at end of file
--- a/src/misc/DocumentPointer.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once - -#include "../Main.h" - -class LDSubfile; -class LDDocument; - -//! -//! \brief A reference-counting pointer to LDDocument. -//! -//! The LDDocumentPointer class defines a reference-counting pointer which -//! points to LDDocument. -//! -class LDDocumentPointer -{ - PROPERTY (private, LDDocument*, pointer, setPointer, STOCK_WRITE) - - public: - //! Constructs a null LDDocumentPointer - LDDocumentPointer(); - - //! Constructs a document pointer with the given pointer - LDDocumentPointer (LDDocument* ptr); - - //! Copy-constructs a LDDocumentPointer. - LDDocumentPointer (const LDDocumentPointer& other); - - //! Destructs the pointer. - ~LDDocumentPointer(); - - //! \param ptr the new pointer to change to. - LDDocumentPointer& operator= (LDDocument* ptr); - - //! Copy operator. - //! \param other the pointer whose internal pointer to copy. - inline LDDocumentPointer& operator= (LDDocumentPointer& other) - { - return operator= (other.pointer()); - } - - //! Operator overload for a->b support. - inline LDDocument* operator->() const - { - return pointer(); - } - - //! Cast operator overload - inline operator LDDocument*() const - { - return pointer(); - } - - private: - void addReference(); - void removeReference(); -};
--- a/src/misc/InvokationDeferer.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "InvokationDeferer.h" -#include "../Misc.h" - -static InvokationDeferer* g_invokationDeferer = new InvokationDeferer(); - -// ============================================================================= -// -InvokationDeferer::InvokationDeferer (QObject* parent) : QObject (parent) -{ - connect (this, SIGNAL (functionAdded()), this, SLOT (invokeFunctions()), - Qt::QueuedConnection); -} - -// ============================================================================= -// -void InvokationDeferer::addFunctionCall (InvokationDeferer::FunctionType func) -{ - m_funcs << func; - removeDuplicates (m_funcs); - emit functionAdded(); -} - -// ============================================================================= -// -void InvokationDeferer::invokeFunctions() -{ - for (FunctionType func : m_funcs) - (*func)(); - - m_funcs.clear(); -} - -// ============================================================================= -// -void invokeLater (InvokationDeferer::FunctionType func) -{ - g_invokationDeferer->addFunctionCall (func); -} \ No newline at end of file
--- a/src/misc/InvokationDeferer.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once -#include <QObject> - -class InvokationDeferer : public QObject -{ - Q_OBJECT - - public: - using FunctionType = void(*)(); - - explicit InvokationDeferer (QObject* parent = 0); - void addFunctionCall (FunctionType func); - - signals: - void functionAdded(); - - private: - QList<FunctionType> m_funcs; - - private slots: - void invokeFunctions(); -}; - -void invokeLater (InvokationDeferer::FunctionType func);
--- a/src/misc/RingFinder.cc Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,169 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 "RingFinder.h" -#include "../Misc.h" - -RingFinder g_RingFinder; - -// ============================================================================= -// -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.isSuperiorTo (m_bestSolution)) - m_bestSolution = / - } - - return (m_bestSolution != null); -} - -// ============================================================================= -// -bool RingFinder::Solution::isSuperiorTo (const 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) - { - maxA = max (getComponents()[i].num, maxA); - maxB = max (other->getComponents()[i].num, maxB); - } - - 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; -} \ No newline at end of file
--- a/src/misc/RingFinder.h Wed Mar 12 16:20:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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/>. - */ - -#pragma once - -#include "../Main.h" - -//! -//! \brief Provides an algorithm for finding solutions of rings between given radii. -//! -//! The RingFinder is a class which implements a ring finding algorithm. It is passed -//! two radii and it tries to find solutions of rings that would fill the given space. -//! -//! \note It is not fool-proof and does not always yield a solution, never assume -//! \note that one is a available as none is guaranteed. -//! -class RingFinder -{ - public: - //! A single component in a solution - struct Component - { - int num; - double scale; - }; - - //! A solution whose components would fill the desired ring space. - class Solution - { - public: - //! \returns components of this solution - inline const QVector<Component>& getComponents() const - { - return m_components; - } - - //! Add a component to this solution - inline void addComponent (const Component& a) - { - m_components.push_back (a); - } - - //! \brief Compare solutions. - //! - //! Compares this solution with \c other and determines which - //! one is superior. - //! - //! A solution is considered superior if solution has less - //! components than the other one. If both solution have an - //! equal amount components, the solution with a lesser maximum - //! ring number is found superior, as such solutions should - //! yield less new primitives and cleaner definitions. - //! - //! The solution which is found superior to every other solution - //! will be the one returned by \c RingFinder::bestSolution(). - //! - //! \param other the solution to check against - //! \returns whether this solution is considered superior - //! \returns to \c other. - //! - bool isSuperiorTo (const Solution* other) const; - - private: - QVector<Component> m_components; - }; - - //! Constructs a ring finder. - RingFinder() {} - - //! \brief Tries to find rings between \c r0 and \c r1. - //! - //! 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. - //! - //! \param r0 the lower radius of the ring space - //! \param r1 the higher radius of the ring space - //! \returns whether it was possible to find a solution for the given - //! \returns ring space. - //! - bool findRings (double r0, double r1); - - //! \returns the solution that was considered best. Returns \c null - //! \returns if no suitable solution was found. - //! \see \c RingFinder::Solution::isSuperiorTo() - inline const Solution* bestSolution() - { - return m_bestSolution; - } - - //! \returns all found solutions. The list is empty if no solutions - //! \returns were found. - inline const QVector<Solution>& allSolutions() const - { - return m_solutions; - } - - private: - QVector<Solution> m_solutions; - const Solution* m_bestSolution; - int m_stack; - - //! Helper function for \c findRings - bool findRingsRecursor (double r0, double r1, Solution& currentSolution); -}; - -extern RingFinder g_RingFinder; -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/documentPointer.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,76 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "documentPointer.h" +#include "../ldDocument.h" +#include "../miscallenous.h" + +LDDocumentPointer::LDDocumentPointer() : m_pointer (null) {} + +// ============================================================================= +// +LDDocumentPointer::LDDocumentPointer (LDDocument* ptr) : + m_pointer (ptr) +{ + addReference (); +} + +// ============================================================================= +// +LDDocumentPointer::LDDocumentPointer (const LDDocumentPointer& other) : + m_pointer (other.pointer()) +{ + addReference (); +} + +// ============================================================================= +// +LDDocumentPointer::~LDDocumentPointer() +{ + removeReference(); +} + +// ============================================================================= +// +void LDDocumentPointer::addReference() +{ + if (pointer() != null) + pointer()->addReference (this); +} + +// ============================================================================= +// +void LDDocumentPointer::removeReference() +{ + if (pointer() != null) + pointer()->removeReference (this); +} + +// ============================================================================= +// +LDDocumentPointer& LDDocumentPointer::operator= (LDDocument* ptr) +{ + if (ptr != pointer()) + { + removeReference(); + setPointer (ptr); + addReference(); + } + + return *this; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/documentPointer.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,74 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once + +#include "../main.h" + +class LDSubfile; +class LDDocument; + +//! +//! \brief A reference-counting pointer to LDDocument. +//! +//! The LDDocumentPointer class defines a reference-counting pointer which +//! points to LDDocument. +//! +class LDDocumentPointer +{ + PROPERTY (private, LDDocument*, pointer, setPointer, STOCK_WRITE) + + public: + //! Constructs a null LDDocumentPointer + LDDocumentPointer(); + + //! Constructs a document pointer with the given pointer + LDDocumentPointer (LDDocument* ptr); + + //! Copy-constructs a LDDocumentPointer. + LDDocumentPointer (const LDDocumentPointer& other); + + //! Destructs the pointer. + ~LDDocumentPointer(); + + //! \param ptr the new pointer to change to. + LDDocumentPointer& operator= (LDDocument* ptr); + + //! Copy operator. + //! \param other the pointer whose internal pointer to copy. + inline LDDocumentPointer& operator= (LDDocumentPointer& other) + { + return operator= (other.pointer()); + } + + //! Operator overload for a->b support. + inline LDDocument* operator->() const + { + return pointer(); + } + + //! Cast operator overload + inline operator LDDocument*() const + { + return pointer(); + } + + private: + void addReference(); + void removeReference(); +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/invokeLater.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,56 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "invokeLater.h" +#include "../miscallenous.h" + +static InvokationDeferer* g_invokationDeferer = new InvokationDeferer(); + +// ============================================================================= +// +InvokationDeferer::InvokationDeferer (QObject* parent) : QObject (parent) +{ + connect (this, SIGNAL (functionAdded()), this, SLOT (invokeFunctions()), + Qt::QueuedConnection); +} + +// ============================================================================= +// +void InvokationDeferer::addFunctionCall (InvokationDeferer::FunctionType func) +{ + m_funcs << func; + removeDuplicates (m_funcs); + emit functionAdded(); +} + +// ============================================================================= +// +void InvokationDeferer::invokeFunctions() +{ + for (FunctionType func : m_funcs) + (*func)(); + + m_funcs.clear(); +} + +// ============================================================================= +// +void invokeLater (InvokationDeferer::FunctionType func) +{ + g_invokationDeferer->addFunctionCall (func); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/invokeLater.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,42 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QObject> + +class InvokationDeferer : public QObject +{ + Q_OBJECT + + public: + using FunctionType = void(*)(); + + explicit InvokationDeferer (QObject* parent = 0); + void addFunctionCall (FunctionType func); + + signals: + void functionAdded(); + + private: + QList<FunctionType> m_funcs; + + private slots: + void invokeFunctions(); +}; + +void invokeLater (InvokationDeferer::FunctionType func);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/ringFinder.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,169 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "ringFinder.h" +#include "../miscallenous.h" + +RingFinder g_RingFinder; + +// ============================================================================= +// +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.isSuperiorTo (m_bestSolution)) + m_bestSolution = / + } + + return (m_bestSolution != null); +} + +// ============================================================================= +// +bool RingFinder::Solution::isSuperiorTo (const 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) + { + maxA = max (getComponents()[i].num, maxA); + maxB = max (other->getComponents()[i].num, maxB); + } + + 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; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/ringFinder.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,129 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once + +#include "../main.h" + +//! +//! \brief Provides an algorithm for finding solutions of rings between given radii. +//! +//! The RingFinder is a class which implements a ring finding algorithm. It is passed +//! two radii and it tries to find solutions of rings that would fill the given space. +//! +//! \note It is not fool-proof and does not always yield a solution, never assume +//! \note that one is a available as none is guaranteed. +//! +class RingFinder +{ + public: + //! A single component in a solution + struct Component + { + int num; + double scale; + }; + + //! A solution whose components would fill the desired ring space. + class Solution + { + public: + //! \returns components of this solution + inline const QVector<Component>& getComponents() const + { + return m_components; + } + + //! Add a component to this solution + inline void addComponent (const Component& a) + { + m_components.push_back (a); + } + + //! \brief Compare solutions. + //! + //! Compares this solution with \c other and determines which + //! one is superior. + //! + //! A solution is considered superior if solution has less + //! components than the other one. If both solution have an + //! equal amount components, the solution with a lesser maximum + //! ring number is found superior, as such solutions should + //! yield less new primitives and cleaner definitions. + //! + //! The solution which is found superior to every other solution + //! will be the one returned by \c RingFinder::bestSolution(). + //! + //! \param other the solution to check against + //! \returns whether this solution is considered superior + //! \returns to \c other. + //! + bool isSuperiorTo (const Solution* other) const; + + private: + QVector<Component> m_components; + }; + + //! Constructs a ring finder. + RingFinder() {} + + //! \brief Tries to find rings between \c r0 and \c r1. + //! + //! 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. + //! + //! \param r0 the lower radius of the ring space + //! \param r1 the higher radius of the ring space + //! \returns whether it was possible to find a solution for the given + //! \returns ring space. + //! + bool findRings (double r0, double r1); + + //! \returns the solution that was considered best. Returns \c null + //! \returns if no suitable solution was found. + //! \see \c RingFinder::Solution::isSuperiorTo() + inline const Solution* bestSolution() + { + return m_bestSolution; + } + + //! \returns all found solutions. The list is empty if no solutions + //! \returns were found. + inline const QVector<Solution>& allSolutions() const + { + return m_solutions; + } + + private: + QVector<Solution> m_solutions; + const Solution* m_bestSolution; + int m_stack; + + //! Helper function for \c findRings + bool findRingsRecursor (double r0, double r1, Solution& currentSolution); +}; + +extern RingFinder g_RingFinder; +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/miscallenous.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,303 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "miscallenous.h" +#include "mainWindow.h" +#include "dialogs.h" +#include "ldDocument.h" +#include "ui_rotpoint.h" +#include "misc/documentPointer.cc" +#include "misc/ringFinder.cc" +#include "misc/invokeLater.cc" + +// 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 long g_e10[] = +{ + 1l, + 10l, + 100l, + 1000l, + 10000l, + 100000l, + 1000000l, + 10000000l, + 100000000l, + 1000000000l, +}; + +// ============================================================================= +// +// 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 (Vertex, edit_customrotpoint, g_origin); + +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]; + 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 QString& 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 LDObjectList& objs) +{ + switch ((ERotationPoint) edit_rotpoint) + { + case EObjectOrigin: + { + LDBoundingBox box; + + // Calculate center vertex + for (LDObject* obj : objs) + { + if (obj->hasMatrix()) + box << dynamic_cast<LDMatrixObject*> (obj)->position(); + else + box << obj; + } + + return box.center(); + } + + case EWorldOrigin: + { + return g_origin; + } + + case ECustomPoint: + { + return edit_customrotpoint; + } + } + + return Vertex(); +} + +// ============================================================================= +// +void configRotationPoint() +{ + QDialog* dlg = new QDialog; + Ui::RotPointUI ui; + ui.setupUi (dlg); + + switch ((ERotationPoint) edit_rotpoint) + { + case EObjectOrigin: + ui.objectPoint->setChecked (true); + break; + + case EWorldOrigin: + ui.worldPoint->setChecked (true); + break; + + case ECustomPoint: + ui.customPoint->setChecked (true); + break; + } + + ui.customX->setValue (edit_customrotpoint.x()); + ui.customY->setValue (edit_customrotpoint.y()); + ui.customZ->setValue (edit_customrotpoint.z()); + + if (!dlg->exec()) + return; + + edit_rotpoint = + (ui.objectPoint->isChecked()) ? EObjectOrigin : + (ui.worldPoint->isChecked()) ? EWorldOrigin : + ECustomPoint; + + edit_customrotpoint.x() = ui.customX->value(); + edit_customrotpoint.y() = ui.customY->value(); + edit_customrotpoint.z() = ui.customZ->value(); +} + +// ============================================================================= +// +QString join (QList<StringFormatArg> vals, QString delim) +{ + QStringList list; + + for (const StringFormatArg& arg : vals) + list << arg.text(); + + return list.join (delim); +} + +// ============================================================================= +// +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]; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/miscallenous.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,138 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QVector> +#include "configuration.h" +#include "main.h" +#include "basics.h" + +#define NUM_PRIMES 500 + +class LDDocument; +class QColor; +class QAction; + +// Prime numbers +extern const int g_primes[NUM_PRIMES]; + +// Returns whether a given string represents a floating point number. +bool numeric (const QString& tok); + +// Simplifies the given fraction. +void simplify (int& numer, int& denom); + +void roundToDecimals (double& a, int decimals); + +QString join (QList< StringFormatArg > vals, QString delim = " "); + +// Grid stuff +struct gridinfo +{ + const char* const name; + float* const confs[4]; +}; + +extern_cfg (Int, grid); +static const int g_NumGrids = 3; +extern const gridinfo g_GridInfo[3]; + +inline const gridinfo& currentGrid() +{ + return g_GridInfo[grid]; +} + +// ============================================================================= +enum ERotationPoint +{ + EObjectOrigin, + EWorldOrigin, + ECustomPoint +}; + +Vertex rotPoint (const LDObjectList& objs); +void configRotationPoint(); + +// ============================================================================= +namespace Grid +{ + enum Type + { + Coarse, + Medium, + Fine + }; + + enum Config + { + X, + Y, + Z, + Angle + }; + + double snap (double value, const Grid::Config axis); +} + +// ============================================================================= +// Plural expression +template<class T> static inline const char* plural (T n) +{ + return (n != 1) ? "s" : ""; +} + +// ============================================================================= +// Templated clamp +template<class T> static inline T clamp (T a, T min, T max) +{ + return (a > max) ? max : (a < min) ? min : a; +} + +// Templated minimum +template<class T> static inline T min (T a, T b) +{ + return (a < b) ? a : b; +} + +// Templated maximum +template<class T> static inline T max (T a, T b) +{ + return (a > b) ? a : b; +} + +// Templated absolute value +template<class T> static inline T abs (T a) +{ + return (a >= 0) ? a : -a; +} + +template<class T> inline bool isZero (T a) +{ + return abs<T> (a) < 0.0001; +} + +template<class T> inline bool isInteger (T a) +{ + return isZero (a - (int) a); +} + +template<class T> void removeDuplicates (QList<T>& a) +{ + std::sort (a.begin(), a.end()); + a.erase (std::unique (a.begin(), a.end()), a.end()); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/partDownloader.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,539 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "partDownloader.h" +#include "ui_downloadfrom.h" +#include "basics.h" +#include "mainWindow.h" +#include "ldDocument.h" +#include "glRenderer.h" +#include "configDialog.h" + +cfg (String, net_downloadpath, ""); +cfg (Bool, net_guesspaths, true); +cfg (Bool, net_autoclose, true); + +const QString g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/"); + +// ============================================================================= +// +void PartDownloader::staticBegin() +{ + QString 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(); +} + +// ============================================================================= +// +QString PartDownloader::getDownloadPath() +{ + QString path = net_downloadpath; + +#if DIRSLASH_CHAR != '/' + path.replace (DIRSLASH, "/"); +#endif + + return path; +} + +// ============================================================================= +// +PartDownloader::PartDownloader (QWidget* parent) : QDialog (parent) +{ + setInterface (new Ui_DownloadFrom); + interface()->setupUi (this); + interface()->fname->setFocus(); + interface()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); + + setDownloadButton (new QPushButton (tr ("Download"))); + interface()->buttonBox->addButton (downloadButton(), QDialogButtonBox::ActionRole); + getButton (Abort)->setEnabled (false); + + connect (interface()->source, SIGNAL (currentIndexChanged (int)), + this, SLOT (sourceChanged (int))); + connect (interface()->buttonBox, SIGNAL (clicked (QAbstractButton*)), + this, SLOT (buttonClicked (QAbstractButton*))); +} + +// ============================================================================= +// +PartDownloader::~PartDownloader() +{ + delete interface(); +} + +// ============================================================================= +// +QString PartDownloader::getURL() const +{ + const Source src = getSource(); + QString dest; + + switch (src) + { + case PartsTracker: + dest = interface()->fname->text(); + modifyDestination (dest); + return g_unofficialLibraryURL + dest; + + case CustomURL: + return interface()->fname->text(); + } + + // Shouldn't happen + return ""; +} + +// ============================================================================= +// +void PartDownloader::modifyDestination (QString& 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. */ + QString partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*"; + QString 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) interface()->source->currentIndex(); +} + +// ============================================================================= +// +void PartDownloader::sourceChanged (int i) +{ + if (i == CustomURL) + interface()->fileNameLabel->setText (tr ("URL:")); + else + interface()->fileNameLabel->setText (tr ("File name:")); +} + +// ============================================================================= +// +void PartDownloader::buttonClicked (QAbstractButton* btn) +{ + if (btn == getButton (Close)) + { + reject(); + } + elif (btn == getButton (Abort)) + { + setAborted (true); + + for (PartDownloadRequest* req : requests()) + req->abort(); + } + elif (btn == getButton (Download)) + { + QString dest = interface()->fname->text(); + setPrimaryFile (null); + setAborted (false); + + if (getSource() == CustomURL) + dest = basename (getURL()); + + modifyDestination (dest); + + if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest)) + { + const QString overwritemsg = format (tr ("%1 already exists in download directory. Overwrite?"), dest); + if (!confirm (tr ("Overwrite?"), overwritemsg)) + return; + } + + downloadButton()->setEnabled (false); + interface()->progress->setEnabled (true); + interface()->fname->setEnabled (false); + interface()->source->setEnabled (false); + downloadFile (dest, getURL(), true); + getButton (Close)->setEnabled (false); + getButton (Abort)->setEnabled (true); + getButton (Download)->setEnabled (false); + } +} + +// ============================================================================= +// +void PartDownloader::downloadFile (QString dest, QString url, bool primary) +{ + const int row = interface()->progress->rowCount(); + + // Don't download files repeadetly. + if (filesToDownload().indexOf (dest) != -1) + return; + + modifyDestination (dest); + PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); + m_filesToDownload << dest; + m_requests << req; + interface()->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 : requests()) + { + if (!req->isFinished()) + return; + + if (req->state() == PartDownloadRequest::EFailed) + failed = true; + } + + for (PartDownloadRequest* req : requests()) + delete req; + + m_requests.clear(); + + // Update everything now + if (primaryFile() != null) + { + LDDocument::setCurrent (primaryFile()); + 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 downloadButton(); + + case Abort: + return qobject_cast<QPushButton*> (interface()->buttonBox->button (QDialogButtonBox::Abort)); + + case Close: + return qobject_cast<QPushButton*> (interface()->buttonBox->button (QDialogButtonBox::Close)); + } + + return null; +} + +// ============================================================================= +// +PartDownloadRequest::PartDownloadRequest (QString url, QString 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_networkManager (new QNetworkAccessManager), + m_isFirstUpdate (true), + m_isPrimary (primary), + m_filePointer (null) +{ + // Make sure that we have a valid destination. + QString dirpath = dirname (filePath()); + + QDir dir (dirpath); + + if (dir.exists() == false) + { + print ("Creating %1...\n", dirpath); + + if (!dir.mkpath (dirpath)) + critical (format (tr ("Couldn't create the directory %1!"), dirpath)); + } + + setNetworkReply (networkManager()->get (QNetworkRequest (QUrl (url)))); + connect (networkReply(), SIGNAL (finished()), this, SLOT (downloadFinished())); + connect (networkReply(), SIGNAL (readyRead()), this, SLOT (readyRead())); + connect (networkReply(), SIGNAL (downloadProgress (qint64, qint64)), + this, SLOT (downloadProgress (qint64, qint64))); +} + +// ============================================================================= +// +PartDownloadRequest::~PartDownloadRequest() {} + +// ============================================================================= +// +void PartDownloadRequest::updateToTable() +{ + const int labelcol = PartDownloader::PartLabelColumn, + progcol = PartDownloader::ProgressColumn; + QTableWidget* table = prompt()->interface()->progress; + QProgressBar* prog; + + switch (state()) + { + case ERequesting: + case EDownloading: + { + prog = qobject_cast<QProgressBar*> (table->cellWidget (tableRow(), progcol)); + + if (!prog) + { + prog = new QProgressBar; + table->setCellWidget (tableRow(), progcol, prog); + } + + prog->setRange (0, numBytesTotal()); + prog->setValue (numBytesRead()); + } break; + + case EFinished: + case EFailed: + { + const QString text = (state() == 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 (tableRow(), progcol, lb); + } break; + } + + QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (tableRow(), labelcol)); + + if (isFirstUpdate()) + { + lb = new QLabel (format ("<b>%1</b>", destinaton()), table); + table->setCellWidget (tableRow(), 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 (networkReply()->error() != QNetworkReply::NoError) + { + if (isPrimary() && !prompt()->isAborted()) + critical (networkReply()->errorString()); + + setState (EFailed); + } + elif (state() != EFailed) + setState (EFinished); + + setNumBytesRead (numBytesTotal()); + updateToTable(); + + if (filePointer()) + { + filePointer()->close(); + delete filePointer(); + setFilePointer (null); + + if (state() == EFailed) + QFile::remove (filePath()); + } + + if (state() != EFinished) + { + prompt()->checkIfFinished(); + return; + } + + // Try to load this file now. + LDDocument* f = openDocument (filePath(), 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->objects()) + { + LDError* err = dynamic_cast<LDError*> (obj); + + if (err == null || err->fileReferenced().isEmpty()) + continue; + + QString dest = err->fileReferenced(); + prompt()->modifyDestination (dest); + prompt()->downloadFile (dest, g_unofficialLibraryURL + dest, false); + } + + if (isPrimary()) + { + addRecentFile (filePath()); + prompt()->setPrimaryFile (f); + } + + prompt()->checkIfFinished(); +} + +// ============================================================================= +// +void PartDownloadRequest::downloadProgress (int64 recv, int64 total) +{ + setNumBytesRead (recv); + setNumBytesTotal (total); + setState (EDownloading); + updateToTable(); +} + +// ============================================================================= +// +void PartDownloadRequest::readyRead() +{ + if (state() == EFailed) + return; + + if (filePointer() == null) + { + m_filePath.replace ("\\", "/"); + + // We have already asked the user whether we can overwrite so we're good + // to go here. + setFilePointer (new QFile (filePath().toLocal8Bit())); + + if (!filePointer()->open (QIODevice::WriteOnly)) + { + critical (format (tr ("Couldn't open %1 for writing: %2"), filePath(), strerror (errno))); + setState (EFailed); + networkReply()->abort(); + updateToTable(); + prompt()->checkIfFinished(); + return; + } + } + + filePointer()->write (networkReply()->readAll()); +} + +// ============================================================================= +// +bool PartDownloadRequest::isFinished() const +{ + return state() == EFinished || state() == EFailed; +} + +// ============================================================================= +// +void PartDownloadRequest::abort() +{ + networkReply()->abort(); +} + +// ============================================================================= +// +DEFINE_ACTION (DownloadFrom, 0) +{ + PartDownloader::staticBegin(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/partDownloader.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,127 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QDialog> +#include "main.h" +#include "basics.h" + +class LDDocument; +class QFile; +class PartDownloadRequest; +class Ui_DownloadFrom; +class QNetworkAccessManager; +class QNetworkRequest; +class QNetworkReply; +class QAbstractButton; + +// ============================================================================= +// +class PartDownloader : public QDialog +{ + public: + enum Source + { + PartsTracker, + CustomURL, + }; + + enum Button + { + Download, + Abort, + Close + }; + + enum TableColumn + { + PartLabelColumn, + ProgressColumn, + }; + + using RequestList = QList<PartDownloadRequest*>; + + Q_OBJECT + PROPERTY (public, LDDocument*, primaryFile, setPrimaryFile, STOCK_WRITE) + PROPERTY (public, bool, isAborted, setAborted, STOCK_WRITE) + PROPERTY (private, Ui_DownloadFrom*, interface, setInterface, STOCK_WRITE) + PROPERTY (private, QStringList, filesToDownload, setFilesToDownload, STOCK_WRITE) + PROPERTY (private, RequestList, requests, setRequests, STOCK_WRITE) + PROPERTY (private, QPushButton*, downloadButton, setDownloadButton, STOCK_WRITE) + + public: + explicit PartDownloader (QWidget* parent = null); + virtual ~PartDownloader(); + + void downloadFile (QString dest, QString url, bool primary); + QPushButton* getButton (Button i); + QString getURL() const; + Source getSource() const; + void modifyDestination (QString& dest) const; + + static QString getDownloadPath(); + static void staticBegin(); + + public slots: + void buttonClicked (QAbstractButton* btn); + void checkIfFinished(); + void sourceChanged (int i); +}; + +// ============================================================================= +// +class PartDownloadRequest : public QObject +{ + public: + enum EState + { + ERequesting, + EDownloading, + EFinished, + EFailed, + }; + + Q_OBJECT + PROPERTY (public, int, tableRow, setTableRow, STOCK_WRITE) + PROPERTY (private, EState, state, setState, STOCK_WRITE) + PROPERTY (private, PartDownloader*, prompt, setPrompt, STOCK_WRITE) + PROPERTY (private, QString, url, setURL, STOCK_WRITE) + PROPERTY (private, QString, destinaton, setDestination, STOCK_WRITE) + PROPERTY (private, QString, filePath, setFilePath, STOCK_WRITE) + PROPERTY (private, QNetworkAccessManager*, networkManager, setNetworkManager, STOCK_WRITE) + PROPERTY (private, QNetworkReply*, networkReply, setNetworkReply, STOCK_WRITE) + PROPERTY (private, bool, isFirstUpdate, setFirstUpdate, STOCK_WRITE) + PROPERTY (private, int64, numBytesRead, setNumBytesRead, STOCK_WRITE) + PROPERTY (private, int64, numBytesTotal, setNumBytesTotal, STOCK_WRITE) + PROPERTY (private, bool, isPrimary, setPrimary, STOCK_WRITE) + PROPERTY (private, QFile*, filePointer, setFilePointer, STOCK_WRITE) + + public: + explicit PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent); + PartDownloadRequest (const PartDownloadRequest&) = delete; + virtual ~PartDownloadRequest(); + void updateToTable(); + bool isFinished() const; + void operator= (const PartDownloadRequest&) = delete; + + public slots: + void downloadFinished(); + void readyRead(); + void downloadProgress (qint64 recv, qint64 total); + void abort(); +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/primitives.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,703 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "ldDocument.h" +#include "mainWindow.h" +#include "primitives.h" +#include "ui_makeprim.h" +#include "miscallenous.h" +#include "colors.h" + +QList<PrimitiveCategory*> g_PrimitiveCategories; +QList<Primitive> g_primitives; +static PrimitiveScanner* g_activeScanner = null; +PrimitiveCategory* g_unmatched = null; + +extern_cfg (String, ld_defaultname); +extern_cfg (String, ld_defaultuser); +extern_cfg (Int, ld_defaultlicense); + +static const QStringList g_radialNameRoots = +{ + "edge", + "cyli", + "disc", + "ndis", + "ring", + "con" +}; + +PrimitiveScanner* getActivePrimitiveScanner() +{ + return g_activeScanner; +} + +// ============================================================================= +// +void loadPrimitives() +{ + PrimitiveCategory::loadCategories(); + + // Try to load prims.cfg + QFile conf (Config::filepath ("prims.cfg")); + + if (conf.open (QIODevice::ReadOnly) == false) + { + // No prims.cfg, build it + PrimitiveScanner::start(); + } + else + { + while (conf.atEnd() == false) + { + QString line = conf.readLine(); + + if (line.endsWith ("\n")) + line.chop (1); + + 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(); + print ("%1 primitives loaded.\n", g_primitives.size()); + } +} + +// ============================================================================= +// +static void recursiveGetFilenames (QDir dir, QList<QString>& fnames) +{ + QFileInfoList flist = dir.entryInfoList (QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + + for (const QFileInfo& info : flist) + { + if (info.isDir()) + recursiveGetFilenames (QDir (info.absoluteFilePath()), fnames); + else + fnames << info.absoluteFilePath(); + } +} + +// ============================================================================= +// +PrimitiveScanner::PrimitiveScanner (QObject* parent) : + QObject (parent), + m_i (0) +{ + g_activeScanner = this; + QDir dir (LDPaths::prims()); + assert (dir.exists()); + m_baselen = dir.absolutePath().length(); + recursiveGetFilenames (dir, m_files); + emit starting (m_files.size()); + print ("Scanning primitives..."); +} + +// ============================================================================= +// +PrimitiveScanner::~PrimitiveScanner() +{ + g_activeScanner = null; +} + +// ============================================================================= +// +void PrimitiveScanner::work() +{ + int j = min (m_i + 100, m_files.size()); + + for (; m_i < j; ++m_i) + { + QString fname = m_files[m_i]; + QFile f (fname); + + if (!f.open (QIODevice::ReadOnly)) + continue; + + Primitive info; + info.name = fname.mid (m_baselen + 1); // make full path relative + info.name.replace ('/', '\\'); // use DOS backslashes, they're expected + info.category = null; + QByteArray titledata = f.readLine(); + + if (titledata != QByteArray()) + info.title = QString::fromUtf8 (titledata); + + info.title = info.title.simplified(); + + if (Q_LIKELY (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 + QString path = Config::filepath ("prims.cfg"); + QFile conf (path); + + if (!conf.open (QIODevice::WriteOnly | QIODevice::Text)) + critical (format ("Couldn't write primitive list %1: %2", + path, conf.errorString())); + else + { + for (Primitive& info : m_prims) + fprint (conf, "%1 %2\r\n", info.name, info.title); + + conf.close(); + } + + g_primitives = m_prims; + PrimitiveCategory::populateCategories(); + print ("%1 primitives scanned", g_primitives.size()); + g_activeScanner = 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 PrimitiveScanner::start() +{ + if (g_activeScanner) + return; + + PrimitiveScanner* scanner = new PrimitiveScanner; + scanner->work(); +} + +// ============================================================================= +// +PrimitiveCategory::PrimitiveCategory (QString 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.category = 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.category = cat; + break; + } + } + + // Drop out if a category was decided on. + if (prim.category != null) + break; + } + + // If there was a match, add the primitive to the category. + // Otherwise, add it to the list of unmatched primitives. + if (prim.category != null) + prim.category->prims << prim; + else + g_unmatched->prims << prim; + } +} + +// ============================================================================= +// +void PrimitiveCategory::loadCategories() +{ + for (PrimitiveCategory* cat : g_PrimitiveCategories) + delete cat; + + g_PrimitiveCategories.clear(); + QString path = Config::dirpath() + "primregexps.cfg"; + + if (!QFile::exists (path)) + path = ":/data/primitive-categories.cfg"; + + QFile f (path); + + if (!f.open (QIODevice::ReadOnly)) + { + critical (format (QObject::tr ("Failed to open primitive categories: %1"), f.errorString())); + return; + } + + PrimitiveCategory* cat = null; + + while (f.atEnd() == false) + { + QString line = f.readLine(); + int colon; + + if (line.endsWith ("\n")) + line.chop (1); + + 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) + { + QString cmd = line.left (colon); + RegexType type = EFilenameRegex; + + if (cmd == "f") + type = EFilenameRegex; + elif (cmd == "t") + type = ETitleRegex; + else + { + print (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 + print ("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; + f.close(); +} + +// ============================================================================= +// +bool PrimitiveCategory::isValidToInclude() +{ + if (regexes.isEmpty()) + { + print (tr ("Warning: category \"%1\" left without patterns"), name()); + deleteLater(); + return false; + } + + return true; +} + +// ============================================================================= +// +bool isPrimitiveLoaderBusy() +{ + return g_activeScanner != 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)); + } +} + +// ============================================================================= +// +LDObjectList makePrimitive (PrimitiveType type, int segs, int divs, int num) +{ + LDObjectList 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 QString 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"; +} + +// ============================================================================= +// +QString 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 + QString prefix = (divs == g_lores) ? "" : format ("%1/", divs); + QString frac = format ("%1-%2", numer, denom); + QString root = g_radialNameRoots[type]; + QString numstr = (type == Ring || type == Cone) ? format ("%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 + QString frac = QString::number ((float) segs / divs); + QString name = radialFileName (type, segs, divs, num); + QString descr; + + // Ensure that there's decimals, even if they're 0. + if (frac.indexOf (".") == -1) + frac += ".0"; + + if (type == Ring || type == Cone) + { + QString spacing = + (num < 10) ? " " : + (num < 100) ? " " : ""; + + descr = format ("%1 %2%3 x %4", primitiveTypeName (type), spacing, num, frac); + } + else + descr = format ("%1 %2", primitiveTypeName (type), frac); + + // Prepend "Hi-Res" if 48/ primitive. + if (divs == g_hires) + descr.insert (0, "Hi-Res "); + + LDDocument* f = new LDDocument; + f->setDefaultName (name); + + QString author = APPNAME; + QString license = ""; + + if (ld_defaultname.isEmpty() == false) + { + license = getLicenseText (ld_defaultlicense); + author = format ("%1 [%2]", ld_defaultname, ld_defaultuser); + } + + f->addObjects ( + { + new LDComment (descr), + new LDComment (format ("Name: %1", name)), + new LDComment (format ("Author: %1", author)), + new LDComment (format ("!LDRAW_ORG Unofficial_%1Primitive", divs == g_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) +{ + QString 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 ? g_hires : g_lores); + + // If the current value is 16 and we switch to hi-res, default the + // spinbox to 48. + if (on && ui->sb_segs->value() == g_lores) + ui->sb_segs->setValue (g_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() ? g_hires : g_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/primitives.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,130 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include "main.h" +#include "basics.h" +#include <QRegExp> +#include <QDialog> + +class LDDocument; +class Ui_MakePrimUI; +class PrimitiveCategory; +struct Primitive +{ + QString name, + title; + PrimitiveCategory* category; +}; + +class PrimitiveCategory : public QObject +{ + Q_OBJECT + PROPERTY (public, QString, name, setName, STOCK_WRITE) + + public: + enum RegexType + { + EFilenameRegex, + ETitleRegex + }; + + struct RegexEntry + { + QRegExp regex; + RegexType type; + }; + + QList<RegexEntry> regexes; + QList<Primitive> prims; + + explicit PrimitiveCategory (QString name, QObject* parent = 0); + bool isValidToInclude(); + + static void loadCategories(); + static void populateCategories(); +}; + +// ============================================================================= +// +// PrimitiveScanner +// +// Worker object that scans the primitives folder for primitives and +// builds an index of them. +// +class PrimitiveScanner : public QObject +{ + Q_OBJECT + + public: + explicit PrimitiveScanner (QObject* parent = 0); + virtual ~PrimitiveScanner(); + static void start(); + + public slots: + void work(); + + signals: + void starting (int num); + void workDone(); + void update (int i); + + private: + QList<Primitive> m_prims; + QStringList m_files; + int m_i; + int m_baselen; +}; + +extern QList<PrimitiveCategory*> g_PrimitiveCategories; + +void loadPrimitives(); +PrimitiveScanner* getActivePrimitiveScanner(); + +enum PrimitiveType +{ + Circle, + Cylinder, + Disc, + DiscNeg, + Ring, + Cone, +}; + +// ============================================================================= +class PrimitivePrompt : public QDialog +{ + Q_OBJECT + + public: + explicit PrimitivePrompt (QWidget* parent = null, Qt::WindowFlags f = 0); + virtual ~PrimitivePrompt(); + Ui_MakePrimUI* ui; + + public slots: + void hiResToggled (bool on); +}; + +void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines); +LDDocument* generatePrimitive (PrimitiveType type, int segs, int divs, int num); + +// Gets a primitive by the given specs. If the primitive cannot be found, it will +// be automatically generated. +LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num); + +QString radialFileName (PrimitiveType type, int segs, int divs, int num);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/radioGroup.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,194 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 "radioGroup.h" + +// ============================================================================= +// +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, QList<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(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/radioGroup.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,92 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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/>. + */ + +#pragma once +#include <QGroupBox> +#include <QSpinBox> +#include <map> +#include "main.h" +#include "basics.h" + +class QIcon; +class QCheckBox; +class QButtonGroup; +class QBoxLayout; +class QRadioButton; + +// ============================================================================= +// RadioGroup +// +// Convenience widget - is a groupbox of radio buttons. +// ============================================================================= +class RadioGroup : public QGroupBox +{ + Q_OBJECT + + public: + typedef QList<QRadioButton*>::Iterator Iterator; + + explicit RadioGroup() + { + init (Qt::Vertical); + } + + explicit RadioGroup (QWidget* parent = null) : QGroupBox (parent) + { + init (Qt::Vertical); + } + + explicit RadioGroup (const QString& title, QWidget* parent = null); + explicit RadioGroup (const QString& title, QList<char const*> entries, int const defaultId, + const Qt::Orientation orient = Qt::Vertical, QWidget* parent = null); + + void addButton (const char* entry); + void addButton (QRadioButton* button); + Iterator begin(); + Iterator end(); + void init (Qt::Orientation orient); + bool isChecked (int n) const; + void rowBreak(); + void setCurrentRow (int row); + void setValue (int val); + int value() const; + + QRadioButton* operator[] (int n) const; + RadioGroup& operator<< (QRadioButton* button); + RadioGroup& operator<< (const char* entry); + + signals: + void buttonPressed (int btn); + void buttonReleased (int btn); + void valueChanged (int val); + + private: + QList<QRadioButton*> m_objects; + QList<QBoxLayout*> m_layouts; + QBoxLayout* m_coreLayout; + QBoxLayout* m_currentLayout; + bool m_vert; + int m_curId, m_defId, m_oldId; + QButtonGroup* m_buttonGroup; + + Q_DISABLE_COPY (RadioGroup) + + private slots: + void slot_buttonPressed (int btn); + void slot_buttonReleased (int btn); +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/version.cc Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,57 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 <stdio.h> +#include <string.h> +#include "version.h" +#include "gitinfo.h" + +char gVersionString[64] = {'\0'}; +char gFullVersionString[256] = {'\0'}; + +// ============================================================================= +// +const char* versionString() +{ + if (gVersionString[0] == '\0') + { +#if VERSION_PATCH == 0 + sprintf (gVersionString, "%d.%d", VERSION_MAJOR, VERSION_MINOR); +#else + sprintf (gVersionString, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); +#endif // VERSION_PATCH + } + + return gVersionString; +} + +// ============================================================================= +// +const char* fullVersionString() +{ + if (gFullVersionString[0] == '\0') + { +#if BUILD_ID != BUILD_RELEASE + strcpy (gFullVersionString, GIT_DESCRIPTION); +#else + sprintf (gFullVersionString, "v%s", versionString()); +#endif + } + + return gFullVersionString; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/version.h Sat Mar 29 05:26:10 2014 +0200 @@ -0,0 +1,55 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 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 version.h +//! Contains macros related to application name and version. + +#pragma once + +//! The application name. +#define APPNAME "LDForge" + +//! The unix-style name of the application. used in filenames +#define UNIXNAME "ldforge" + +//! The major version number. +#define VERSION_MAJOR 0 + +//! The minor version number. +#define VERSION_MINOR 3 + +//! The patch level version number. +#define VERSION_PATCH 0 + +//! The build ID, use either BUILD_INTERNAL or BUILD_RELEASE +#define BUILD_ID BUILD_INTERNAL + +//! The build code for internal builds +#define BUILD_INTERNAL 0 + +//! The build code for release builds. +#define BUILD_RELEASE 1 + +// ============================================= +#ifdef DEBUG +# undef RELEASE +#endif // DEBUG + +#ifdef RELEASE +# undef DEBUG +#endif // RELEASE