# HG changeset patch # User Santeri Piippo # Date 1390262607 -7200 # Node ID b75c6cce02e2f5149ebffdb732f28cadc6447c1b # Parent 6b13e4c2e97b9af1e70e85b4028cef74169bba64 - refactored filenames diff -r 6b13e4c2e97b -r b75c6cce02e2 ldforge.pro --- a/ldforge.pro Mon Jan 20 23:44:22 2014 +0200 +++ b/ldforge.pro Tue Jan 21 02:03:27 2014 +0200 @@ -9,7 +9,7 @@ RCC_DIR = ./build_shared/ MOC_DIR = ./build_shared/ UI_DIR = ./build_shared/ -SOURCES = src/*.cc +SOURCES = src/*.cc src/actions/*.cc HEADERS = src/*.h src/misc/*.h FORMS = ui/*.ui QT += opengl network diff -r 6b13e4c2e97b -r b75c6cce02e2 src/AddObjectDialog.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/AddObjectDialog.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,444 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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" +#include "moc_AddObjectDialog.cpp" + +// ============================================================================= +// ----------------------------------------------------------------------------- +class SubfileListItem : public QTreeWidgetItem +{ + PROPERTY (public, Primitive*, PrimitiveInfo, NO_OPS, STOCK_WRITE) + + public: + SubfileListItem (QTreeWidgetItem* parent, Primitive* info) : + QTreeWidgetItem (parent), + m_PrimitiveInfo (info) {} + + SubfileListItem (QTreeWidget* parent, Primitive* info) : + QTreeWidgetItem (parent), + m_PrimitiveInfo (info) {} +}; + +// ============================================================================= +// ----------------------------------------------------------------------------- +AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) : + QDialog (parent) +{ + setlocale (LC_ALL, "C"); + + int coordCount = 0; + QString typeName = LDObject::typeName (type); + + switch (type) + { + case LDObject::EComment: + { + le_comment = new QLineEdit; + + if (obj) + le_comment->setText (static_cast (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::statements[i]); + } + + if (obj) + rb_bfcType->setValue ( (int) static_cast (obj)->type); + } 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->getName()); + QList subfileItems; + + for (Primitive& prim : cat->prims) + { + SubfileListItem* item = new SubfileListItem (parentItem, &prim); + item->setText (0, fmt ("%1 - %2", prim.name, prim.title)); + subfileItems << item; + + // If this primitive is the one the current object points to, + // select it by default + if (obj && static_cast (obj)->getFileInfo()->getName() == prim.name) + tw_subfileList->setCurrentItem (item); + } + + tw_subfileList->addTopLevelItem (parentItem); + } + + connect (tw_subfileList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_subfileTypeChanged())); + lb_subfileName = new QLabel ("File:"); + le_subfileName = new QLineEdit; + le_subfileName->setFocus(); + + if (obj) + { + LDSubfile* ref = static_cast (obj); + le_subfileName->setText (ref->getFileInfo()->getName()); + } + } break; + + default: + { + critical (fmt ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName)); + } return; + } + + QPixmap icon = getIcon (fmt ("add-%1", typeName)); + LDObject* defaults = LDObject::getDefault (type); + + lb_typeIcon = new QLabel; + lb_typeIcon->setPixmap (icon); + + // Show a color edit dialog for the types that actually use the color + if (defaults->isColored()) + { + if (obj != null) + colnum = obj->getColor(); + else + colnum = (type == LDObject::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->getVertex (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 (obj); + + QLabel* lb_matrix = new QLabel ("Matrix:"); + le_matrix = new QLineEdit; + // le_matrix->setValidator (new QDoubleValidator); + Matrix defaultMatrix = g_identity; + + if (mo) + { + for_axes (ax) + dsb_coords[ax]->setValue (mo->getPosition()[ax]); + + defaultMatrix = mo->getTransform(); + } + + le_matrix->setText (defaultMatrix.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 (fmt (tr ("Edit %1"), typeName)); + + setWindowIcon (icon); + defaults->deleteSelf(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddObjectDialog::setButtonBackground (QPushButton* button, int colnum) +{ + LDColor* col = getColor (colnum); + + button->setIcon (getIcon ("palette")); + button->setAutoFillBackground (true); + + if (col) + button->setStyleSheet (fmt ("background-color: %1", col->hexcode)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString AddObjectDialog::currentSubfileName() +{ + SubfileListItem* item = static_cast (tw_subfileList->currentItem()); + + if (item->getPrimitiveInfo() == null) + return ""; // selected a heading + + return item->getPrimitiveInfo()->name; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddObjectDialog::slot_colorButtonClicked() +{ + ColorSelector::selectColor (colnum, colnum, this); + setButtonBackground (pb_color, colnum); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddObjectDialog::slot_subfileTypeChanged() +{ + QString name = currentSubfileName(); + + if (name.length() > 0) + le_subfileName->setText (name); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +template static T* initObj (LDObject*& obj) +{ + if (obj == null) + obj = new T; + + return static_cast (obj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddObjectDialog::staticDialog (const LDObject::Type type, LDObject* obj) +{ + setlocale (LC_ALL, "C"); + + // FIXME: Redirect to Edit Raw + if (obj && obj->getType() == 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 || obj->getType() == 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 (obj); + comm->text = 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 (obj); + bfc->type = (LDBFC::Type) dlg.rb_bfcType->value(); + } break; + + case LDObject::EVertex: + { + LDVertex* vert = initObj (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 (fmt ("Couldn't open `%1': %2", name, strerror (errno))); + return; + } + + LDSubfile* ref = initObj (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(); +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/AddObjectDialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/AddObjectDialog.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,73 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_ADDOBJECTDIALOG_H +#define LDFORGE_ADDOBJECTDIALOG_H + +#include +#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(); +}; + +#endif // LDFORGE_ADDOBJECTDIALOG_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/ColorSelector.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ColorSelector.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,211 @@ +/* + * 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 . + * ===================================================================== + * + * colorSelectDialog.cpp: Color selector box. + */ + +#include +#include +#include +#include + +#include "Main.h" +#include "MainWindow.h" +#include "ColorSelector.h" +#include "Colors.h" +#include "Configuration.h" +#include "Misc.h" +#include "ui_colorsel.h" +#include "moc_ColorSelector.cpp" + +static const int g_numColumns = 16; +static const int g_squareSize = 32; + +extern_cfg (String, gl_maincolor); +extern_cfg (Float, gl_maincolor_alpha); + +// ============================================================================= +// ----------------------------------------------------------------------------- +ColorSelector::ColorSelector (int defval, QWidget* parent) : QDialog (parent) +{ + // Remove the default color if it's invalid + if (!getColor (defval)) + defval = -1; + + m_firstResize = true; + ui = new Ui_ColorSelUI; + ui->setupUi (this); + + m_scene = new QGraphicsScene; + ui->viewport->setScene (m_scene); + setSelection (getColor (defval)); + + // not really an icon but eh + m_scene->setBackgroundBrush (getIcon ("checkerboard")); + drawScene(); + + int width = viewportWidth(); + ui->viewport->setMinimumWidth (width); + ui->viewport->setMaximumWidth (width); + + drawColorInfo(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +ColorSelector::~ColorSelector() +{ + delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ColorSelector::drawScene() +{ + const int numCols = g_numColumns; + const int square = g_squareSize; + const int g_maxHeight = (numRows() * square); + QRect sceneRect (0, 0, viewportWidth(), g_maxHeight); + + m_scene->setSceneRect (sceneRect); + ui->viewport->setSceneRect (sceneRect); + + const double penWidth = 1.0f; + + // Draw the color rectangles. + m_scene->clear(); + + for (int i = 0; i < MAX_COLORS; ++i) + { + LDColor* info = ::getColor (i); + + if (!info) + continue; + + const double x = (i % numCols) * square; + const double y = (i / numCols) * square; + const double w = square - (penWidth / 2); + + QColor col = info->faceColor; + + if (i == maincolor) + { + // Use the user preferences for main color here + col = QColor (gl_maincolor); + col.setAlpha (gl_maincolor_alpha * 255.0f); + } + + QPen pen (info->edgeColor, penWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + m_scene->addRect (x, y, w, w, pen, col); + QGraphicsTextItem* numtext = m_scene->addText (fmt ("%1", i)); + numtext->setDefaultTextColor ( (luma (col) < 80) ? Qt::white : Qt::black); + numtext->setPos (x, y); + + if (getSelection() && i == getSelection()->index) + { + auto curspic = m_scene->addPixmap (getIcon ("colorcursor")); + curspic->setPos (x, y); + } + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int ColorSelector::numRows() const +{ + return (MAX_COLORS / g_numColumns); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int ColorSelector::viewportWidth() const +{ + return g_numColumns * g_squareSize + 21; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ColorSelector::drawColorInfo() +{ + if (!getSelection()) + { + ui->colorLabel->setText ("---"); + return; + } + + ui->colorLabel->setText (fmt ("%1 - %2", getSelection()->index, getSelection()->name)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ColorSelector::resizeEvent (QResizeEvent* ev) +{ + // If this is the first resize, check if we need to scroll down to see the + // currently selected color. We cannot do this in the constructor because the + // height is not set properly there. + if (m_firstResize) + { + int visibleColors = (ui->viewport->height() / g_squareSize) * g_numColumns; + + if (getSelection() && getSelection()->index >= visibleColors) + { + int y = (getSelection()->index / g_numColumns) * g_squareSize; + ui->viewport->verticalScrollBar()->setValue (y); + } + + m_firstResize = false; + } + + (void) ev; + drawScene(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ColorSelector::mousePressEvent (QMouseEvent* event) +{ + QPointF scenepos = ui->viewport->mapToScene (event->pos()); + + int x = (scenepos.x() - (g_squareSize / 2)) / g_squareSize; + int y = (scenepos.y() - (g_squareSize / 2)) / g_squareSize; + int idx = (y * g_numColumns) + x; + + LDColor* col = ::getColor (idx); + + if (!col) + return; + + setSelection (col); + drawScene(); + drawColorInfo(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool ColorSelector::selectColor (int& val, int defval, QWidget* parent) +{ + ColorSelector dlg (defval, parent); + + if (dlg.exec() && dlg.getSelection() != null) + { + val = dlg.getSelection()->index; + return true; + } + + return false; +} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/ColorSelector.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ColorSelector.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,54 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_COLORSELECTOR_H +#define LDFORGE_COLORSELECTOR_H + +#include +#include "Main.h" + +class LDColor; +class Ui_ColorSelUI; +class QGraphicsScene; + +class ColorSelector : public QDialog +{ + Q_OBJECT + PROPERTY (private, LDColor*, Selection, NO_OPS, 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(); +}; + +#endif // LDFORGE_COLORSELECTOR_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Colors.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Colors.cc Tue Jan 21 02:03:27 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 . + * ===================================================================== + * + * colors.cpp: LDraw color management. LDConfig.ldr parsing is not here! + * TODO: Make LDColor more full-fledged, add support for direct colors. + * TODO: g_LDColors should probably be a map. + */ + +#include "Main.h" +#include "Colors.h" +#include "Document.h" +#include "Misc.h" +#include "MainWindow.h" +#include "LDConfig.h" +#include + +static LDColor* g_LDColors[MAX_COLORS]; + +// ============================================================================= +// ----------------------------------------------------------------------------- +void initColors() +{ + LDColor* col; + log ("%1: initializing color information.\n", __func__); + + // Always make sure there's 16 and 24 available. They're special like that. + col = new LDColor; + col->faceColor = col->hexcode = "#AAAAAA"; + col->edgeColor = Qt::black; + g_LDColors[maincolor] = col; + + col = new LDColor; + col->faceColor = col->edgeColor = col->hexcode = "#000000"; + g_LDColors[edgecolor] = col; + + parseLDConfig(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDColor* getColor (int colnum) +{ + // Check bounds + if (colnum < 0 || colnum >= MAX_COLORS) + return null; + + return g_LDColors[colnum]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void setColor (int colnum, LDColor* col) +{ + if (colnum < 0 || colnum >= MAX_COLORS) + return; + + g_LDColors[colnum] = col; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int luma (QColor& col) +{ + return (0.2126f * col.red()) + + (0.7152f * col.green()) + + (0.0722f * col.blue()); +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Colors.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Colors.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,46 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_COLORS_H +#define LDFORGE_COLORS_H + +#include +#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; + +#endif // LDFORGE_COLORS_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Configuration.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Configuration.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,178 @@ +/* + * 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 . + * ===================================================================== + * + * config.cpp: Configuration management. I don't like how unsafe QSettings + * is so this implements a type-safer and identifer-safer wrapping system of + * configuration variables. QSettings is used underlyingly, this is a matter + * of interface. + */ + +#include +#include +#include +#include +#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 g_configsByName; +static QList 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(); + log ("config::load: Loading configuration file from %1\n", settings->fileName()); + + for (Config* cfg : g_configPointers) + { + if (!cfg) + break; + + QVariant val = settings->value (cfg->getName(), cfg->getDefaultAsVariant()); + cfg->loadFromVariant (val); + g_configsByName[cfg->getName()] = cfg; + g_configs << cfg; + } + + settings->deleteLater(); + return true; +} + +// ============================================================================= +// Save the configuration to disk +// ----------------------------------------------------------------------------- +bool Config::save() +{ + QSettings* settings = getSettingsObject(); + log ("Saving configuration to %1...\n", settings->fileName()); + + for (Config* cfg : g_configs) + { + if (!cfg->isDefault()) + settings->setValue (cfg->getName(), cfg->toVariant()); + else + settings->remove (cfg->getName()); + } + + settings->sync(); + 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 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 (cfg); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +#undef IMPLEMENT_CONFIG + +#define IMPLEMENT_CONFIG(NAME) \ + NAME##Config* NAME##Config::getByName (QString name) \ + { \ + return getConfigByName (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) diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Configuration.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Configuration.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,192 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_CONFIG_H +#define LDFORGE_CONFIG_H + +#include "PropertyMacro.h" +#include "Types.h" + +// ============================================================================= +#include +#include +#include +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, STR_OPS, 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; + 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(); \ + } \ + \ + virtual QVariant toVariant() const \ + { \ + return QVariant::fromValue (*m_valueptr); \ + } \ + \ + virtual QVariant getDefaultAsVariant() const \ + { \ + return QVariant::fromValue (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) +}; + +#endif // LDFORGE_CONFIG_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/ConfigurationDialog.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ConfigurationDialog.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,800 @@ +/* + * 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 . + * ===================================================================== + * + * configDialog.cpp: Settings dialog and everything related to it. + * Actual configuration core is in config.cpp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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" +#include "moc_ConfigurationDialog.cpp" + +extern_cfg (String, gl_bgcolor); +extern_cfg (String, gl_maincolor); +extern_cfg (Bool, lv_colorize); +extern_cfg (Bool, gl_colorbfc); +extern_cfg (Float, gl_maincolor_alpha); +extern_cfg (Int, gl_linethickness); +extern_cfg (String, gui_colortoolbar); +extern_cfg (Bool, edit_schemanticinline); +extern_cfg (Bool, gl_blackedges); +extern_cfg (Bool, gl_aa); +extern_cfg (Bool, gui_implicitfiles); +extern_cfg (String, net_downloadpath); +extern_cfg (Bool, net_guesspaths); +extern_cfg (Bool, net_autoclose); +extern_cfg (Bool, gl_logostuds); +extern_cfg (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()) + { + 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 : initlist ({xlabel, ylabel, zlabel, anglabel})) + { + label->setAlignment (Qt::AlignCenter); + gridlayout->addWidget (label, 0, i++); + } + + for (int i = 0; i < g_NumGrids; ++i) + { + // Icon + lb_gridIcons[i] = new QLabel; + lb_gridIcons[i]->setPixmap (getIcon (fmt ("grid-%1", QString (g_GridInfo[i].name).toLower()))); + + // Text label + lb_gridLabels[i] = new QLabel (fmt ("%1:", g_GridInfo[i].name)); + + QHBoxLayout* labellayout = new QHBoxLayout; + labellayout->addWidget (lb_gridIcons[i]); + labellayout->addWidget (lb_gridLabels[i]); + gridlayout->addLayout (labellayout, i + 1, 0); + + // Add the widgets + for (int j = 0; j < 4; ++j) + { + dsb_gridData[i][j] = new QDoubleSpinBox; + + // 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->updateToolBars(); + g_win->updateDocumentList(); +} + +// ============================================================================= +// A dialog button was clicked +// ----------------------------------------------------------------------------- +void ConfigDialog::buttonClicked (QAbstractButton* button) +{ + typedef QDialogButtonBox QDDB; + QDialogButtonBox* dbb = ui->buttonBox; + + if (button == dbb->button (QDDB::Ok)) + { + applySettings(); + accept(); + } elif (button == dbb->button (QDDB::Apply)) + { + applySettings(); + } elif (button == dbb->button (QDDB::Cancel)) + { + reject(); + } +} + +// ============================================================================= +// Update the list of color toolbar items in the quick color tab. +// ----------------------------------------------------------------------------- +void ConfigDialog::updateQuickColorList (LDQuickColor* sel) +{ + for (QListWidgetItem * item : quickColorItems) + delete item; + + quickColorItems.clear(); + + // Init table items + for (LDQuickColor& entry : quickColors) + { + QListWidgetItem* item = new QListWidgetItem; + + if (entry.isSeparator()) + { + item->setText ("--------"); + item->setIcon (getIcon ("empty")); + } + else + { + LDColor* col = entry.getColor(); + + if (col == null) + { + item->setText ("[[unknown color]]"); + item->setIcon (getIcon ("error")); + } + else + { + item->setText (col->name); + item->setIcon (makeColorIcon (col, 16)); + } + } + + ui->quickColorList->addItem (item); + quickColorItems << item; + + if (sel && &entry == sel) + { + ui->quickColorList->setCurrentItem (item); + ui->quickColorList->scrollToItem (item); + } + } +} + +// ============================================================================= +// Quick colors: add or edit button was clicked. +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_setColor() +{ + LDQuickColor* entry = null; + QListWidgetItem* item = null; + const bool isNew = static_cast (sender()) == ui->quickColor_add; + + if (isNew == false) + { + item = getSelectedQuickColor(); + + if (!item) + return; + + int i = getItemRow (item, quickColorItems); + entry = &quickColors[i]; + + if (entry->isSeparator() == true) + return; // don't color separators + } + + int defval = entry ? entry->getColor()->index : -1; + int val; + + if (ColorSelector::selectColor (val, defval, this) == false) + return; + + if (entry) + entry->setColor (getColor (val)); + else + { + LDQuickColor entry (getColor (val), null); + + item = getSelectedQuickColor(); + int idx = (item) ? getItemRow (item, quickColorItems) + 1 : quickColorItems.size(); + + quickColors.insert (idx, entry); + entry = quickColors[idx]; + } + + updateQuickColorList (entry); +} + +// ============================================================================= +// Remove a quick color +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_delColor() +{ + if (ui->quickColorList->selectedItems().isEmpty()) + return; + + QListWidgetItem* item = ui->quickColorList->selectedItems() [0]; + quickColors.removeAt (getItemRow (item, quickColorItems)); + updateQuickColorList(); +} + +// ============================================================================= +// Move a quick color up/down +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_moveColor() +{ + const bool up = (static_cast (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 (fmt ("background-color: %1", value)); +} + +// ============================================================================= +// Finds the given list widget item in the list of widget items given. +// ----------------------------------------------------------------------------- +int ConfigDialog::getItemRow (QListWidgetItem* item, QList& 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 ConfigDialog::getShortcutSelection() +{ + QList out; + + for (QListWidgetItem* entry : ui->shortcutsList->selectedItems()) + out << static_cast (entry); + + return out; +} + +// ============================================================================= +// Edit the shortcut of a given action. +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_setShortcut() +{ + QList sel = getShortcutSelection(); + + if (sel.size() < 1) + return; + + ShortcutListItem* item = sel[0]; + + if (KeySequenceDialog::staticDialog (item->getKeyConfig(), this)) + setShortcutText (item); +} + +// ============================================================================= +// Reset a shortcut to defaults +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_resetShortcut() +{ + QList sel = getShortcutSelection(); + + for (ShortcutListItem* item : sel) + { + item->getKeyConfig()->reset(); + setShortcutText (item); + } +} + +// ============================================================================= +// Remove the shortcut of an action. +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_clearShortcut() +{ + QList sel = getShortcutSelection(); + + for (ShortcutListItem* item : sel) + { + item->getKeyConfig()->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, fmt ("Path to %1", info->name), *info->path, g_extProgPathFilter); + + if (fpath.isEmpty()) + return; + + info->input->setText (fpath); +} + +// ============================================================================= +// '...' button pressed for the download path +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_findDownloadFolder() +{ + 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->getAction(); + QString label = act->iconText(); + QString keybind = item->getKeyConfig()->getValue().toString(); + item->setText (fmt ("%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 += fmt ("%1", entry.getColor()->index); + } + + return val; +} + +// =============================================================================================== +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// =============================================================================================== +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// =============================================================================================== +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// =============================================================================================== +KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f), seq (seq) +{ + lb_output = new QLabel; + IMPLEMENT_DIALOG_BUTTONS + + setWhatsThis (tr ("Into this dialog you can input a key sequence for use as a " + "shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to " + "dismiss.")); + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget (lb_output); + layout->addWidget (bbx_buttons); + setLayout (layout); + + updateOutput(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool KeySequenceDialog::staticDialog (KeySequenceConfig* cfg, QWidget* parent) +{ + KeySequenceDialog dlg (cfg->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 = fmt ("
%1
", shortcut); + lb_output->setText (text); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) +{ + seq = ev->key() + ev->modifiers(); + updateOutput(); +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/ConfigurationDialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ConfigurationDialog.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,123 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_CONFIGDIALOG_H +#define LDFORGE_CONFIGDIALOG_H + +#include "MainWindow.h" +#include + +class Ui_ConfigUI; +class QLabel; +class QDoubleSpinBox; + +// ============================================================================= +class ShortcutListItem : public QListWidgetItem +{ + PROPERTY (public, KeySequenceConfig*, KeyConfig, NO_OPS, STOCK_WRITE) + PROPERTY (public, QAction*, Action, NO_OPS, 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 quickColors; + QDoubleSpinBox* dsb_gridData[3][4]; + + private: + Ui_ConfigUI* ui; + QLabel* lb_gridLabels[3]; + QLabel* lb_gridIcons[3]; + QList 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& haystack); + QString quickColorString(); + QListWidgetItem* getSelectedQuickColor(); + QList 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; +}; + +#endif // LDFORGE_CONFIGDIALOG_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/CrashCatcher.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/CrashCatcher.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,132 @@ +/* + * 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 . + */ + +#ifdef __unix__ + +#include +#include +#include +#include +#include +#include +#include +#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 g_signalsToCatch ({ + SIGSEGV, // segmentation fault + SIGABRT, // abort() calls + SIGFPE, // floating point exceptions (e.g. division by zero) + SIGILL, // illegal instructions +}); + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void handleCrash (int sig) +{ + printf ("%s: crashed with signal %d, launching gdb\n", __func__, sig); + + if (g_crashCatcherActive) + { + printf ("caught signal while crash catcher is active!\n"); + exit (149); + } + + const pid_t pid = getpid(); + QProcess proc; + QTemporaryFile commandsFile; + + g_crashCatcherActive = true; + + if (commandsFile.open()) + { + commandsFile.write (fmt ("attach %1\n", pid).toLocal8Bit()); + commandsFile.write (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. + prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0); + + proc.waitForFinished (1000); + QString output = QString (proc.readAllStandardOutput()); + QString err = QString (proc.readAllStandardError()); + + bombBox (fmt ("

Program crashed with signal %1

\n\n" + "%2" + "

GDB stdout:

%3
\n" + "

GDB stderr:

%4
", + sig, (!g_assertionFailure.isEmpty()) ? g_assertionFailure : "", output, err)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void initCrashCatcher() +{ + struct sigaction sighandler; + sighandler.sa_handler = &handleCrash; + sighandler.sa_flags = 0; + sigemptyset (&sighandler.sa_mask); + + for (int sig : g_signalsToCatch) + sigaction (sig, &sighandler, null); + + log ("%1: crash catcher hooked to signals: %2\n", __func__, g_signalsToCatch); +} +#endif // #ifdef __unix__ + +// ============================================================================= +// This function must be readily available in both Windows and Linux. We display +// the bomb box straight in Windows while in Linux we let abort() trigger the +// signal handler, which will cause the usual bomb box with GDB diagnostics. +// Said prompt will embed the assertion failure information. +// ----------------------------------------------------------------------------- +void assertionFailure (const char* file, int line, const char* funcname, const char* expr) +{ + QString errmsg = fmt ( + "

File: %1
" + "Line: %2
" + "Function: %3

" + "

Assertion `%4' failed.

", + file, line, funcname, expr); + + g_assertionFailure = errmsg; + +#ifndef __unix__ + bombBox (errmsg); +#endif + + abort(); +} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/CrashCatcher.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/CrashCatcher.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,29 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_CRASHCATCHER_H +#define LDFORGE_CRASHCATCHER_H + +#ifdef __unix__ + +void initCrashCatcher(); + +#else // ifdef __unix__ +#define initCrashCatcher() +#endif // ifdef __unix__ +#endif // ifndef LDFORGE_CRASHCATCHER_H \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Dialogs.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Dialogs.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,379 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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" +#include "moc_Dialogs.cpp" + +extern const char* g_extProgPathFilter; +extern_cfg (String, io_ldpath); + +// ============================================================================= +// ----------------------------------------------------------------------------- +OverlayDialog::OverlayDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) +{ + ui = new Ui_OverlayUI; + ui->setupUi (this); + + m_cameraArgs = + { + { ui->top, GL::ETopCamera }, + { ui->bottom, GL::EBottomCamera }, + { ui->front, GL::EFrontCamera }, + { ui->back, GL::EBackCamera }, + { ui->left, GL::ELeftCamera }, + { ui->right, GL::ERightCamera } + }; + + GL::EFixedCamera cam = g_win->R()->camera(); + + if (cam == GL::EFreeCamera) + cam = GL::ETopCamera; + + connect (ui->width, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged())); + connect (ui->height, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged())); + connect (ui->buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_help())); + connect (ui->fileSearchButton, SIGNAL (clicked (bool)), this, SLOT (slot_fpath())); + + slot_dimensionsChanged(); + fillDefaults (cam); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +OverlayDialog::~OverlayDialog() +{ + delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void OverlayDialog::fillDefaults (int newcam) +{ + LDGLOverlay& info = g_win->R()->getOverlay (newcam); + radioDefault (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 (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 (fmt ("%1", LDPaths::getError())); + okButton()->setEnabled (false); + return; + } + + ui->status->setText ("OK!"); + okButton()->setEnabled (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDrawPathDialog::slot_accept() +{ + Config::save(); + accept(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) +{ + ui = new Ui_OpenProgressUI; + ui->setupUi (this); + ui->progressText->setText ("Parsing..."); + setNumLines (0); + m_Progress = 0; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +OpenProgressDialog::~OpenProgressDialog() +{ + delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void OpenProgressDialog::setNumLines (int const& a) +{ + m_NumLines = a; + ui->progressBar->setRange (0, getNumLines()); + updateValues(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void OpenProgressDialog::updateValues() +{ + ui->progressText->setText (fmt ("Parsing... %1 / %2", getProgress(), getNumLines())); + ui->progressBar->setValue (getProgress()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void OpenProgressDialog::updateProgress (int progress) +{ + setProgress (progress); + updateValues(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +ExtProgPathPrompt::ExtProgPathPrompt (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 ("", 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 " " + fullVersionString()); + + QPushButton* mailButton = new QPushButton; + mailButton->setText (tr ("Contact")); + mailButton->setIcon (getIcon ("mail")); + ui.buttonBox->addButton (static_cast (mailButton), QDialogButtonBox::HelpRole); + connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail())); + + setWindowTitle (fmt (tr ("About %1"), APPNAME)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AboutDialog::slot_mail() +{ + QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo ?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(); +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Dialogs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Dialogs.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,145 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_DIALOGS_H +#define LDFORGE_DIALOGS_H + +#include +#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> 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, NUM_OPS, STOCK_WRITE) + PROPERTY (public, int, NumLines, NUM_OPS, 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); + +#endif // LDFORGE_DIALOGS_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Document.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Document.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,1435 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#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" +#include "moc_Document.cpp" + +cfg (String, io_ldpath, ""); +cfg (List, io_recentfiles, {}); +extern_cfg (String, net_downloadpath); +extern_cfg (Bool, gl_logostuds); + +static bool g_loadingMainFile = false; +static const int g_maxRecentFiles = 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
have LDConfig.ldr, parts/ and p/."; + return false; + } + + pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path); + pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path); + pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path); + + return true; + } + + // Accessors + 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); + setListItem (null); + setHistory (new History); + m_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 : getObjects()) + obj->deleteSelf(); + + // Clear the cache as well + for (LDObject* obj : getCache()) + obj->deleteSelf(); + + 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(); + log ("Closed %1", getName()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocument* findDocument (QString name) +{ + for (LDDocument * file : g_loadedFiles) + if (!file->getName().isEmpty() && file->getName() == 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->getFullPath().isEmpty()) + continue; + + QString partpath = fmt ("%1/%2", dirname (doc->getFullPath()), 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 = fmt ("%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 : initlist ({ io_ldpath, net_downloadpath })) + { + for (const QString& subdir : initlist ({ "parts", "p" })) + { + fullPath = fmt ("%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) +{ + log ("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 (getLines().size()); + dlg->setModal (true); + dlg->show(); + + // Connect the loader in so we can show updates + connect (this, SIGNAL (workDone()), dlg, SLOT (accept())); + connect (dlg, SIGNAL (rejected()), this, SLOT (abort())); + } + else + dlg = null; + + // Begin working + work (0); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDFileLoader::work (int i) +{ + // User wishes to abort, so stop here now. + if (isAborted()) + { + for (LDObject* obj : m_Objects) + obj->deleteSelf(); + + m_Objects.clear(); + setDone (true); + return; + } + + // Parse up to 300 lines per iteration + int max = i + 300; + + for (; i < max && i < (int) getLines().size(); ++i) + { + QString line = getLines()[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->getType() == LDObject::EError) + { + log ("Couldn't parse line #%1: %2", getProgress() + 1, static_cast (obj)->reason); + + if (getWarnings() != null) + (*getWarnings())++; + } + + m_Objects << obj; + setProgress (i); + + // If we have a dialog pointer, update the progress now + if (isOnForeground()) + dlg->updateProgress (i); + } + + // If we're done now, tell the environment we're done and stop. + if (i >= ((int) getLines().size()) - 1) + { + emit workDone(); + setDone (true); + return; + } + + // Otherwise, continue, by recursing back. + if (!isDone()) + { + // If we have a dialog to show progress output to, we cannot just call + // work() again immediately as the dialog needs some processor cycles as + // well. Thus, take a detour through the event loop by using the + // meta-object system. + // + // This terminates the loop here and control goes back to the function + // which called the file loader. It will keep processing the event loop + // until we're ready (see loadFileContents), thus the event loop will + // eventually catch the invokation we throw here and send us back. Though + // it's not technically recursion anymore, more like a for loop. :P + if (isOnForeground()) + QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i)); + else + work (i); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +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->getObjects(); + 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->getFullPath())); + dlog ("name: %1 (%2)", load->getName(), load->getFullPath()); + g_loadedFiles << load; + + // Don't take the file loading as actual edits to the file + load->getHistory()->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()->setFile (load); + log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); + } + + load->getHistory()->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 = fmt (tr ("There are unsaved changes to %1. Should it be saved?"), + (getName().length() > 0) ? getName() : tr ("")); + + 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 (getName().length() == 0) + { + QString newpath = QFileDialog::getSaveFileName (g_win, tr ("Save As"), + getCurrentDocument()->getName(), tr ("LDraw files (*.dat *.ldr)")); + + if (newpath.length() == 0) + return false; + + setName (newpath); + } + + if (!save()) + { + message = fmt (tr ("Failed to save %1 (%2)\nDo you still want to close?"), + getName(), 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 files = g_loadedFiles; + + for (LDDocument* file : files) + delete file; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void newFile() +{ + // Create a new anonymous file and set it to our current + LDDocument* f = new LDDocument; + f->setName (""); + f->setImplicit (false); + g_loadedFiles << f; + LDDocument::setCurrent (f); + LDDocument::closeInitialFile(); + g_win->R()->setFile (f); + g_win->doFullRefresh(); + g_win->updateTitle(); + g_win->updateActions(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void addRecentFile (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->getName() == 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 (fmt (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->getReferences()) + { dlog ("ptr: %1 (%2)\n", + ptr, ptr->getPointer() ? ptr->getPointer()->getName() : ""); + + ptr->operator= (file); + } + + assert (documentToReplace->countReferences() == 0); + 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 = getFullPath(); + + 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->getType() == LDObject::EComment) + { + LDComment* nameComment = static_cast (nameObject); + + if (nameComment->text.left (6) == "Name: ") + { + QString newname = shortenName (savepath); + nameComment->text = fmt ("Name: %1", newname); + g_win->buildObjList(); + } + } + + // File is open, now save the model to it. Note that LDraw requires files to + // have DOS line endings, so we terminate the lines with \r\n. + for (LDObject* obj : getObjects()) + f.write ((obj->raw() + "\r\n").toUtf8()); + + // File is saved, now clean up. + f.close(); + + // We have successfully saved, update the save position now. + setSavePosition (getHistory()->getPosition()); + setFullPath (savepath); + setName (shortenName (savepath)); + + g_win->updateDocumentListItem (this); + g_win->updateTitle(); + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +class LDParseError : public std::exception +{ + PROPERTY (private, QString, Error, STR_OPS, STOCK_WRITE) + PROPERTY (private, QString, Line, STR_OPS, STOCK_WRITE) + + public: + LDParseError (QString line, QString a) : m_Error (a), m_Line (line) {} + + const char* what() const throw() + { + return getError().toLocal8Bit().constData(); + } +}; + +// ============================================================================= +// ----------------------------------------------------------------------------- +void checkTokenCount (QString line, const QStringList& tokens, int num) +{ + if (tokens.size() != num) + throw LDParseError (line, fmt ("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, fmt ("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 == fmt ("BFC %1", LDBFC::statements [i])) + return new LDBFC ( (LDBFC::Type) i); + + // MLCAD is notorious for stuffing these statements in parts it + // creates. The above block only handles valid statements, so we + // need to handle MLCAD-style invertnext, clip and noclip separately. + struct + { + QString a; + LDBFC::Type b; + } BFCData[] = + { + { "INVERTNEXT", LDBFC::InvertNext }, + { "NOCLIP", LDBFC::NoClip }, + { "CLIP", LDBFC::Clip } + }; + + for (const auto& i : BFCData) + if (comm == "BFC CERTIFY " + i.a) + return new LDBFC (i.b); + } + + if (tokens.size() > 2 && tokens[1] == "!LDFORGE") + { + // Handle LDForge-specific types, they're embedded into comments too + if (tokens[2] == "VERTEX") + { + // Vertex (0 !LDFORGE VERTEX) + 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->text = 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, fmt ("Could not open %1", tokens[14])); + obj->setFileReferenced (tokens[14]); + return obj; + } + + LDSubfile* obj = new LDSubfile; + obj->setColor (tokens[1].toLong()); + obj->setPosition (parseVertex (tokens, 2)); // 2 - 4 + + Matrix transform; + + for (int i = 0; i < 9; ++i) + transform[i] = tokens[i + 5].toDouble(); // 5 - 13 + + obj->setTransform (transform); + obj->setFileInfo (load); + return obj; + } + + case 2: + { + 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.getLine(), e.getError()); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +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()->getObjects()) + { + if (obj->getType() == LDObject::ESubfile) + { + LDSubfile* ref = static_cast (obj); + LDDocument* fileInfo = getDocument (ref->getFileInfo()->getName()); + + if (fileInfo) + ref->setFileInfo (fileInfo); + else + ref->replace (new LDError (ref->raw(), fmt ("Could not open %1", ref->getFileInfo()->getName()))); + } + + // Reparse gibberish files. It could be that they are invalid because + // of loading errors. Circumstances may be different now. + if (obj->getType() == LDObject::EError) + obj->replace (parseLine (static_cast (obj)->contents)); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int LDDocument::addObject (LDObject* obj) +{ + getHistory()->add (new AddHistory (getObjects().size(), obj)); + m_Objects << obj; + + if (obj->getType() == LDObject::EVertex) + m_Vertices << obj; + +#ifdef DEBUG + if (!isImplicit()) + dlog ("Added object #%1 (%2)\n", obj->getID(), obj->getTypeName()); +#endif + + obj->setFile (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) +{ + getHistory()->add (new AddHistory (pos, obj)); + m_Objects.insert (pos, obj); + obj->setFile (this); + +#ifdef DEBUG + if (!isImplicit()) + dlog ("Inserted object #%1 (%2) at %3\n", obj->getID(), obj->getTypeName(), pos); +#endif +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::forgetObject (LDObject* obj) +{ + int idx = obj->getIndex(); + obj->unselect(); + assert (m_Objects[idx] == obj); + + if (!getHistory()->isIgnoring()) + getHistory()->add (new DelHistory (idx, obj)); + + m_Objects.removeAt (idx); + obj->setFile (null); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool safeToCloseAll() +{ + for (LDDocument* f : g_loadedFiles) + if (!f->isSafeToClose()) + return false; + + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::setObject (int idx, LDObject* obj) +{ + assert (idx >= 0 && idx < m_Objects.size()); + + // Mark this change to history + if (!m_History->isIgnoring()) + { + QString oldcode = getObject (idx)->raw(); + QString newcode = obj->raw(); + *m_History << new EditHistory (idx, oldcode, newcode); + } + + m_Objects[idx]->unselect(); + m_Objects[idx]->setFile (null); + obj->setFile (this); + m_Objects[idx] = obj; +} + +// ============================================================================= +// Close all implicit files with no references +// ----------------------------------------------------------------------------- +void LDDocument::closeUnused() +{ + for (LDDocument* file : g_loadedFiles) + if (file->isImplicit() && file->countReferences() == 0) + delete file; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject* LDDocument::getObject (int pos) const +{ + if (m_Objects.size() <= pos) + return null; + + return m_Objects[pos]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int LDDocument::getObjectCount() const +{ + return getObjects().size(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool LDDocument::hasUnsavedChanges() const +{ + return !isImplicit() && getHistory()->getPosition() != getSavePosition(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString LDDocument::getDisplayName() +{ + if (!getName().isEmpty()) + return getName(); + + if (!getDefaultName().isEmpty()) + return "[" + getDefaultName() + "]"; + + return tr (""); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +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 (getName() == "stud.dat" && g_logoedStud) + return g_logoedStud->inlineContents (flags); + elif (getName() == "stud2.dat" && g_logoedStud2) + return g_logoedStud2->inlineContents (flags); + } + + LDObjectList objs, objcache; + + bool deep = flags & LDSubfile::DeepInline, + doCache = flags & LDSubfile::CacheInline; + + if (m_needsCache) + { + clearCache(); + doCache = true; + } + + // If we have this cached, just create a copy of that + if (deep && getCache().isEmpty() == false) + { + for (LDObject* obj : getCache()) + objs << obj->createCopy(); + } + else + { + if (!deep) + doCache = false; + + for (LDObject* obj : getObjects()) + { + // Skip those without scemantic meaning + if (!obj->isScemantic()) + continue; + + // Got another sub-file reference, inline it if we're deep-inlining. If not, + // just add it into the objects normally. Also, we only cache immediate + // subfiles and this is not one. Yay, recursion! + if (deep && obj->getType() == LDObject::ESubfile) + { + LDSubfile* ref = static_cast (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()->setFile (f); + g_win->R()->repaint(); + log ("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]->getName().isEmpty() && + g_loadedFiles[1]->getName().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); + + log (LDDocument::tr ("Logoed studs loaded.\n")); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::addToSelection (LDObject* obj) // [protected] +{ + if (obj->isSelected()) + return; + + assert (obj->getFile() == this); + m_sel << obj; + obj->setSelected (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::removeFromSelection (LDObject* obj) // [protected] +{ + if (!obj->isSelected()) + return; + + assert (obj->getFile() == this); + m_sel.removeOne (obj); + obj->setSelected (false); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::clearSelection() +{ + for (LDObject* obj : m_sel) + removeFromSelection (obj); + + assert (m_sel.isEmpty()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +const 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->getID(), other->getID())); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +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) +{ + pushToReferences (ptr); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocument::removeReference (LDDocumentPointer* ptr) +{ + removeFromReferences (ptr); + + if (getReferences().size() == 0) + invokeLater (closeUnused); +} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Document.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Document.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,245 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_DOCUMENT_H +#define LDFORGE_DOCUMENT_H + +#include +#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(); +} + +// ============================================================================= +// LDDocument +// +// The LDDocument class stores a document opened in LDForge 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 +{ + properties: + Q_OBJECT + PROPERTY (private, LDObjectList, Objects, LIST_OPS, STOCK_WRITE) + PROPERTY (private, History*, History, NO_OPS, STOCK_WRITE) + PROPERTY (private, LDObjectList, Vertices, LIST_OPS, STOCK_WRITE) + PROPERTY (private, QList, References, LIST_OPS, STOCK_WRITE) + PROPERTY (public, QString, Name, STR_OPS, STOCK_WRITE) + PROPERTY (public, QString, FullPath, STR_OPS, STOCK_WRITE) + PROPERTY (public, QString, DefaultName, STR_OPS, STOCK_WRITE) + PROPERTY (public, bool, Implicit, BOOL_OPS, STOCK_WRITE) + PROPERTY (public, LDObjectList, Cache, LIST_OPS, STOCK_WRITE) + PROPERTY (public, long, SavePosition, NUM_OPS, STOCK_WRITE) + PROPERTY (public, QListWidgetItem*, ListItem, NO_OPS, 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() + { + m_History->addStep(); + } + + inline void undo() + { + m_History->undo(); + } + + inline void redo() + { + m_History->redo(); + } + + inline void clearHistory() + { + m_History->clear(); + } + + inline void addToHistory (AbstractHistoryEntry* entry) + { + *m_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 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 g_loadedFiles; // Vector of all currently opened files. + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +// FileLoader +// +// 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, NO_OPS, STOCK_WRITE) + PROPERTY (private, bool, Done, BOOL_OPS, STOCK_WRITE) + PROPERTY (private, int, Progress, NUM_OPS, STOCK_WRITE) + PROPERTY (private, bool, Aborted, BOOL_OPS, STOCK_WRITE) + PROPERTY (public, QStringList, Lines, NO_OPS, STOCK_WRITE) + PROPERTY (public, int*, Warnings, NO_OPS, STOCK_WRITE) + PROPERTY (public, bool, OnForeground, BOOL_OPS, STOCK_WRITE) + + public slots: + void start(); + void abort(); + + private: + OpenProgressDialog* dlg; + + private slots: + void work (int i); + + signals: + void progressUpdate (int progress); + void workDone(); +}; + +#endif // LDFORGE_DOCUMENT_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Documentation.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Documentation.cc Tue Jan 21 02:03:27 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 . + */ + +#include +#include +#include +#include +#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 = + "

Overlay images


" + "

" 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.

" + "

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.

" + "

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.

" + "

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(); +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Documentation.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Documentation.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,26 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_DOCS_H +#define LDFORGE_DOCS_H + +extern const char* g_docs_overlays; + +void showDocumentation (const char* text); + +#endif // LDFORGE_DOCS_H \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/EditHistory.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditHistory.cc Tue Jan 21 02:03:27 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 . + */ + +#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() || getPosition() == -1) + return; + + // Don't take the changes done here as actual edits to the document + setIgnoring (true); + + const Changeset& set = getChangeset (getPosition()); + + // Iterate the list in reverse and undo all actions + for (int i = set.size() - 1; i >= 0; --i) + { + AbstractHistoryEntry* change = set[i]; + change->undo(); + } + + decreasePosition(); + g_win->refresh(); + g_win->updateActions(); + dlog ("Position is now %1", getPosition()); + setIgnoring (false); +} + +// ============================================================================= +// +void History::redo() +{ + if (getPosition() == m_changesets.size()) + return; + + setIgnoring (true); + const Changeset& set = getChangeset (getPosition() + 1); + + // Redo things - in the order as they were done in the first place + for (const AbstractHistoryEntry* change : set) + change->redo(); + + setPosition (getPosition() + 1); + g_win->refresh(); + g_win->updateActions(); + dlog ("Position is now %1", getPosition()); + setIgnoring (false); +} + +// ============================================================================= +// +void History::clear() +{ + for (Changeset set : m_changesets) + for (AbstractHistoryEntry* change : set) + delete change; + + m_changesets.clear(); + dlog ("History: cleared"); +} + +// ============================================================================= +// +void History::addStep() +{ + if (m_currentChangeset.isEmpty()) + return; + + while (getPosition() < getSize() - 1) + { + Changeset last = m_changesets.last(); + + for (AbstractHistoryEntry* entry : last) + delete entry; + + m_changesets.removeLast(); + } + + dlog ("History: step added (%1 changes)", m_currentChangeset.size()); + m_changesets << m_currentChangeset; + m_currentChangeset.clear(); + setPosition (getPosition() + 1); + g_win->updateActions(); +} + +// ============================================================================= +// +void History::add (AbstractHistoryEntry* entry) +{ + if (isIgnoring()) + { + delete entry; + return; + } + + entry->setParent (this); + m_currentChangeset << entry; + dlog ("History: added entry of type %1", entry->getTypeName()); +} + +// ============================================================================= +// +void AddHistory::undo() const +{ + LDObject* obj = getParent()->getDocument()->getObject (getIndex()); + obj->deleteSelf(); +} + +// ============================================================================= +// +void AddHistory::redo() const +{ + LDObject* obj = parseLine (getCode()); + getParent()->getDocument()->insertObj (getIndex(), obj); + g_win->R()->compileObject (obj); +} + +// ============================================================================= +// +DelHistory::DelHistory (int idx, LDObject* obj) : + m_Index (idx), + m_Code (obj->raw()) {} + +// ============================================================================= +// heh +// +void DelHistory::undo() const +{ + LDObject* obj = parseLine (getCode()); + getParent()->getDocument()->insertObj (getIndex(), obj); + g_win->R()->compileObject (obj); +} + +// ============================================================================= +// +void DelHistory::redo() const +{ + LDObject* obj = getParent()->getDocument()->getObject (getIndex()); + obj->deleteSelf(); +} + +// ============================================================================= +// +void EditHistory::undo() const +{ + LDObject* obj = getCurrentDocument()->getObject (getIndex()); + LDObject* newobj = parseLine (getOldCode()); + obj->replace (newobj); + g_win->R()->compileObject (newobj); +} + +// ============================================================================= +// +void EditHistory::redo() const +{ + LDObject* obj = getCurrentDocument()->getObject (getIndex()); + LDObject* newobj = parseLine (getNewCode()); + obj->replace (newobj); + g_win->R()->compileObject (newobj); +} + +// ============================================================================= +// +void SwapHistory::undo() const +{ + LDObject::fromID (a)->swap (LDObject::fromID (b)); +} + +// ============================================================================= +// +void SwapHistory::redo() const +{ + undo(); +} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/EditHistory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EditHistory.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,184 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_HISTORY_H +#define LDFORGE_HISTORY_H + +#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, NUM_OPS, STOCK_WRITE) + PROPERTY (public, LDDocument*, Document, NO_OPS, STOCK_WRITE) + PROPERTY (public, bool, Ignoring, BOOL_OPS, STOCK_WRITE) + + public: + typedef QList 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 m_changesets; +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class AbstractHistoryEntry +{ + PROPERTY (public, History*, Parent, NO_OPS, 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, NO_OPS, STOCK_WRITE) + PROPERTY (private, QString, Code, NO_OPS, STOCK_WRITE) + + public: + IMPLEMENT_HISTORY_TYPE (Del) + DelHistory (int idx, LDObject* obj); +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class EditHistory : public AbstractHistoryEntry +{ + PROPERTY (private, int, Index, NO_OPS, STOCK_WRITE) + PROPERTY (private, QString, OldCode, NO_OPS, STOCK_WRITE) + PROPERTY (private, QString, NewCode, NO_OPS, 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, NO_OPS, STOCK_WRITE) + PROPERTY (private, QString, Code, NO_OPS, STOCK_WRITE) + + public: + IMPLEMENT_HISTORY_TYPE (Add) + + AddHistory (int idx, LDObject* obj) : + m_Index (idx), + m_Code (obj->raw()) {} +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class MoveHistory : public AbstractHistoryEntry +{ + public: + IMPLEMENT_HISTORY_TYPE (Move) + + QList indices; + Vertex dest; + + MoveHistory (QList 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; +}; + +#endif // LDFORGE_HISTORY_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/ExternalPrograms.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ExternalPrograms.cc Tue Jan 21 02:03:27 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#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 fmt ("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 fmt ("Timed out (30 seconds)"); + } + + return ""; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void writeObjects (const LDObjectList& objects, QFile& f) +{ + for (LDObject* obj : objects) + { + if (obj->getType() == LDObject::ESubfile) + { + LDSubfile* ref = static_cast (obj); + LDObjectList objs = ref->inlineContents (LDSubfile::DeepInline); + + writeObjects (objs, f); + + for (LDObject* obj : objs) + obj->deleteSelf(); + } + else + f.write ((obj->raw() + "\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 (fmt ("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()->getObjects()) + { + if (obj->isColored() == false || obj->getColor() != 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 + + log ("cmdline: %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 (fmt ("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 = fmt ("Program exited abnormally (return code %1).", proc.exitCode()); + + if (!err.isEmpty()) + { + critical (fmt ("%1 failed: %2\n", g_extProgNames[prog], err)); + return false; + } + + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void insertOutput (QString fname, bool replace, QList colorsToReplace) +{ +#ifdef DEBUG + QFile::copy (fname, "./debug_lastOutput"); +#endif // RELEASE + + // Read the output file + QFile f (fname); + + if (!f.open (QIODevice::ReadOnly)) + { + critical (fmt ("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->deleteSelf(); + 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 ? fmt ("-l %1", ui.dsb_segsplit->value()) : ""), + (ui.sb_bias->value() != 0 ? fmt ("-s %1", ui.sb_bias->value()) : ""), + in1DATName, + in2DATName, + outDATName + }); + + writeColorGroup (in1Col, in1DATName); + writeColorGroup (in2Col, in2DATName); + + if (!runUtilityProcess (Coverer, prog_coverer, argv)) + return; + + insertOutput (outDATName, false, {}); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Isecalc, 0) +{ + setlocale (LC_ALL, "C"); + + if (!checkProgPath (Isecalc)) + return; + + Ui::IsecalcUI ui; + QDialog* dlg = new QDialog; + ui.setupUi (dlg); + + makeColorComboBox (ui.cmb_col1); + makeColorComboBox (ui.cmb_col2); + + int in1Col, in2Col; + + // Run the dialog and validate input + forever + { + if (!dlg->exec()) + return; + + in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt(), + in2Col = ui.cmb_col1->itemData (ui.cmb_col2->currentIndex()).toInt(); + + if (in1Col == in2Col) + { + critical ("Cannot use the same color group for both input and cutter!"); + continue; + } + + break; + } + + QTemporaryFile in1dat, in2dat, outdat; + 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 ( + { + fmt ("-p %1", ui.precision->value()), + fmt ("-af %1", ui.flatAngle->value()), + fmt ("-ac %1", ui.condAngle->value()), + fmt ("-ae %1", ui.edgeAngle->value()), + ui.delLines->isChecked() ? "-de" : "", + ui.delCondLines->isChecked() ? "-dc" : "", + ui.colored->isChecked() ? "-c" : "", + ui.bfc->isChecked() ? "-b" : "", + ui.convex->isChecked() ? "-cx" : "", + ui.concave->isChecked() ? "-cv" : "", + unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""), + inName, + outName, + }); + + writeSelection (inName); + + if (!runUtilityProcess (Edger2, prog_edger2, argv)) + return; + + insertOutput (outName, true, {}); +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/GLRenderer.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/GLRenderer.cc Tue Jan 21 02:03:27 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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" +#include "moc_GLRenderer.cpp" + +static const LDFixedCameraInfo g_FixedCameras[6] = +{ + {{ 1, 0, 0 }, X, Z, false, false }, + {{ 0, 0, 0 }, X, Y, false, true }, + {{ 0, 1, 0 }, Z, Y, true, true }, + {{ -1, 0, 0 }, X, Z, false, true }, + {{ 0, 0, 0 }, X, Y, true, true }, + {{ 0, -1, 0 }, Z, Y, false, true }, +}; + +// 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 g_warnedColors; + +// ============================================================================= +// +GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) +{ + m_Picking = m_rangepick = false; + m_camera = (GL::EFixedCamera) gl_camera; + m_drawToolTip = false; + m_EditMode = ESelectMode; + m_rectdraw = false; + m_panning = false; + setFile (null); + setDrawOnly (false); + setMessageLog (null); + m_width = m_height = -1; + m_hoverpos = g_origin; + + m_toolTipTimer = new QTimer (this); + m_toolTipTimer->setSingleShot (true); + connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer())); + + m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); + m_thinBorderPen = m_thickBorderPen; + m_thinBorderPen.setWidth (1); + + // Init camera icons + for (const GL::EFixedCamera cam : g_Cameras) + { + QString iconname = fmt ("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()->getID(); + + // Calculate a color based from this index. This method caters for + // 16777216 objects. I don't think that'll be exceeded anytime soon. :) + // ATM biggest is 53588.dat with 12600 lines. + double r = (i / 0x10000) % 0x100, + g = (i / 0x100) % 0x100, + b = i % 0x100; + + qglColor (QColor (r, g, b)); + return; + } + + if ((list == BFCFrontList || list == BFCBackList) && + obj->getType() != LDObject::ELine && + obj->getType() != LDObject::ECondLine) + { + if (list == GL::BFCFrontList) + qcol = QColor (40, 192, 0); + else + qcol = QColor (224, 0, 0); + } + else + { + if (obj->getColor() == maincolor) + qcol = getMainColor(); + else + { + LDColor* col = getColor (obj->getColor()); + + if (col) + qcol = col->faceColor; + } + + if (obj->getColor() == edgecolor) + { + LDColor* col; + + if (!gl_blackedges && obj->getParent() && (col = getColor (obj->getParent()->getColor()))) + 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->getColor() != edgecolor) + qcol = getMainColor(); + + // Warn about the unknown colors, but only once. + for (int i : g_warnedColors) + if (obj->getColor() == i) + return; + + log ("%1: Unknown color %2!\n", __func__, obj->getColor()); + g_warnedColors << obj->getColor(); + return; + } + } + + 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 (getFile() == null) + return; + + if (gl_wireframe && !isPicking()) + glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + + glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable (GL_DEPTH_TEST); + + if (m_camera != EFreeCamera) + { + glMatrixMode (GL_PROJECTION); + glPushMatrix(); + + glLoadIdentity(); + glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f); + glTranslatef (pan (X), pan (Y), 0.0f); + + if (m_camera != EFrontCamera && m_camera != EBackCamera) + { + glRotatef (90.0f, g_FixedCameras[camera()].glrotate[0], + g_FixedCameras[camera()].glrotate[1], + g_FixedCameras[camera()].glrotate[2]); + } + + // Back camera needs to be handled differently + if (m_camera == GLRenderer::EBackCamera) + { + glRotatef (180.0f, 1.0f, 0.0f, 0.0f); + glRotatef (180.0f, 0.0f, 0.0f, 1.0f); + } + } + else + { + glMatrixMode (GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glTranslatef (0.0f, 0.0f, -2.0f); + glTranslatef (pan (X), pan (Y), -zoom()); + glRotatef (rot (X), 1.0f, 0.0f, 0.0f); + glRotatef (rot (Y), 0.0f, 1.0f, 0.0f); + glRotatef (rot (Z), 0.0f, 0.0f, 1.0f); + } + + const GL::ListType list = (!isDrawOnly() && isPicking()) ? PickList : NormalList; + + if (gl_colorbfc && !isPicking() && !isDrawOnly()) + { + glEnable (GL_CULL_FACE); + + for (LDObject* obj : getFile()->getObjects()) + { + if (obj->isHidden()) + continue; + + glCullFace (GL_BACK); + glCallList (obj->glLists[BFCFrontList]); + + glCullFace (GL_FRONT); + glCallList (obj->glLists[BFCBackList]); + } + + glDisable (GL_CULL_FACE); + } + else + { + for (LDObject* obj : getFile()->getObjects()) + { + if (obj->isHidden()) + continue; + + glCallList (obj->glLists[list]); + } + } + + if (gl_axes && !isPicking() && !isDrawOnly()) + glCallList (m_axeslist); + + glPopMatrix(); + glMatrixMode (GL_MODELVIEW); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); +} + +// ============================================================================= +// +// This converts a 2D point on the screen to a 3D point in the model. If 'snap' +// is true, the 3D point will snap to the current grid. +// +Vertex GLRenderer::coordconv2_3 (const QPoint& pos2d, bool snap) const +{ + assert (camera() != EFreeCamera); + + Vertex pos3d; + const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; + const Axis axisX = cam->axisX; + const Axis axisY = cam->axisY; + const int negXFac = cam->negX ? -1 : 1, + negYFac = cam->negY ? -1 : 1; + + // Calculate cx and cy - these are the LDraw unit coords the cursor is at. + double cx = (-m_virtWidth + ((2 * pos2d.x() * m_virtWidth) / m_width) - pan (X)); + double cy = (m_virtHeight - ((2 * pos2d.y() * m_virtHeight) / m_height) - pan (Y)); + + if (snap) + { + cx = Grid::snap (cx, (Grid::Config) axisX); + cy = Grid::snap (cy, (Grid::Config) axisY); + } + + cx *= negXFac; + cy *= negYFac; + + 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 = fmt (tr ("X: %1, Y: %2, Z: %3"), m_hoverpos[X], m_hoverpos[Y], m_hoverpos[Z]); + QFontMetrics metrics = QFontMetrics (font()); + QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); + paint.setPen (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 (getEditMode() == 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 (getEditMode() == ECircleMode) + { + // If we have not specified the center point of the circle yet, preview it on the screen. + if (m_drawedVerts.isEmpty()) + drawBlip (paint, coordconv3_2 (m_hoverpos)); + else + { + QVector verts, verts2; + const double dist0 = getCircleDrawDist (0), + dist1 = (m_drawedVerts.size() >= 2) ? getCircleDrawDist (1) : -1; + const int segs = lores; + const double angleUnit = (2 * pi) / segs; + Axis relX, relY; + QVector 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] && getEditMode() != ESelectMode) + continue; + + paint.drawPixmap (info.destRect, *info.img, info.srcRect); + } + + QString fmtstr = tr ("%1 Camera"); + + // Draw a label for the current camera in the bottom left corner + { + const int margin = 4; + + QString label; + label = fmt (fmtstr, tr (g_CameraNames[camera()])); + paint.setPen (textpen); + paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label); + } + + // Tool tips + if (m_drawToolTip) + { + if (m_cameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false) + m_drawToolTip = false; + else + { + QString label = fmt (fmtstr, tr (g_CameraNames[m_toolTipCamera])); + QToolTip::showText (m_globalpos, label); + } + } + } + + // Message log + if (getMessageLog()) + { + int y = 0; + const int margin = 2; + QColor penColor = textpen.color(); + + for (const MessageManager::Line& line : getMessageLog()->getLines()) + { + penColor.setAlphaF (line.alpha); + paint.setPen (penColor); + paint.drawText (QPoint (margin, y + margin + metrics.ascent()), line.text); + y += metrics.height(); + } + } + + // If we're range-picking, draw a rectangle encompassing the selection area. + if (m_rangepick && !isPicking() && m_totalmove >= 10) + { + int x0 = m_rangeStart.x(), + y0 = m_rangeStart.y(), + x1 = m_pos.x(), + y1 = m_pos.y(); + + QRect rect (x0, y0, x1 - x0, y1 - y0); + QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF"); + fillColor.setAlphaF (0.2f); + + paint.setPen (m_thickBorderPen); + paint.setBrush (QBrush (fillColor)); + paint.drawRect (rect); + } +} + +// ============================================================================= +// +void GLRenderer::drawBlip (QPainter& paint, QPoint pos) const +{ + QPen pen = m_thinBorderPen; + const int blipsize = 8; + pen.setWidth (1); + paint.setPen (pen); + paint.setBrush (QColor (64, 192, 0)); + paint.drawEllipse (pos.x() - blipsize / 2, pos.y() - blipsize / 2, blipsize, blipsize); +} + +// ============================================================================= +// +void GLRenderer::compileAllObjects() +{ + if (!getFile()) + return; + + // Compiling all is a big job, use a busy cursor + setCursor (Qt::BusyCursor); + + m_knownVerts.clear(); + + for (LDObject* obj : getFile()->getObjects()) + compileObject (obj); + + // Compile axes + glDeleteLists (m_axeslist, 1); + m_axeslist = glGenLists (1); + glNewList (m_axeslist, GL_COMPILE); + glBegin (GL_LINES); + + for (const LDGLAxis& ax : g_GLAxes) + { + qglColor (ax.col); + compileVertex (ax.vert); + compileVertex (-ax.vert); + } + + glEnd(); + glEndList(); + + setCursor (Qt::ArrowCursor); +} + +// ============================================================================= +// +void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype) +{ + glBegin (gltype); + + const int numverts = (obj->getType() != LDObject::ECondLine) ? obj->vertices() : 2; + + if (g_glInvert == false) + for (int i = 0; i < numverts; ++i) + compileVertex (obj->getVertex (i)); + else + for (int i = numverts - 1; i >= 0; --i) + compileVertex (obj->getVertex (i)); + + glEnd(); +} + +// ============================================================================= +// +void GLRenderer::compileList (LDObject* obj, const GLRenderer::ListType list) +{ + setObjectColor (obj, list); + + switch (obj->getType()) + { + case LDObject::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 (obj); + LDObjectList objs; + + objs = ref->inlineContents (LDSubfile::DeepCacheInline | LDSubfile::RendererInline); + bool oldinvert = g_glInvert; + + if (ref->getTransform().getDeterminant() < 0) + g_glInvert = !g_glInvert; + + LDObject* prev = ref->prev(); + + if (prev && prev->getType() == LDObject::EBFC && static_cast (prev)->type == LDBFC::InvertNext) + g_glInvert = !g_glInvert; + + for (LDObject* obj : objs) + { + compileList (obj, list); + obj->deleteSelf(); + } + + g_glInvert = oldinvert; + } break; + + default: + break; + } +} + +// ============================================================================= +// +void GLRenderer::compileVertex (const Vertex& vrt) +{ + glVertex3d (vrt[X], -vrt[Y], -vrt[Z]); +} + +// ============================================================================= +// +void GLRenderer::clampAngle (double& angle) const +{ + while (angle < 0) + angle += 360.0; + + while (angle > 360.0) + angle -= 360.0; +} + +// ============================================================================= +// +void GLRenderer::addDrawnVertex (Vertex pos) +{ + // If we picked an already-existing vertex, stop drawing + if (getEditMode() == 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 (getEditMode()) + { + case EDrawMode: + { + if (m_rectdraw) + { + if (m_drawedVerts.size() == 2) + { + endDraw (true); + return; + } + } else + { + // If we have 4 verts, stop drawing. + if (m_drawedVerts.size() >= 4) + { + endDraw (true); + return; + } + + if (m_drawedVerts.isEmpty() && ev->modifiers() & Qt::ShiftModifier) + { + m_rectdraw = true; + updateRectVerts(); + } + } + + addDrawnVertex (m_hoverpos); + } break; + + case ECircleMode: + { + if (m_drawedVerts.size() == 3) + { + endDraw (true); + return; + } + + addDrawnVertex (m_hoverpos); + } break; + + case ESelectMode: + { + if (!isDrawOnly()) + { + if (m_totalmove < 10) + m_rangepick = false; + + if (!m_rangepick) + m_addpick = (m_keymods & Qt::ControlModifier); + + if (m_totalmove < 10 || m_rangepick) + pick (ev->x(), ev->y()); + } + } break; + } + + m_rangepick = false; + } + + if (wasMid && getEditMode() != ESelectMode && m_drawedVerts.size() < 4 && m_totalmove < 10) + { + // Find the closest vertex to our cursor + double mindist = 1024.0f; + Vertex closest; + bool valid = false; + + QPoint curspos = coordconv3_2 (m_hoverpos); + + for (const Vertex& pos3d: m_knownVerts) + { + QPoint pos2d = coordconv3_2 (pos3d); + + // Measure squared distance + const double dx = abs (pos2d.x() - curspos.x()), + dy = abs (pos2d.y() - curspos.y()), + distsq = (dx * dx) + (dy * dy); + + if (distsq >= 1024.0f) // 32.0f ** 2 + continue; // too far away + + if (distsq < mindist) + { + mindist = distsq; + closest = pos3d; + valid = true; + + // If it's only 4 pixels away, I think we found our vertex now. + if (distsq <= 16.0f) // 4.0f ** 2 + break; + } + } + + if (valid) + addDrawnVertex (closest); + } + + if (wasRight && !m_drawedVerts.isEmpty()) + { + // Remove the last vertex + m_drawedVerts.removeLast(); + + if (m_drawedVerts.isEmpty()) + m_rectdraw = false; + } + +end: + update(); + m_totalmove = 0; +} + +// ============================================================================= +// +void GLRenderer::mousePressEvent (QMouseEvent* ev) +{ + m_totalmove = 0; + + if (ev->modifiers() & Qt::ControlModifier) + { + m_rangepick = true; + m_rangeStart.setX (ev->x()); + m_rangeStart.setY (ev->y()); + m_addpick = (m_keymods & Qt::AltModifier); + ev->accept(); + } + + m_lastButtons = ev->buttons(); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::mouseMoveEvent (QMouseEvent* ev) +{ + int dx = ev->x() - m_pos.x(); + int dy = ev->y() - m_pos.y(); + m_totalmove += abs (dx) + abs (dy); + + const bool left = ev->buttons() & Qt::LeftButton, + mid = ev->buttons() & Qt::MidButton, + shift = ev->modifiers() & Qt::ShiftModifier; + + if (mid || (left && shift)) + { + pan (X) += 0.03f * dx * (zoom() / 7.5f); + pan (Y) -= 0.03f * dy * (zoom() / 7.5f); + m_panning = true; + } elif (left && !m_rangepick && camera() == EFreeCamera) + { + rot (X) = rot (X) + dy; + rot (Y) = rot (Y) + dx; + + clampAngle (rot (X)); + clampAngle (rot (Y)); + } + + // Start the tool tip timer + if (!m_drawToolTip) + m_toolTipTimer->start (500); + + // Update 2d position + m_pos = ev->pos(); + m_globalpos = ev->globalPos(); + + // Calculate 3d position of the cursor + m_hoverpos = (camera() != EFreeCamera) ? coordconv2_3 (m_pos, true) : g_origin; + + // Update rect vertices since m_hoverpos may have changed + updateRectVerts(); + + update(); +} + +// ============================================================================= +// +void GLRenderer::keyPressEvent (QKeyEvent* ev) +{ + m_keymods = ev->modifiers(); +} + +// ============================================================================= +// +void GLRenderer::keyReleaseEvent (QKeyEvent* ev) +{ + m_keymods = ev->modifiers(); +} + +// ============================================================================= +// +void GLRenderer::wheelEvent (QWheelEvent* ev) +{ + makeCurrent(); + + zoomNotch (ev->delta() > 0); + zoom() = clamp (zoom(), 0.01, 10000.0); + + update(); + ev->accept(); +} + +// ============================================================================= +// +void GLRenderer::leaveEvent (QEvent* ev) +{ + (void) ev; + m_drawToolTip = false; + m_toolTipTimer->stop(); + update(); +} + +// ============================================================================= +// +void GLRenderer::contextMenuEvent (QContextMenuEvent* ev) +{ + g_win->spawnContextMenu (ev->globalPos()); +} + +// ============================================================================= +// +void GLRenderer::setCamera (const GLRenderer::EFixedCamera cam) +{ + m_camera = cam; + gl_camera = (int) cam; + g_win->updateEditModeActions(); +} + +// ============================================================================= +// +void GLRenderer::pick (int mouseX, int mouseY) +{ + makeCurrent(); + + // Use particularly thick lines while picking ease up selecting lines. + glLineWidth (max (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::setFile (LDDocument* const& a) +{ + m_File = 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& verts = m_drawedVerts; + LDObjectList objs; + + switch (getEditMode()) + { + case EDrawMode: + { + if (m_rectdraw) + { + LDQuad* quad = new LDQuad; + + // Copy the vertices from m_rectverts + updateRectVerts(); + + for (int i = 0; i < quad->vertices(); ++i) + quad->setVertex (i, m_rectverts[i]); + + quad->setColor (maincolor); + objs << quad; + } + else + { + switch (verts.size()) + { + case 1: + { + // 1 vertex - add a vertex object + LDVertex* obj = new LDVertex; + obj->pos = verts[0]; + obj->setColor (maincolor); + objs << obj; + } break; + + case 2: + { + // 2 verts - make a line + LDLine* obj = new LDLine (verts[0], verts[1]); + obj->setColor (edgecolor); + objs << obj; + } break; + + case 3: + case 4: + { + LDObject* obj = (verts.size() == 3) ? + static_cast (new LDTriangle) : + static_cast (new LDQuad); + + obj->setColor (maincolor); + + for (int i = 0; i < obj->vertices(); ++i) + obj->setVertex (i, verts[i]); + + objs << obj; + } break; + } + } + } break; + + case ECircleMode: + { + const int segs = lores, divs = lores; // TODO: make customizable + double dist0 = getCircleDrawDist (0), + dist1 = getCircleDrawDist (1); + LDDocument* refFile = null; + Matrix transform; + bool circleOrDisc = false; + + if (dist1 < dist0) + std::swap (dist0, dist1); + + if (dist0 == dist1) + { + // If the radii are the same, there's no ring space to fill. Use a circle. + refFile = ::getDocument ("4-4edge.dat"); + transform = getCircleDrawMatrix (dist0); + circleOrDisc = true; + } + elif (dist0 == 0 || dist1 == 0) + { + // If either radii is 0, use a disc. + refFile = ::getDocument ("4-4disc.dat"); + transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); + circleOrDisc = true; + } + elif (g_RingFinder (dist0, dist1)) + { + // The ring finder found a solution, use that. Add the component rings to the file. + for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents()) + { + // Get a ref file for this primitive. If we cannot find it in the + // LDraw library, generate it. + if ((refFile = ::getDocument (radialFileName (::Ring, lores, lores, cmp.num))) == null) + { + refFile = generatePrimitive (::Ring, lores, lores, cmp.num); + refFile->setImplicit (false); + } + + LDSubfile* ref = new LDSubfile; + ref->setFileInfo (refFile); + ref->setTransform (getCircleDrawMatrix (cmp.scale)); + ref->setPosition (m_drawedVerts[0]); + ref->setColor (maincolor); + objs << ref; + } + } + else + { + // Ring finder failed, last resort: draw the ring with quads + QList 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) + { + getFile()->addObject (obj); + compileObject (obj); + } + + g_win->refresh(); + g_win->endAction(); + } + + m_drawedVerts.clear(); + m_rectdraw = false; +} + +// ============================================================================= +// +double GLRenderer::getCircleDrawDist (int pos) const +{ + assert (m_drawedVerts.size() >= pos + 1); + const Vertex& v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : m_hoverpos; + Axis relX, relY; + getRelativeAxes (relX, relY); + + const double dx = m_drawedVerts[0][relX] - v1[relX]; + const double dy = m_drawedVerts[0][relY] - v1[relY]; + return sqrt ((dx * dx) + (dy * dy)); +} + +// ============================================================================= +// +void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const +{ + const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; + relX = cam->axisX; + relY = cam->axisY; +} + +// ============================================================================= +// +static QList getVertices (LDObject* obj) +{ + QList verts; + + if (obj->vertices() >= 2) + { + for (int i = 0; i < obj->vertices(); ++i) + verts << obj->getVertex (i); + } elif (obj->getType() == LDObject::ESubfile) + { + LDSubfile* ref = static_cast (obj); + LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline); + + for (LDObject* obj : objs) + { + verts << getVertices (obj); + obj->deleteSelf(); + } + } + + 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 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 (getFile() == null || m_width == -1 || m_height == -1) + { + zoom() = 30.0f; + return; + } + + bool lastfilled = false; + bool firstrun = true; + const uint32 white = 0xFFFFFFFF; + bool inward = true; + const int w = m_width, h = m_height; + 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 (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) || getEditMode() != ESelectMode) + return; + + pick (ev->x(), ev->y()); + + if (selection().isEmpty()) + return; + + LDObject* obj = selection().first(); + AddObjectDialog::staticDialog (obj->getType(), obj); + g_win->endAction(); + ev->accept(); +} + +// ============================================================================= +// +LDOverlay* GLRenderer::findOverlayObject (EFixedCamera cam) +{ + LDOverlay* ovlobj = null; + + for (LDObject* obj : getFile()->getObjects()) + { + if (obj->getType() == LDObject::EOverlay && static_cast (obj)->getCamera() == cam) + { + ovlobj = static_cast (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->getFileName())) + setupOverlay (cam, ovlobj->getFileName(), ovlobj->getX(), + ovlobj->getY(), ovlobj->getWidth(), ovlobj->getHeight()); + } +} + +// ============================================================================= +// +void GLRenderer::updateOverlayObjects() +{ + for (EFixedCamera cam : g_Cameras) + { + if (cam == EFreeCamera) + continue; + + LDGLOverlay& meta = 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->getType() == LDObject::EEmpty) + nextobj->deleteSelf(); + + // If the overlay object was there and the overlay itself is + // not, remove the object. + ovlobj->deleteSelf(); + } elif (meta.img && !ovlobj) + { + // Inverse case: image is there but the overlay object is + // not, thus create the object. + ovlobj = new LDOverlay; + + // Find a suitable position to place this object. We want to place + // this into the header, which is everything up to the first scemantic + // object. If we find another overlay object, place this object after + // the last one found. Otherwise, place it before the first schemantic + // object and put an empty object after it (though don't do this if + // there was no schemantic elements at all) + int i, lastOverlay = -1; + bool found = false; + + for (i = 0; i < getFile()->getObjectCount(); ++i) + { + LDObject* obj = getFile()->getObject (i); + + if (obj->isScemantic()) + { + found = true; + break; + } + + if (obj->getType() == LDObject::EOverlay) + lastOverlay = i; + } + + if (lastOverlay != -1) + getFile()->insertObj (lastOverlay + 1, ovlobj); + else + { + getFile()->insertObj (i, ovlobj); + + if (found) + getFile()->insertObj (i + 1, new LDEmpty); + } + } + + if (meta.img && ovlobj) + { + ovlobj->setCamera (cam); + ovlobj->setFileName (meta.fname); + ovlobj->setX (meta.ox); + ovlobj->setY (meta.oy); + ovlobj->setWidth (meta.lw); + ovlobj->setHeight (meta.lh); + } + } + + if (g_win->R() == this) + g_win->refresh(); +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/GLRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/GLRenderer.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,309 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_GLDRAW_H +#define LDFORGE_GLDRAW_H + +#include +#include "Main.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 +{ + typedefs: + 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; + }; + + properties: + Q_OBJECT + PROPERTY (public, bool, DrawOnly, BOOL_OPS, STOCK_WRITE) + PROPERTY (public, MessageManager*, MessageLog, NO_OPS, STOCK_WRITE) + PROPERTY (private, bool, Picking, BOOL_OPS, STOCK_WRITE) + PROPERTY (public, LDDocument*, File, NO_OPS, CUSTOM_WRITE) + PROPERTY (public, EditMode, EditMode, NO_OPS, 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 m_drawedVerts; + bool m_rectdraw; + Vertex m_rectverts[4]; + QColor m_bgcolor; + QList 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 *getFile()->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()]; + } + + 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]; + +#endif // LDFORGE_GLDRAW_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/LDConfig.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/LDConfig.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,214 @@ +/* + * 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 . + */ + +#include +#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); +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/LDConfig.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/LDConfig.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,58 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_LDCONFIG_H +#define LDFORGE_LDCONFIG_H + +#include "Types.h" +#include + +// ============================================================================= +// LDConfigParser +// +// 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(); + +#endif // LDFORGE_LDCONFIG_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/LDObject.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/LDObject.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,835 @@ +/* + * 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 . + */ + +#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_Hidden (false), + m_Selected (false), + m_Parent (null), + m_File (null), + m_GLInit (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->getID() >= id) + id = obj->getID() + 1; + } + + setID (id); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::setVertexCoord (int i, Axis ax, double value) +{ + Vertex v = getVertex (i); + v[ax] = value; + setVertex (i, v); +} + +LDError::LDError() {} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString LDComment::raw() const +{ + return fmt ("0 %1", text); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString LDSubfile::raw() const +{ + QString val = fmt ("1 %1 %2 ", getColor(), getPosition()); + val += getTransform().toString(); + val += ' '; + val += getFileInfo()->getName(); + return val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString LDLine::raw() const +{ + QString val = fmt ("2 %1", getColor()); + + for (int i = 0; i < 2; ++i) + val += fmt (" %1", getVertex (i)); + + return val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString LDTriangle::raw() const +{ + QString val = fmt ("3 %1", getColor()); + + for (int i = 0; i < 3; ++i) + val += fmt (" %1", getVertex (i)); + + return val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString LDQuad::raw() const +{ + QString val = fmt ("4 %1", getColor()); + + for (int i = 0; i < 4; ++i) + val += fmt (" %1", getVertex (i)); + + return val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString LDCondLine::raw() const +{ + QString val = fmt ("5 %1", getColor()); + + // Add the coordinates + for (int i = 0; i < 4; ++i) + val += fmt (" %1", getVertex (i)); + + return val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString LDError::raw() const +{ + return contents; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString LDVertex::raw() const +{ + return fmt ("0 !LDFORGE VERTEX %1 %2", getColor(), pos); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString LDEmpty::raw() const +{ + return ""; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +const char* LDBFC::statements[] = +{ + "CERTIFY CCW", + "CCW", + "CERTIFY CW", + "CW", + "NOCERTIFY", + "INVERTNEXT", + "CLIP", + "CLIP CCW", + "CLIP CW", + "NOCLIP", +}; + +QString LDBFC::raw() const +{ + return fmt ("0 BFC %1", LDBFC::statements[type]); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QList LDQuad::splitToTriangles() +{ + // Create the two triangles based on this quadrilateral: + // 0---3 0---3 3 + // | | | / /| + // | | ==> | / / | + // | | |/ / | + // 1---2 1 1---2 + LDTriangle* tri1 = new LDTriangle (getVertex (0), getVertex (1), getVertex (3)); + LDTriangle* tri2 = new LDTriangle (getVertex (1), getVertex (2), getVertex (3)); + + // The triangles also inherit the quad's color + tri1->setColor (getColor()); + tri2->setColor (getColor()); + + QList triangles; + triangles << tri1; + triangles << tri2; + return triangles; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::replace (LDObject* other) +{ + long idx = getIndex(); + assert (idx != -1); + + // Replace the instance of the old object with the new object + getFile()->setObject (idx, other); + + // Remove the old object + deleteSelf(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::swap (LDObject* other) +{ + assert (getFile() == other->getFile()); + getFile()->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::deleteSelf() +{ + // If this object was selected, unselect it now + if (isSelected()) + unselect(); + + // If this object was associated to a file, remove it off it now + if (getFile()) + getFile()->forgetObject (this); + + // Delete the GL lists + GL::deleteLists (this); + + // Remove this object from the list of LDObjects + g_LDObjects.removeOne (this); + + delete this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void transformObject (LDObject* obj, Matrix transform, Vertex pos, int parentcolor) +{ + switch (obj->getType()) + { + case LDObject::ELine: + case LDObject::ECondLine: + case LDObject::ETriangle: + case LDObject::EQuad: + + for (int i = 0; i < obj->vertices(); ++i) + { + Vertex v = obj->getVertex (i); + v.transform (transform, pos); + obj->setVertex (i, v); + } + + break; + + case LDObject::ESubfile: + { + LDSubfile* ref = static_cast (obj); + Matrix newMatrix = transform * ref->getTransform(); + Vertex newpos = ref->getPosition(); + + newpos.transform (transform, pos); + ref->setPosition (newpos); + ref->setTransform (newMatrix); + } + break; + + default: + break; + } + + if (obj->getColor() == maincolor) + obj->setColor (parentcolor); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObjectList LDSubfile::inlineContents (InlineFlags flags) +{ + LDObjectList objs = getFileInfo()->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, getTransform(), getPosition(), getColor()); + } + + return objs; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +long LDObject::getIndex() const +{ + assert (getFile() != null); + + for (int i = 0; i < getFile()->getObjectCount(); ++i) + if (getFile()->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]->getFile(); + + for (long i = start; i != end; i += incr) + { + LDObject* obj = objs[i]; + + const long idx = obj->getIndex(), + target = idx + (up ? -1 : 1); + + if ( (up && idx == 0) || (!up && idx == (long) (file->getObjects().size() - 1))) + { + // One of the objects hit the extrema. If this happens, this should be the first + // object to be iterated on. Thus, nothing has changed yet and it's safe to just + // abort the entire operation. + assert (i == start); + return; + } + + objsToCompile << obj; + objsToCompile << file->getObject (target); + + obj->swap (file->getObject (target)); + } + + removeDuplicates (objsToCompile); + + // The objects need to be recompiled, otherwise their pick lists are left with + // the wrong index colors which messes up selection. + for (LDObject* obj : objsToCompile) + g_win->R()->compileObject (obj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString LDObject::typeName (LDObject::Type type) +{ + LDObject* obj = LDObject::getDefault (type); + QString name = obj->getTypeName(); + obj->deleteSelf(); + 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->getType() == objType) + count++; + + if (count == 0) + continue; + + if (!firstDetails) + text += ", "; + + QString noun = fmt ("%1%2", typeName (objType), plural (count)); + + // Plural of "vertex" is "vertices", correct that + if (objType == EVertex && count != 1) + noun = "vertices"; + + text += fmt ("%1 %2", count, noun); + firstDetails = false; + } + + return text; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject* LDObject::topLevelParent() +{ + if (!getParent()) + return this; + + LDObject* it = this; + + while (it->getParent()) + it = it->getParent(); + + return it; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject* LDObject::next() const +{ + long idx = getIndex(); + assert (idx != -1); + + if (idx == (long) getFile()->getObjectCount() - 1) + return null; + + return getFile()->getObject (idx + 1); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject* LDObject::prev() const +{ + long idx = getIndex(); + assert (idx != -1); + + if (idx == 0) + return null; + + return getFile()->getObject (idx - 1); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::move (Vertex vect) +{ + if (hasMatrix()) + { + LDMatrixObject* mo = dynamic_cast (this); + mo->setPosition (mo->getPosition() + vect); + } + elif (getType() == LDObject::EVertex) + { + // ugh + static_cast (this)->pos += vect; + } + else + { + for (int i = 0; i < vertices(); ++i) + setVertex (i, getVertex (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 = getVertex (1); + setVertex (1, getVertex (2)); + setVertex (2, tmp); + + return; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDQuad::invert() +{ + // Quad: 0 -> 1 -> 2 -> 3 + // rev: 0 -> 3 -> 2 -> 1 + // Thus, we swap 1 and 3. + Vertex tmp = getVertex (1); + setVertex (1, getVertex (3)); + setVertex (3, tmp); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDSubfile::invert() +{ + // Subfiles are inverted when they're prefixed with + // a BFC INVERTNEXT statement. Thus we need to toggle this status. + // For flat primitives it's sufficient that the determinant is + // flipped but I don't have a method for checking flatness yet. + // Food for thought... + + int idx = getIndex(); + + if (idx > 0) + { + LDBFC* bfc = dynamic_cast (prev()); + + if (bfc && bfc->type == LDBFC::InvertNext) + { + // This is prefixed with an invertnext, thus remove it. + bfc->deleteSelf(); + return; + } + } + + // Not inverted, thus prefix it with a new invertnext. + LDBFC* bfc = new LDBFC (LDBFC::InvertNext); + getFile()->insertObj (idx, bfc); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void invertLine (LDObject* line) +{ + // For lines, we swap the vertices. I don't think that a + // cond-line's control points need to be swapped, do they? + Vertex tmp = line->getVertex (0); + line->setVertex (0, line->getVertex (1)); + line->setVertex (1, tmp); +} + +void LDLine::invert() +{ + invertLine (this); +} + +void LDCondLine::invert() +{ + invertLine (this); +} + +void LDVertex::invert() {} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDLine* LDCondLine::demote() +{ + LDLine* repl = new LDLine; + + for (int i = 0; i < repl->vertices(); ++i) + repl->setVertex (i, getVertex (i)); + + repl->setColor (getColor()); + + replace (repl); + return repl; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject* LDObject::fromID (int id) +{ + for (LDObject* obj : g_LDObjects) + if (obj->getID() == id) + return obj; + + return null; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString LDOverlay::raw() const +{ + return fmt ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6", + getFileName(), getCamera(), getX(), getY(), getWidth(), getHeight()); +} + +void LDOverlay::invert() {} + +// ============================================================================= +// Hook the set accessors of certain properties to this changeProperty function. +// It takes care of history management so we can capture low-level changes, this +// makes history stuff work out of the box. +// ----------------------------------------------------------------------------- +template static void changeProperty (LDObject* obj, T* ptr, const T& val) +{ + long idx; + + if (*ptr == val) + return; + + if (obj->getFile() && (idx = obj->getIndex()) != -1) + { + QString before = obj->raw(); + *ptr = val; + QString after = obj->raw(); + + if (before != after) + obj->getFile()->addToHistory (new EditHistory (idx, before, after)); + } + else + *ptr = val; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::setColor (const int& val) +{ + changeProperty (this, &m_Color, val); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +const Vertex& LDObject::getVertex (int i) const +{ + return m_coords[i]->data(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::setVertex (int i, const Vertex& vert) +{ + changeProperty (this, &m_coords[i], LDSharedVertex::getSharedVertex (vert)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDMatrixObject::setPosition (const Vertex& a) +{ + changeProperty (getLinkPointer(), &m_Position, LDSharedVertex::getSharedVertex (a)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDMatrixObject::setTransform (const Matrix& val) +{ + changeProperty (getLinkPointer(), &m_Transform, val); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static QMap g_sharedVerts; + +LDSharedVertex* LDSharedVertex::getSharedVertex (const Vertex& a) +{ + auto it = g_sharedVerts.find (a); + + if (it == g_sharedVerts.end()) + { + LDSharedVertex* v = new LDSharedVertex (a); + g_sharedVerts[a] = v; + return v; + } + + return *it; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDSharedVertex::addRef (LDObject* a) +{ + m_refs << a; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDSharedVertex::delRef (LDObject* a) +{ + m_refs.removeOne (a); + + if (m_refs.empty()) + { + g_sharedVerts.remove (m_data); + delete this; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::select() +{ + if (!getFile()) + { + log ("Warning: Object #%1 cannot be selected as it is not assigned a file!\n", getID()); + return; + } + + getFile()->addToSelection (this); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::unselect() +{ + if (!getFile()) + { + log ("Warning: Object #%1 cannot be unselected as it is not assigned a file!\n", getID()); + return; + } + + getFile()->removeFromSelection (this); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString getLicenseText (int id) +{ + switch (id) + { + case 0: + return CALicense; + + case 1: + return 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 (getColor()); + + if (hasMatrix()) + { + LDMatrixObject* copyMo = static_cast (copy); + const LDMatrixObject* mo = static_cast (this); + copyMo->setPosition (mo->getPosition()); + copyMo->setTransform (mo->getTransform()); + } + else + { + for (int i = 0; i < vertices(); ++i) + copy->setVertex (getVertex (i)); + } + + switch (getType()) + { + case Subfile: + { + LDSubfile* copyRef = static_cast (copy); + const LDSubfile* ref = static_cast (this); + + copyRef->setFileInfo (ref->getFileInfo()); + } + } + */ + + LDObject* copy = parseLine (raw()); + return copy; +} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/LDObject.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/LDObject.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,570 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_LDTYPES_H +#define LDFORGE_LDTYPES_H + +#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 getType() const override \ + { \ + return LDObject::E##T; \ + } \ + virtual QString raw() const override; \ + virtual void invert() override; + +#define LDOBJ_NAME(N) virtual QString getTypeName() 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, Hidden, BOOL_OPS, STOCK_WRITE) + PROPERTY (public, bool, Selected, BOOL_OPS, STOCK_WRITE) + PROPERTY (public, LDObject*, Parent, NO_OPS, STOCK_WRITE) + PROPERTY (public, LDDocument*, File, NO_OPS, STOCK_WRITE) // TODO: rename~ + PROPERTY (private, int, ID, NUM_OPS, STOCK_WRITE) + PROPERTY (public, int, Color, NUM_OPS, CUSTOM_WRITE) + PROPERTY (public, bool, GLInit, BOOL_OPS, 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 deleteSelf(); + + // Index (i.e. line number) of this object + long getIndex() const; + + // Type enumerator of this object + virtual Type getType() const = 0; + + // Get a vertex by index + const Vertex& getVertex (int i) const; + + // Type name of this object + virtual QString getTypeName() 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* prev() const; + + // This object as LDraw code + virtual QString raw() 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; +}; + +// ============================================================================= +// LDMatrixObject +// ============================================================================= +// +// 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, NO_OPS, STOCK_WRITE) + PROPERTY (public, Matrix, Transform, NO_OPS, 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& getPosition() const + { + return m_Position->data(); + } + + void setCoordinate (const Axis ax, double value) + { + Vertex v = getPosition(); + v[ax] = value; + setPosition (v); + } + + void setPosition (const Vertex& a); + + private: + LDSharedVertex* m_Position; +}; + +// ============================================================================= +// LDError +// +// 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. The member +// zContent contains the contents of the unparsable line. +// ============================================================================= +class LDError : public LDObject +{ + LDOBJ (Error) + LDOBJ_NAME (error) + LDOBJ_VERTICES (0) + LDOBJ_UNCOLORED + LDOBJ_SCEMANTIC + LDOBJ_NO_MATRIX + PROPERTY (public, QString, FileReferenced, STR_OPS, STOCK_WRITE) + + public: + LDError(); + LDError (QString contents, QString reason) : contents (contents), reason (reason) {} + + // Content of this unknown line + QString contents; + + // Why is this gibberish? + QString reason; +}; + +// ============================================================================= +// LDEmpty +// +// 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 +}; + +// ============================================================================= +// LDComment +// +// Represents a code-0 comment in the LDraw code file. Member text contains +// the text of the comment. +// ============================================================================= +class LDComment : public LDObject +{ + LDOBJ (Comment) + LDOBJ_NAME (comment) + LDOBJ_VERTICES (0) + LDOBJ_UNCOLORED + LDOBJ_NON_SCEMANTIC + LDOBJ_NO_MATRIX + + public: + LDComment() {} + LDComment (QString text) : text (text) {} + + QString text; // The text of this comment +}; + +// ============================================================================= +// LDBFC +// +// Represents a 0 BFC statement in the LDraw code. eStatement contains the type +// of this statement. +// ============================================================================= +class LDBFC : public LDObject +{ + public: + enum Type + { + 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 (type == InvertNext); } + LDOBJ_NO_MATRIX + + public: + LDBFC() {} + LDBFC (const LDBFC::Type type) : + type (type) {} + + // Statement strings + static const char* statements[]; + + Type type; +}; + +// ============================================================================= +// 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, NO_OPS, 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 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, NUM_OPS, STOCK_WRITE) + PROPERTY (public, int, X, NUM_OPS, STOCK_WRITE) + PROPERTY (public, int, Y, NUM_OPS, STOCK_WRITE) + PROPERTY (public, int, Width, NUM_OPS, STOCK_WRITE) + PROPERTY (public, int, Height, NUM_OPS, STOCK_WRITE) + PROPERTY (public, QString, FileName, STR_OPS, STOCK_WRITE) +}; + +// Other common LDraw stuff +static const QString CALicense = "!LICENSE Redistributable under CCAL version 2.0 : see CAreadme.txt", + NonCALicense = "!LICENSE Not redistributable : see NonCAreadme.txt"; +static const int lores = 16; +static const int hires = 48; + +QString getLicenseText (int id); + +#endif // LDFORGE_LDTYPES_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Main.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Main.cc Tue Jan 21 02:03:27 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 . + */ + +#include +#include +#include +#include +#include +#include +#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 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()) + { + log ("Creating configuration file...\n"); + + if (Config::save()) + log ("Configuration file successfully created.\n"); + else + log ("failed to create configuration file!\n"); + } + + LDPaths::initPaths(); + initColors(); + 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(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void doPrint (QFile& f, QList args) +{ + QString msg = DoFormat (args); + f.write (msg.toUtf8()); + f.flush(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void doPrint (FILE* fp, QList args) +{ + QString msg = DoFormat (args); + fwrite (msg.toStdString().c_str(), 1, msg.length(), fp); + fflush (fp); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString versionString() +{ + if (g_versionString.length() == 0) + { +#if VERSION_PATCH == 0 + g_versionString = fmt ("%1.%2", VERSION_MAJOR, VERSION_MINOR); +#else + g_versionString = fmt ("%1.%2.%3", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); +#endif // VERSION_PATCH + } + + return g_versionString; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString fullVersionString() +{ +#if BUILD_ID != BUILD_RELEASE && defined (GIT_DESCRIBE) + return GIT_DESCRIBE; +#else + return "v" + versionString(); +#endif +} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Main.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Main.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,125 @@ +/* + * 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 . + */ + +// ============================================================================= +// This file is included one way or another in every source file of LDForge. +// Stuff defined and included here is universally included. + +#ifndef LDFORGE_MAIN_H +#define LDFORGE_MAIN_H + +#include +#include +#include +#include +#include +#include + +#include "PropertyMacro.h" +#include "Configuration.h" + +#define APPNAME "LDForge" +#define UNIXNAME "ldforge" +#define VERSION_MAJOR 0 +#define VERSION_MINOR 2 +#define VERSION_PATCH 999 +#define BUILD_ID BUILD_INTERNAL + +#define BUILD_INTERNAL 0 +#define BUILD_RELEASE 1 + +// ============================================= +#ifdef DEBUG +# undef RELEASE +#endif // DEBUG + +#ifdef RELEASE +# undef DEBUG +#endif // RELEASE + +// ============================================= +#define alias auto& +#define elif(A) else if (A) + +// Null pointer +static const std::nullptr_t null = nullptr; + +#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__ + +#ifdef IN_IDE_PARSER +void dlog(void, ...) {} +#else +# ifdef DEBUG +# define dlog(...) log (QString (__PRETTY_FUNCTION__) + ": " __VA_ARGS__) +# else +# define dlog(...) +# endif // DEBUG +#endif // IN_IDE_PARSER + +#define dvalof(A) dlog ("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. +void assertionFailure (const char* file, int line, const char* funcname, const char* expr); + +#undef assert + +#ifdef DEBUG +# define assert(N) { ((N) ? (void) 0 : assertionFailure (__FILE__, __LINE__, FUNCNAME, #N)); } +#else +# define assert(N) {} +#endif // DEBUG + +// Version string identifier +QString versionString(); +QString fullVersionString(); + +#define properties private +#define typedefs public +#define for_axes(AX) for (const Axis AX : std::initializer_list ({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 + +#endif // LDFORGE_MAIN_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/MainWindow.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/MainWindow.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,1010 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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" +#include "moc_MainWindow.cpp" + +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() +{ + g_win = this; + m_renderer = new GLRenderer; + + ui = new Ui_LDForgeUI; + ui->setupUi (this); + + // Stuff the renderer into its frame + QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame); + rendererLayout->addWidget (R()); + + connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged())); + connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*))); + connect (ui->fileList, SIGNAL (currentItemChanged (QListWidgetItem*, QListWidgetItem*)), this, SLOT (changeCurrentFile())); + + // Init message log manager + m_msglog = new MessageManager; + m_msglog->setRenderer (R()); + m_renderer->setMessageLog (m_msglog); + m_quickColors = quickColorsFromConfig(); + slot_selectionChanged(); + setStatusBar (new QStatusBar); + + // Make certain actions checkable + ui->actionAxes->setChecked (gl_axes); + ui->actionWireframe->setChecked (gl_wireframe); + ui->actionBFCView->setChecked (gl_colorbfc); + updateGridToolBar(); + updateEditModeActions(); + updateRecentFilesMenu(); + updateToolBars(); + updateTitle(); + updateActionShortcuts(); + + setMinimumSize (300, 200); + + connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup())); + + // Connect all actions + for (QAction* act : findChildren()) + if (!act->objectName().isEmpty()) + connect (act, SIGNAL (triggered()), this, SLOT (slot_action())); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +KeySequenceConfig* MainWindow::shortcutForAction (QAction* act) +{ + QString keycfgname = fmt ("key_%1", act->objectName()); + return KeySequenceConfig::getByName (keycfgname); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void MainWindow::updateActionShortcuts() +{ + for (QAction* act : findChildren()) + { + 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. + QString methodName = fmt ("slot_%1", sender()->objectName()); + +#ifdef DEBUG + log ("Action %1 triggered", sender()->objectName()); +#endif + + // Now invoke this slot to call the action. + QMetaObject::invokeMethod (this, methodName.toAscii().constData(), 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 quickColorsFromConfig() +{ + QList 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::updateToolBars() +{ + m_colorButtons.clear(); + ui->colorToolbar->clear(); + + for (LDQuickColor& entry : m_quickColors) + { + if (entry.isSeparator()) + ui->colorToolbar->addSeparator(); + else + { + QToolButton* colorButton = new QToolButton; + colorButton->setIcon (makeColorIcon (entry.getColor(), 22)); + colorButton->setIconSize (QSize (22, 22)); + colorButton->setToolTip (entry.getColor()->name); + + connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor())); + ui->colorToolbar->addWidget (colorButton); + m_colorButtons << colorButton; + + entry.setToolButton (colorButton); + } + } + + updateGridToolBar(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void 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 = fmt (APPNAME " %1", fullVersionString()); + + // Append our current file if we have one + if (getCurrentDocument()) + { + if (getCurrentDocument()->getName().length() > 0) + title += fmt (": %1", basename (getCurrentDocument()->getName())); + else + title += fmt (": "); + + if (getCurrentDocument()->getObjectCount() > 0 && + getCurrentDocument()->getObject (0)->getType() == LDObject::EComment) + { + // Append title + LDComment* comm = static_cast (getCurrentDocument()->getObject (0)); + title += fmt (": %1", comm->text); + } + + if (getCurrentDocument()->getHistory()->getPosition() != getCurrentDocument()->getSavePosition()) + title += '*'; + } + +#ifdef DEBUG + title += " [debug build]"; +#elif BUILD_ID != BUILD_RELEASE + title += " [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->deleteSelf(); + + 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()->getObjects()) + { + QString descr; + + switch (obj->getType()) + { + case LDObject::EComment: + { + descr = static_cast (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->getVertex (i).toString (true); + } + } break; + + case LDObject::EError: + { + descr = fmt ("ERROR: %1", obj->raw()); + } break; + + case LDObject::EVertex: + { + descr = static_cast (obj)->pos.toString (true); + } break; + + case LDObject::ESubfile: + { + LDSubfile* ref = static_cast (obj); + + descr = fmt ("%1 %2, (", ref->getFileInfo()->getDisplayName(), ref->getPosition().toString (true)); + + for (int i = 0; i < 9; ++i) + descr += fmt ("%1%2", ref->getTransform()[i], (i != 8) ? " " : ""); + + descr += ')'; + } break; + + case LDObject::EBFC: + { + descr = LDBFC::statements[static_cast (obj)->type]; + } break; + + case LDObject::EOverlay: + { + LDOverlay* ovl = static_cast (obj); + descr = fmt ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->getCamera()], + basename (ovl->getFileName()), ovl->getX(), ovl->getY(), + ovl->getWidth(), ovl->getHeight()); + } + break; + + default: + { + descr = obj->getTypeName(); + } break; + } + + QListWidgetItem* item = new QListWidgetItem (descr); + item->setIcon (getIcon (obj->getTypeName())); + + // Use italic font if hidden + if (obj->isHidden()) + { + QFont font = item->font(); + font.setItalic (true); + item->setFont (font); + } + + // Color gibberish orange on red so it stands out. + if (obj->getType() == LDObject::EError) + { + item->setBackground (QColor ("#AA0000")); + item->setForeground (QColor ("#FFAA00")); + } + elif (lv_colorize && obj->isColored() && obj->getColor() != maincolor && obj->getColor() != edgecolor) + { + // If the object isn't in the main or edge color, draw this + // list entry in said color. + LDColor* col = getColor (obj->getColor()); + + if (col) + item->setForeground (col->faceColor); + } + + obj->qObjListEntry = item; + ui->objectList->insertItem (ui->objectList->count(), item); + } + + g_isSelectionLocked = false; + updateSelection(); + scrollToSelection(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void 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 items = ui->objectList->selectedItems(); + + for (LDObject* obj : getCurrentDocument()->getObjects()) + { + 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 (sender()); + openMainFile (qAct->text()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void MainWindow::slot_quickColor() +{ + QToolButton* button = static_cast (sender()); + LDColor* col = null; + + for (const LDQuickColor& entry : m_quickColors) + { + if (entry.getToolButton() == button) + { + col = entry.getColor(); + break; + } + } + + if (col == null) + return; + + int newColor = col->index; + + for (LDObject* obj : selection()) + { + if (obj->isColored() == false) + continue; // uncolored object + + obj->setColor (newColor); + R()->compileObject (obj); + } + + endAction(); + refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int MainWindow::getInsertionPoint() +{ + // If we have a selection, put the item after it. + if (!selection().isEmpty()) + return selection().last()->getIndex() + 1; + + // Otherwise place the object at the end. + return getCurrentDocument()->getObjectCount(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void MainWindow::doFullRefresh() +{ + buildObjList(); + m_renderer->hardRefresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void MainWindow::refresh() +{ + buildObjList(); + m_renderer->update(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void MainWindow::updateSelection() +{ + g_isSelectionLocked = true; + + for (LDObject* obj : getCurrentDocument()->getObjects()) + obj->setSelected (false); + + ui->objectList->clearSelection(); + + for (LDObject* obj : selection()) + { + if (obj->qObjListEntry == null) + continue; + + obj->qObjListEntry->setSelected (true); + obj->setSelected (true); + } + + g_isSelectionLocked = false; + slot_selectionChanged(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int MainWindow::getSelectedColor() +{ + int result = -1; + + for (LDObject* obj : selection()) + { + if (obj->isColored() == false) + continue; // doesn't use color + + if (result != -1 && obj->getColor() != result) + return -1; // No consensus in object color + + if (result == -1) + result = obj->getColor(); + } + + return result; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObject::Type MainWindow::getUniformSelectedType() +{ + LDObject::Type result = LDObject::EUnidentified; + + for (LDObject* obj : selection()) + { + if (result != LDObject::EUnidentified && obj->getColor() != result) + return LDObject::EUnidentified; + + if (result == LDObject::EUnidentified) + result = obj->getType(); + } + + 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->getType() != 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); +} + +// ============================================================================= +// TODO: what the heh? +// ----------------------------------------------------------------------------- +void MainWindow::deleteObjects (LDObjectList objs) +{ + for (LDObject* obj : objs) + obj->deleteSelf(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void MainWindow::deleteByColor (const int colnum) +{ + LDObjectList objs; + + for (LDObject* obj : getCurrentDocument()->getObjects()) + { + if (!obj->isColored() || obj->getColor() != colnum) + continue; + + objs << obj; + } + + deleteObjects (objs); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void MainWindow::updateEditModeActions() +{ + const EditMode mode = R()->getEditMode(); + 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()->getObjects()) + { + if (it->qObjListEntry == listitem) + { + obj = it; + break; + } + } + + AddObjectDialog::staticDialog (obj->getType(), obj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool MainWindow::save (LDDocument* f, bool saveAs) +{ + QString path = f->getFullPath(); + + if (saveAs || path.isEmpty()) + { + QString name = f->getDefaultName(); + + if (!f->getFullPath().isEmpty()) + name = f->getFullPath(); + elif (!f->getName().isEmpty()) + name = f->getName(); + + 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 (f->save (path)) + { + if (f == getCurrentDocument()) + updateTitle(); + + log ("Saved to %1.", path); + + // Add it to recent files + addRecentFile (path); + return true; + } + + QString message = fmt (tr ("Failed to save to %1: %2"), path, strerror (errno)); + + // Tell the user the save failed, and give the option for saving as with it. + QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win); + + // Add a save-as button + QPushButton* saveAsBtn = new QPushButton (tr ("Save As")); + saveAsBtn->setIcon (getIcon ("file-save-as")); + dlg.addButton (saveAsBtn, QMessageBox::ActionRole); + dlg.setDefaultButton (QMessageBox::Close); + dlg.exec(); + + if (dlg.clickedButton() == saveAsBtn) + return save (f, 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 (fmt (":/icons/%1.png", iconName))); +} + +// ============================================================================= +bool confirm (QString msg) +{ + return confirm (MainWindow::tr ("Confirm"), msg); +} + +bool confirm (QString title, QString msg) +{ + return QMessageBox::question (g_win, title, msg, + (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes; +} + +// ============================================================================= +void critical (QString msg) +{ + QMessageBox::critical (g_win, MainWindow::tr ("Error"), msg, + (QMessageBox::Close), QMessageBox::Close); +} + +// ============================================================================= +QIcon makeColorIcon (LDColor* colinfo, const int size) +{ + // Create an image object and link a painter to it. + QImage img (size, size, QImage::Format_ARGB32); + QPainter paint (&img); + QColor col = colinfo->faceColor; + + if (colinfo->index == maincolor) + { + // Use the user preferences for main color here + col = gl_maincolor; + 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 counts; + + for (LDObject* obj : getCurrentDocument()->getObjects()) + { + if (!obj->isColored()) + continue; + + if (counts.find (obj->getColor()) == counts.end()) + counts[obj->getColor()] = 1; + else + counts[obj->getColor()]++; + } + + box->clear(); + int row = 0; + + for (const auto& pair : counts) + { + LDColor* col = getColor (pair.first); + assert (col != null); + + QIcon ico = makeColorIcon (col, 16); + box->addItem (ico, fmt ("[%1] %2 (%3 object%4)", + pair.first, col->name, pair.second, plural (pair.second))); + box->setItemData (row, pair.first); + + ++row; + } +} + +void MainWindow::updateDocumentList() +{ + ui->fileList->clear(); + + for (LDDocument* f : g_loadedFiles) + { + // Don't list implicit files unless explicitly desired. + if (f->isImplicit() && !gui_implicitfiles) + continue; + + // Add an item to the list for this file and store a pointer to it in + // the file, so we can find files by the list item. + ui->fileList->addItem (""); + QListWidgetItem* item = ui->fileList->item (ui->fileList->count() - 1); + f->setListItem (item); + updateDocumentListItem (f); + } +} + +void MainWindow::updateDocumentListItem (LDDocument* f) +{ + if (f->getListItem() == null) + { + // We don't have a list item for this file, so the list either doesn't + // exist yet or is out of date. Build the list now. + updateDocumentList(); + return; + } + + // If this is the current file, it also needs to be the selected item on + // the list. + if (f == getCurrentDocument()) + ui->fileList->setCurrentItem (f->getListItem()); + + // If we list implicit files, draw them with a shade of gray to make them + // distinct. + if (f->isImplicit()) + f->getListItem()->setForeground (QColor (96, 96, 96)); + + f->getListItem()->setText (f->getDisplayName()); + + // If the Document.has unsaved changes, draw a little icon next to it to mark that. + f->getListItem()->setIcon (f->hasUnsavedChanges() ? getIcon ("file-save") : QIcon()); +} + +// ============================================================================= +// 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() +{ + LDDocument* f = null; + QListWidgetItem* item = ui->fileList->currentItem(); + + // Find the file pointer of the item that was selected. + for (LDDocument* it : g_loadedFiles) + { + if (it->getListItem() == item) + { + f = it; + break; + } + } + + // If we picked the same file we're currently on, we don't need to do + // anything. + if (!f || f == getCurrentDocument()) + return; + + LDDocument::setCurrent (f); +} + +void 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()->getHistory(); + int pos = his->getPosition(); + ui->actionUndo->setEnabled (pos != -1); + ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1); + ui->actionAxes->setChecked (gl_axes); + ui->actionBFCView->setChecked (gl_colorbfc); + 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 getColor() == null; +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/MainWindow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/MainWindow.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,302 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_GUI_H +#define LDFORGE_GUI_H + +#include +#include +#include +#include +#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, NO_OPS, STOCK_WRITE) + PROPERTY (public, QToolButton*, ToolButton, NO_OPS, STOCK_WRITE) + + public: + LDQuickColor (LDColor* color, QToolButton* toolButton); + bool isSeparator() const; + + static LDQuickColor getSeparator(); +}; + +// ============================================================================= +// ObjectList +// +// Object list class for MainWindow +// ============================================================================= +class ObjectList : public QListWidget +{ + Q_OBJECT + + protected: + void contextMenuEvent (QContextMenuEvent* ev); +}; + +// ============================================================================= +// MainWindow +// +// The one main GUI class. Hosts the renderer, object list, message log. Contains +// slot_action, which is what all actions connect to. Manages menus and toolbars. +// Large and in charge. +// ============================================================================= +class MainWindow : public QMainWindow +{ + Q_OBJECT + + public: + MainWindow(); + void buildObjList(); + void updateTitle(); + void doFullRefresh(); + void refresh(); + int getInsertionPoint(); + void updateToolBars(); + void updateRecentFilesMenu(); + void updateSelection(); + void updateGridToolBar(); + void updateEditModeActions(); + void updateDocumentList(); + void updateDocumentListItem (LDDocument* f); + int getSelectedColor(); + LDObject::Type getUniformSelectedType(); + void scrollToSelection(); + void spawnContextMenu (const QPoint pos); + void deleteObjects (LDObjectList objs); + int deleteSelection(); + void deleteByColor (const int colnum); + bool save (LDDocument* f, bool saveAs); + void updateActions(); + + inline GLRenderer* R() + { + return m_renderer; + } + + inline void setQuickColors (QList& colors) + { + m_quickColors = colors; + } + + void addMessage (QString msg); + void refreshObjectList(); + void updateActionShortcuts(); + KeySequenceConfig* shortcutForAction (QAction* act); + 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 m_quickColors; + QList m_colorButtons; + QList m_recentFiles; + MessageManager* m_msglog; + Ui_LDForgeUI* ui; + + 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; + +// ----------------------------------------------------------------------------- +// Other GUI-related stuff not directly part of MainWindow: +QPixmap getIcon (QString iconName); // Get an icon from the resource dir +QList quickColorsFromConfig(); // Make a list of quick colors based on config +bool confirm (QString title, QString msg); // Generic confirm prompt +bool confirm (QString msg); // Generic confirm prompt +void critical (QString msg); // Generic error prompt +QIcon makeColorIcon (LDColor* colinfo, const int size); // Makes an icon for the given color +void makeColorComboBox (QComboBox* box); // Fills the given combo-box with color information +QImage imageFromScreencap (uchar* data, int w, int h); + +// ============================================================================= +// ----------------------------------------------------------------------------- +// Takes in pairs of radio buttons and respective values and returns the value of +// the first found radio button that was checked. +// ============================================================================= +template +T radioSwitch (const T& defval, QList> haystack) +{ + for (pair 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 to have the given value. +// ============================================================================= +template +void radioDefault (const T& expr, QList> haystack) +{ + for (pair i : haystack) + { + if (i.second == expr) + { + i.first->setChecked (true); + return; + } + } +} + +#endif // LDFORGE_GUI_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/MessageLog.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/MessageLog.cc Tue Jan 21 02:03:27 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 . + */ + +#include +#include +#include "MessageLog.h" +#include "GLRenderer.h" +#include "MainWindow.h" +#include "moc_MessageLog.cpp" + +static const int g_maxMessages = 5; +static const int g_expiry = 5; +static const int g_fadeTime = 500; // msecs + +// ============================================================================= +// ----------------------------------------------------------------------------- +MessageManager::MessageManager (QObject* parent) : + QObject (parent) +{ + m_ticker = new QTimer; + m_ticker->start (100); + connect (m_ticker, SIGNAL (timeout()), this, SLOT (tick())); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +MessageManager::Line::Line (QString text) : + text (text), + alpha (1.0f), + expiry (QDateTime::currentDateTime().addSecs (g_expiry)) {} + +// ============================================================================= +// Check this line's expiry and update alpha accordingly. Returns true if the +// line is to still stick around, false if it expired. 'changed' is updated to +// whether the line has somehow changed since the last update. +// ----------------------------------------------------------------------------- +bool MessageManager::Line::update (bool& changed) +{ + changed = false; + QDateTime now = QDateTime::currentDateTime(); + int msec = now.msecsTo (expiry); + + if (now >= expiry) + { + // Message line has expired + changed = true; + return false; + } + + if (msec <= g_fadeTime) + { + // Message line has not expired but is fading out + alpha = ( (float) msec) / g_fadeTime; + changed = true; + } + + return true; +} + +// ============================================================================= +// Add a line to the message manager. +// ----------------------------------------------------------------------------- +void MessageManager::addLine (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 (getRenderer()) + getRenderer()->update(); +} + +// ============================================================================= +// Ticks the message manager. All lines are ticked and the renderer scene is +// redrawn if something changed. +// ----------------------------------------------------------------------------- +void MessageManager::tick() +{ + if (m_lines.isEmpty()) + return; + + bool changed = false; + + for (int i = 0; i < m_lines.size(); ++i) + { + bool lineChanged; + + if (!m_lines[i].update (lineChanged)) + m_lines.removeAt (i--); + + changed |= lineChanged; + } + + if (changed && getRenderer()) + getRenderer()->update(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +const QList& MessageManager::getLines() const +{ + return m_lines; +} + +// ============================================================================= +// log() interface - format the argument list and add the resulting string to +// the main message manager. +// ----------------------------------------------------------------------------- +void DoLog (std::initializer_list args) +{ + const QString msg = DoFormat (args); + + for (QString& a : msg.split ("\n", QString::SkipEmptyParts)) + { + if (g_win) + g_win->addMessage (a); + + // Also print it to stdout + fprint (stdout, "%1\n", a); + } +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/MessageLog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/MessageLog.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,70 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_MESSAGELOG_H +#define LDFORGE_MESSAGELOG_H + +#include +#include +#include "Main.h" +#include "Types.h" + +class GLRenderer; +class QTimer; + +/* 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 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, NO_OPS, STOCK_WRITE) + + public: + // Single line of the message log. + class Line + { + public: + Line (QString text); + bool update (bool& changed); + + QString text; + float alpha; + QDateTime expiry; + }; + + explicit MessageManager (QObject* parent = 0); + void addLine (QString line); + const QList& getLines() const; + + private: + QList m_lines; + QTimer* m_ticker; + + private slots: + void tick(); +}; + +#endif // LDFORGE_MESSAGELOG_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Misc.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Misc.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,301 @@ +/* + * 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 . + */ + +#include +#include +#include +#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 (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 (obj)->getPosition(); + 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 (initlist vals, QString delim) +{ + QStringList list; + + for (const StringFormatArg& arg : vals) + list << arg.value(); + + 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 diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Misc.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Misc.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,142 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_MISC_H +#define LDFORGE_MISC_H + +#include +#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 (initlist 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 static inline const char* plural (T n) +{ + return (n != 1) ? "s" : ""; +} + +// ----------------------------------------------------------------------------- +// Templated clamp +template static inline T clamp (T a, T min, T max) +{ + return (a > max) ? max : (a < min) ? min : a; +} + +// Templated minimum +template static inline T min (T a, T b) +{ + return (a < b) ? a : b; +} + +// Templated maximum +template static inline T max (T a, T b) +{ + return (a > b) ? a : b; +} + +// Templated absolute value +template static inline T abs (T a) +{ + return (a >= 0) ? a : -a; +} + +template inline bool isZero (T a) +{ + return abs (a) < 0.0001; +} + +template inline bool isInteger (T a) +{ + return isZero (a - (int) a); +} + +template void removeDuplicates (QList& a) +{ + std::sort (a.begin(), a.end()); + a.erase (std::unique (a.begin(), a.end()), a.end()); +} + +#endif // LDFORGE_MISC_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/PartDownloader.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/PartDownloader.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,542 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include "PartDownloader.h" +#include "ui_downloadfrom.h" +#include "Types.h" +#include "MainWindow.h" +#include "Document.h" +#include "GLRenderer.h" +#include "ConfigurationDialog.h" +#include "moc_PartDownloader.cpp" + +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); + getInterface()->setupUi (this); + getInterface()->fname->setFocus(); + getInterface()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); + + setDownloadButton (new QPushButton (tr ("Download"))); + getInterface()->buttonBox->addButton (getDownloadButton(), QDialogButtonBox::ActionRole); + getButton (Abort)->setEnabled (false); + + connect (getInterface()->source, SIGNAL (currentIndexChanged (int)), + this, SLOT (sourceChanged (int))); + connect (getInterface()->buttonBox, SIGNAL (clicked (QAbstractButton*)), + this, SLOT (buttonClicked (QAbstractButton*))); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PartDownloader::~PartDownloader() +{ + delete getInterface(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString PartDownloader::getURL() const +{ + const Source src = getSource(); + QString dest; + + switch (src) + { + case PartsTracker: + dest = getInterface()->fname->text(); + modifyDestination (dest); + return g_unofficialLibraryURL + dest; + + case CustomURL: + return getInterface()->fname->text(); + } + + // Shouldn't happen + return ""; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloader::modifyDestination (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) getInterface()->source->currentIndex(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloader::sourceChanged (int i) +{ + if (i == CustomURL) + getInterface()->fileNameLabel->setText (tr ("URL:")); + else + getInterface()->fileNameLabel->setText (tr ("File name:")); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloader::buttonClicked (QAbstractButton* btn) +{ + if (btn == getButton (Close)) + { + reject(); + } + elif (btn == getButton (Abort)) + { + setAborted (true); + + for (PartDownloadRequest* req : getRequests()) + req->abort(); + } + elif (btn == getButton (Download)) + { + QString dest = getInterface()->fname->text(); + setPrimaryFile (null); + setAborted (false); + + if (getSource() == CustomURL) + dest = basename (getURL()); + + modifyDestination (dest); + + if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest)) + { + const QString overwritemsg = fmt (tr ("%1 already exists in download directory. Overwrite?"), dest); + if (!confirm (tr ("Overwrite?"), overwritemsg)) + return; + } + + getDownloadButton()->setEnabled (false); + getInterface()->progress->setEnabled (true); + getInterface()->fname->setEnabled (false); + getInterface()->source->setEnabled (false); + downloadFile (dest, getURL(), true); + getButton (Close)->setEnabled (false); + getButton (Abort)->setEnabled (true); + getButton (Download)->setEnabled (false); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloader::downloadFile (QString dest, QString url, bool primary) +{ + const int row = getInterface()->progress->rowCount(); + + // Don't download files repeadetly. + if (getFilesToDownload().indexOf (dest) != -1) + return; + + modifyDestination (dest); + log ("DOWNLOAD: %1 -> %2\n", url, PartDownloader::getDownloadPath() + DIRSLASH + dest); + PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); + + pushToFilesToDownload (dest); + pushToRequests (req); + getInterface()->progress->insertRow (row); + req->setTableRow (row); + req->updateToTable(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloader::checkIfFinished() +{ + bool failed = isAborted(); + + // If there is some download still working, we're not finished. + for (PartDownloadRequest* req : getRequests()) + { + if (!req->isFinished()) + return; + + if (req->getState() == PartDownloadRequest::EFailed) + failed = true; + } + + for (PartDownloadRequest* req : getRequests()) + delete req; + + clearRequests(); + + // Update everything now + if (getPrimaryFile()) + { + LDDocument::setCurrent (getPrimaryFile()); + reloadAllSubfiles(); + g_win->doFullRefresh(); + g_win->R()->resetAngles(); + } + + if (net_autoclose && !failed) + { + // Close automatically if desired. + accept(); + } + else + { + // Allow the prompt be closed now. + getButton (Abort)->setEnabled (false); + getButton (Close)->setEnabled (true); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QPushButton* PartDownloader::getButton (PartDownloader::Button i) +{ + switch (i) + { + case Download: + return getDownloadButton(); + + case Abort: + return qobject_cast (getInterface()->buttonBox->button (QDialogButtonBox::Abort)); + + case Close: + return qobject_cast (getInterface()->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_NAM (new QNetworkAccessManager), + m_FirstUpdate (true), + m_Primary (primary), + m_FilePointer (null) +{ + // Make sure that we have a valid destination. + QString dirpath = dirname (getFilePath()); + + QDir dir (dirpath); + + if (!dir.exists()) + { + log ("Creating %1...\n", dirpath); + + if (!dir.mkpath (dirpath)) + critical (fmt (tr ("Couldn't create the directory %1!"), dirpath)); + } + + setReply (getNAM()->get (QNetworkRequest (QUrl (url)))); + connect (getReply(), SIGNAL (finished()), this, SLOT (downloadFinished())); + connect (getReply(), SIGNAL (readyRead()), this, SLOT (readyRead())); + connect (getReply(), SIGNAL (downloadProgress (qint64, qint64)), + this, SLOT (downloadProgress (qint64, qint64))); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PartDownloadRequest::~PartDownloadRequest() {} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloadRequest::updateToTable() +{ + const int labelcol = PartDownloader::PartLabelColumn, + progcol = PartDownloader::ProgressColumn; + QTableWidget* table = getPrompt()->getInterface()->progress; + QProgressBar* prog; + + switch (getState()) + { + case ERequesting: + case EDownloading: + { + prog = qobject_cast (table->cellWidget (getTableRow(), progcol)); + + if (!prog) + { + prog = new QProgressBar; + table->setCellWidget (getTableRow(), progcol, prog); + } + + prog->setRange (0, getBytesTotal()); + prog->setValue (getBytesRead()); + } break; + + case EFinished: + case EFailed: + { + const QString text = (getState() == EFinished) + ? "FINISHED" + : "FAILED"; + + QLabel* lb = new QLabel (text); + lb->setAlignment (Qt::AlignCenter); + table->setCellWidget (getTableRow(), progcol, lb); + } break; + } + + QLabel* lb = qobject_cast (table->cellWidget (getTableRow(), labelcol)); + + if (isFirstUpdate()) + { + lb = new QLabel (fmt ("%1", getDestinaton()), table); + table->setCellWidget (getTableRow(), labelcol, lb); + } + + // Make sure that the cell is big enough to contain the label + if (table->columnWidth (labelcol) < lb->width()) + table->setColumnWidth (labelcol, lb->width()); + + setFirstUpdate (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloadRequest::downloadFinished() +{ + if (getReply()->error() != QNetworkReply::NoError) + { + if (isPrimary() && !getPrompt()->isAborted()) + critical (getReply()->errorString()); + + setState (EFailed); + } + elif (getState() != EFailed) + setState (EFinished); + + setBytesRead (getBytesTotal()); + updateToTable(); + + if (getFilePointer()) + { + getFilePointer()->close(); + delete getFilePointer(); + setFilePointer (null); + + if (getState() == EFailed) + QFile::remove (getFilePath()); + } + + if (getState() != EFinished) + { + getPrompt()->checkIfFinished(); + return; + } + + // Try to load this file now. + LDDocument* f = openDocument (getFilePath(), false); + + if (!f) + return; + + f->setImplicit (!isPrimary()); + + // Iterate through this file and check for errors. If there's any that stems + // from unknown file references, try resolve that by downloading the reference. + // This is why downloading a part may end up downloading multiple files, as + // it resolves dependencies. + for (LDObject* obj : f->getObjects()) + { + LDError* err = dynamic_cast (obj); + + if (!err || err->getFileReferenced().isEmpty()) + continue; + + QString dest = err->getFileReferenced(); + getPrompt()->modifyDestination (dest); + getPrompt()->downloadFile (dest, g_unofficialLibraryURL + dest, false); + } + + if (isPrimary()) + { + addRecentFile (getFilePath()); + getPrompt()->setPrimaryFile (f); + } + + getPrompt()->checkIfFinished(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloadRequest::downloadProgress (int64 recv, int64 total) +{ + setBytesRead (recv); + setBytesTotal (total); + setState (EDownloading); + updateToTable(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloadRequest::readyRead() +{ + if (getState() == EFailed) + return; + + if (getFilePointer() == null) + { + replaceInFilePath ("\\", "/"); + + // We have already asked the user whether we can overwrite so we're good + // to go here. + setFilePointer (new QFile (getFilePath().toLocal8Bit())); + + if (!getFilePointer()->open (QIODevice::WriteOnly)) + { + critical (fmt (tr ("Couldn't open %1 for writing: %2"), getFilePath(), strerror (errno))); + setState (EFailed); + getReply()->abort(); + updateToTable(); + getPrompt()->checkIfFinished(); + return; + } + } + + getFilePointer()->write (getReply()->readAll()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool PartDownloadRequest::isFinished() const +{ + return getState() == EFinished || getState() == EFailed; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloadRequest::abort() +{ + getReply()->abort(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (DownloadFrom, 0) +{ + PartDownloader::staticBegin(); +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/PartDownloader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/PartDownloader.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,134 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_DOWNLOAD_H +#define LDFORGE_DOWNLOAD_H + +#include +#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 +{ + typedefs: + enum Source + { + PartsTracker, + CustomURL, + }; + + enum Button + { + Download, + Abort, + Close + }; + + enum TableColumn + { + PartLabelColumn, + ProgressColumn, + }; + + using RequestList = QList; + + properties: + Q_OBJECT + PROPERTY (public, LDDocument*, PrimaryFile, NO_OPS, STOCK_WRITE) + PROPERTY (public, bool, Aborted, BOOL_OPS, STOCK_WRITE) + PROPERTY (private, Ui_DownloadFrom*, Interface, NO_OPS, STOCK_WRITE) + PROPERTY (private, QStringList, FilesToDownload, LIST_OPS, STOCK_WRITE) + PROPERTY (private, RequestList, Requests, LIST_OPS, STOCK_WRITE) + PROPERTY (private, QPushButton*, DownloadButton, NO_OPS, 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 +{ + typedefs: + enum EState + { + ERequesting, + EDownloading, + EFinished, + EFailed, + }; + + properties: + Q_OBJECT + PROPERTY (public, int, TableRow, NUM_OPS, STOCK_WRITE) + PROPERTY (private, EState, State, NO_OPS, STOCK_WRITE) + PROPERTY (private, PartDownloader*, Prompt, NO_OPS, STOCK_WRITE) + PROPERTY (private, QString, URL, STR_OPS, STOCK_WRITE) + PROPERTY (private, QString, Destinaton, STR_OPS, STOCK_WRITE) + PROPERTY (private, QString, FilePath, STR_OPS, STOCK_WRITE) + PROPERTY (private, QNetworkAccessManager*, NAM, NO_OPS, STOCK_WRITE) + PROPERTY (private, QNetworkReply*, Reply, NO_OPS, STOCK_WRITE) + PROPERTY (private, bool, FirstUpdate, BOOL_OPS, STOCK_WRITE) + PROPERTY (private, int64, BytesRead, NUM_OPS, STOCK_WRITE) + PROPERTY (private, int64, BytesTotal, NUM_OPS, STOCK_WRITE) + PROPERTY (private, bool, Primary, BOOL_OPS, STOCK_WRITE) + PROPERTY (private, QFile*, FilePointer, NO_OPS, 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(); +}; + +#endif // LDFORGE_DOWNLOAD_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Primitives.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Primitives.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,704 @@ +/* + * 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 . + */ + +#include +#include +#include +#include "Document.h" +#include "MainWindow.h" +#include "Primitives.h" +#include "ui_makeprim.h" +#include "Misc.h" +#include "Colors.h" +#include "moc_Primitives.cpp" + +QList g_PrimitiveCategories; +QList 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)) + { + // 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(); + log ("%1 primitives loaded.\n", g_primitives.size()); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void recursiveGetFilenames (QDir dir, QList& 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()); + log ("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.cat = 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 (fmt ("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(); + log ("%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.cat = null; + + // Go over the categories and their regexes, if and when there's a match, + // the primitive's category is set to the category the regex beloings to. + for (PrimitiveCategory* cat : g_PrimitiveCategories) + { + for (RegexEntry& entry : cat->regexes) + { + switch (entry.type) + { + case EFilenameRegex: + { + // f-regex, check against filename + matched = entry.regex.exactMatch (prim.name); + } break; + + case ETitleRegex: + { + // t-regex, check against title + matched = entry.regex.exactMatch (prim.title); + } break; + } + + if (matched) + { + prim.cat = cat; + break; + } + } + + // Drop out if a category was decided on. + if (prim.cat != null) + break; + } + + // If there was a match, add the primitive to the category. + // Otherwise, add it to the list of unmatched primitives. + if (prim.cat != null) + prim.cat->prims << prim; + else + g_unmatched->prims << prim; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PrimitiveCategory::loadCategories() +{ + for (PrimitiveCategory* cat : g_PrimitiveCategories) + delete cat; + + g_PrimitiveCategories.clear(); + QString path = Config::dirpath() + "primregexps.cfg"; + + if (!QFile::exists (path)) + path = ":/data/primitive-categories.cfg"; + + QFile f (path); + + if (!f.open (QIODevice::ReadOnly)) + { + critical (fmt (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); + ERegexType type = EFilenameRegex; + + if (cmd == "f") + type = EFilenameRegex; + elif (cmd == "t") + type = ETitleRegex; + else + { + log (tr ("Warning: unknown command \"%1\" on line \"%2\""), cmd, line); + continue; + } + + QRegExp regex (line.mid (colon + 1)); + RegexEntry entry = { regex, type }; + cat->regexes << entry; + } + else + log ("Warning: Rules given before the first category name"); + } + + if (cat->isValidToInclude()) + g_PrimitiveCategories << cat; + + // Add a category for unmatched primitives. + // Note: if this function is called the second time, g_unmatched has been + // deleted at the beginning of the function and is dangling at this point. + g_unmatched = new PrimitiveCategory (tr ("Other")); + g_PrimitiveCategories << g_unmatched; + f.close(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool PrimitiveCategory::isValidToInclude() +{ + if (regexes.size() == 0) + { + log (tr ("Warning: category \"%1\" left without patterns"), getName()); + deleteLater(); + return false; + } + + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool isPrimitiveLoaderBusy() +{ + return g_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& 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 condLineSegs; + QList 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 == lores) ? "" : fmt ("%1/", divs); + QString frac = fmt ("%1-%2", numer, denom); + QString root = g_radialNameRoots[type]; + QString numstr = (type == Ring || type == Cone) ? fmt ("%1", num) : ""; + + // Truncate the root if necessary (7-16rin4.dat for instance). + // However, always keep the root at least 2 characters. + int extra = (frac.length() + numstr.length() + root.length()) - 8; + root.chop (clamp (extra, 0, 2)); + + // Stick them all together and return the result. + return prefix + frac + root + numstr + ".dat"; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocument* generatePrimitive (PrimitiveType type, int segs, int divs, int num) +{ + // Make the description + 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 = fmt ("%1 %2%3 x %4", primitiveTypeName (type), spacing, num, frac); + } + else + descr = fmt ("%1 %2", primitiveTypeName (type), frac); + + // Prepend "Hi-Res" if 48/ primitive. + if (divs == hires) + descr.insert (0, "Hi-Res "); + + LDDocument* f = new LDDocument; + f->setDefaultName (name); + + QString author = APPNAME; + QString license = ""; + + if (ld_defaultname.isEmpty() == false) + { + license = getLicenseText (ld_defaultlicense); + author = fmt ("%1 [%2]", ld_defaultname, ld_defaultuser); + } + + f->addObjects ( + { + new LDComment (descr), + new LDComment (fmt ("Name: %1", name)), + new LDComment (fmt ("Author: %1", author)), + new LDComment (fmt ("!LDRAW_ORG Unofficial_%1Primitive", divs == hires ? "48_" : "")), + new LDComment (license), + new LDEmpty, + new LDBFC (LDBFC::CertifyCCW), + new LDEmpty, + }); + + f->addObjects (makePrimitive (type, segs, divs, num)); + return f; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num) +{ + 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 ? hires : lores); + + // If the current value is 16 and we switch to hi-res, default the + // spinbox to 48. + if (on && ui->sb_segs->value() == lores) + ui->sb_segs->setValue (hires); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (MakePrimitive, 0) +{ + PrimitivePrompt* dlg = new PrimitivePrompt (g_win); + + if (!dlg->exec()) + return; + + int segs = dlg->ui->sb_segs->value(); + int divs = dlg->ui->cb_hires->isChecked() ? hires : lores; + int num = dlg->ui->sb_ringnum->value(); + PrimitiveType type = + dlg->ui->rb_circle->isChecked() ? Circle : + dlg->ui->rb_cylinder->isChecked() ? Cylinder : + dlg->ui->rb_disc->isChecked() ? Disc : + dlg->ui->rb_ndisc->isChecked() ? DiscNeg : + dlg->ui->rb_ring->isChecked() ? Ring : Cone; + + LDDocument* f = generatePrimitive (type, segs, divs, num); + + g_win->save (f, false); + delete f; +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Primitives.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Primitives.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,134 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_PRIMITIVES_H +#define LDFORGE_PRIMITIVES_H + +#include "Main.h" +#include "Types.h" +#include +#include + +class LDDocument; +class Ui_MakePrimUI; +class PrimitiveCategory; +struct Primitive +{ + QString name, title; + PrimitiveCategory* cat; +}; + +class PrimitiveCategory : public QObject +{ + Q_OBJECT + PROPERTY (public, QString, Name, STR_OPS, STOCK_WRITE) + + public: + enum ERegexType + { + EFilenameRegex, + ETitleRegex + }; + + struct RegexEntry + { + QRegExp regex; + ERegexType type; + }; + + QList regexes; + QList 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 m_prims; + QStringList m_files; + int m_i; + int m_baselen; +}; + +extern QList 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& 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); + +#endif // LDFORGE_PRIMITIVES_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/PropertyMacro.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/PropertyMacro.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,124 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_PROPERTY_H +#define LDFORGE_PROPERTY_H + +#define PROPERTY( ACCESS, TYPE, NAME, OPS, WRITETYPE ) \ + private: \ + TYPE m_##NAME; \ + \ + public: \ + inline TYPE const& GET_READ_METHOD( NAME, OPS ) const \ + { \ + return m_##NAME; \ + } \ + \ + ACCESS: \ + DEFINE_WRITE_METHOD_##WRITETYPE( TYPE, NAME ) \ + DEFINE_PROPERTY_##OPS( TYPE, NAME ) + +#define GET_READ_METHOD( NAME, OPS ) \ + GET_READ_METHOD_##OPS( NAME ) + +#define GET_READ_METHOD_BOOL_OPS( NAME ) is##NAME() +#define GET_READ_METHOD_NO_OPS( NAME ) get##NAME() +#define GET_READ_METHOD_STR_OPS( NAME ) get##NAME() +#define GET_READ_METHOD_NUM_OPS( NAME ) get##NAME() +#define GET_READ_METHOD_LIST_OPS( NAME ) get##NAME() + +#define DEFINE_WRITE_METHOD_STOCK_WRITE( TYPE, NAME ) \ + inline void set##NAME( TYPE const& NAME##_ ) \ + { \ + m_##NAME = NAME##_; \ + } + +#define DEFINE_WRITE_METHOD_CUSTOM_WRITE( TYPE, NAME ) \ + void set##NAME( TYPE const& NAME##_ ); \ + +#define DEFINE_WITH_CB( NAME ) void NAME##Changed(); +#define DEFINE_NO_CB( NAME ) + +#define DEFINE_PROPERTY_NO_OPS( TYPE, NAME ) + +#define DEFINE_PROPERTY_STR_OPS( TYPE, NAME ) \ + void appendTo##NAME( TYPE a ) \ + { \ + TYPE tmp( m_##NAME ); \ + tmp.append( a ); \ + set##NAME( tmp ); \ + } \ + \ + void prependTo##NAME( TYPE a ) \ + { \ + TYPE tmp( m_##NAME ); \ + tmp.prepend( a ); \ + set##NAME( tmp ); \ + } \ + \ + void replaceIn##NAME( TYPE a, TYPE b ) \ + { \ + TYPE tmp( m_##NAME ); \ + tmp.replace( a, b ); \ + set##NAME( tmp ); \ + } + +#define DEFINE_PROPERTY_NUM_OPS( TYPE, NAME ) \ + inline void increase##NAME( TYPE a = 1 ) \ + { \ + set##NAME( m_##NAME + a ); \ + } \ + \ + inline void decrease##NAME( TYPE a = 1 ) \ + { \ + set##NAME( m_##NAME - a ); \ + } + +#define DEFINE_PROPERTY_BOOL_OPS( TYPE, NAME ) \ + inline void toggle##NAME() \ + { \ + set##NAME( !m_##NAME ); \ + } + +#define DEFINE_PROPERTY_LIST_OPS( TYPE, NAME ) \ + void pushTo##NAME( const TYPE::value_type& a ) \ + { \ + TYPE tmp( m_##NAME ); \ + tmp.push_back( a ); \ + set##NAME( tmp ); \ + } \ + \ + void removeFrom##NAME( const TYPE::value_type& a ) \ + { \ + TYPE tmp( m_##NAME ); \ + tmp.removeOne( a ); \ + set##NAME( tmp ); \ + } \ + \ + inline void clear##NAME() \ + { \ + set##NAME( TYPE() ); \ + } \ + \ + public: \ + inline int count##NAME() \ + { \ + return get##NAME().size(); \ + } + +#endif // LDFORGE_PROPERTY_H \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Types.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Types.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,416 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include "Main.h" +#include "Types.h" +#include "Misc.h" +#include "LDObject.h" +#include "Document.h" + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString DoFormat (QList args) +{ + assert (args.size() >= 1); + QString text = args[0].value(); + + for (uchar i = 1; i < args.size(); ++i) + text = text.arg (args[i].value()); + + return text; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +Vertex::Vertex (double x, double y, double z) +{ + m_coords[X] = x; + m_coords[Y] = y; + m_coords[Z] = z; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void Vertex::move (const Vertex& other) +{ + for_axes (ax) + m_coords[ax] += other[ax]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +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 fmtstr = "%1 %2 %3"; + + if (mangled) + fmtstr = "(%1, %2, %3)"; + + return fmt (fmtstr, 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 (initlist vals) +{ + assert (vals.size() == 9); + memcpy (&m_vals[0], & (*vals.begin()), sizeof m_vals); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void Matrix::puts() const +{ + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + log ("%1\t", m_vals[ (i * 3) + j]); + + log ("\n"); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +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 (val (0) * val (4) * val (8)) + + (val (1) * val (5) * val (6)) + + (val (2) * val (3) * val (7)) - + (val (2) * val (4) * val (6)) - + (val (1) * val (3) * val (8)) - + (val (0) * val (5) * val (7)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool Matrix::operator== (const Matrix& other) const +{ + for (int i = 0; i < 9; ++i) + if (val (i) != other[i]) + return false; + + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDBoundingBox::LDBoundingBox() +{ + reset(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDBoundingBox::calculate() +{ + reset(); + + if (!getCurrentDocument()) + return; + + for (LDObject* obj : getCurrentDocument()->getObjects()) + calcObject (obj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDBoundingBox::calcObject (LDObject* obj) +{ + switch (obj->getType()) + { + case LDObject::ELine: + case LDObject::ETriangle: + case LDObject::EQuad: + case LDObject::ECondLine: + { + for (int i = 0; i < obj->vertices(); ++i) + calcVertex (obj->getVertex (i)); + } break; + + case LDObject::ESubfile: + { + LDSubfile* ref = static_cast (obj); + LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline); + + for (LDObject * obj : objs) + { + calcObject (obj); + obj->deleteSelf(); + } + } + break; + + default: + break; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDBoundingBox& LDBoundingBox::operator<< (const Vertex& v) +{ + calcVertex (v); + return *this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDBoundingBox& LDBoundingBox::operator<< (LDObject* obj) +{ + calcObject (obj); + return *this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDBoundingBox::calcVertex (const Vertex& v) +{ + for_axes (ax) + { + m_Vertex0[ax] = min (v[ax], m_Vertex0[ax]); + m_Vertex1[ax] = max (v[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::size() const +{ + double xscale = (m_Vertex0[X] - m_Vertex1[X]); + double yscale = (m_Vertex0[Y] - m_Vertex1[Y]); + double zscale = (m_Vertex0[Z] - m_Vertex1[Z]); + double size = zscale; + + if (xscale > yscale) + { + if (xscale > zscale) + size = xscale; + } + elif (yscale > zscale) + size = yscale; + + if (abs (size) >= 2.0f) + return abs (size / 2); + + return 1.0f; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +Vertex LDBoundingBox::center() const +{ + return Vertex ( + (m_Vertex0[X] + m_Vertex1[X]) / 2, + (m_Vertex0[Y] + m_Vertex1[Y]) / 2, + (m_Vertex0[Z] + m_Vertex1[Z]) / 2); +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Types.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Types.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,315 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_TYPES_H +#define LDFORGE_TYPES_H + +#include +#include +#include +#include +#include "PropertyMacro.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 +using initlist = std::initializer_list; + +template +using pair = std::pair; + +enum Axis +{ + X, + Y, + Z +}; + +// ============================================================================= +// +class LDObject; +using LDObjectList = QList; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +// matrix +// +// A mathematical 3 x 3 matrix +// ============================================================================= +class Matrix +{ + public: + Matrix() {} + Matrix (initlist vals); + Matrix (double fillval); + Matrix (double vals[]); + + double getDeterminant() const; + Matrix mult (const Matrix& other) const; + void puts() const; + QString toString() const; + void zero(); + Matrix& operator= (const Matrix& other); + + inline double& val (int idx) + { + return m_vals[idx]; + } + + inline const double& val (int idx) const + { + return m_vals[idx]; + } + + inline Matrix operator* (const Matrix& other) const + { + return mult (other); + } + + inline double& operator[] (int idx) + { + return val (idx); + } + + inline const double& operator[] (int idx) const + { + return val (idx); + } + + bool operator== (const Matrix& other) const; + + private: + double m_vals[9]; +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +// Vertex +// +// Vertex class, contains a single point in 3D space. Not to be confused with +// LDVertex, which is a vertex used in an LDraw part file. +// ============================================================================= +class Vertex +{ + public: + Vertex() {} + Vertex (double x, double y, double z); + + double distanceTo (const Vertex& other) const; + Vertex midpoint (const Vertex& other); + void move (const Vertex& other); + QString toString (bool mangled) const; + void transform (const Matrix& matr, const Vertex& pos); + + Vertex& operator+= (const Vertex& other); + Vertex operator+ (const Vertex& other) const; + Vertex operator/ (const double d) const; + Vertex& operator/= (const double d); + bool operator== (const Vertex& other) const; + bool operator!= (const Vertex& other) const; + Vertex operator-() const; + int operator< (const Vertex& other) const; + + inline double& operator[] (const Axis ax) + { + return getCoordinate ((int) ax); + } + + inline const double& operator[] (const Axis ax) const + { + return getCoordinate ((int) ax); + } + + inline double& operator[] (const int ax) + { + return getCoordinate (ax); + } + + inline const double& operator[] (const int ax) const + { + return getCoordinate (ax); + } + + inline double& getCoordinate (int n) + { + return m_coords[n]; + } + + inline const double& getCoordinate (int n) const + { + return m_coords[n]; + } + + inline double& x() + { + return m_coords[X]; + } + + inline const double& x() const + { + return m_coords[X]; + } + + inline double& y() + { + return m_coords[Y]; + } + + inline const double& y() const + { + return m_coords[Y]; + } + + inline double& z() + { + return m_coords[Z]; + } + + inline const double& z() const + { + return m_coords[Z]; + } + + private: + double m_coords[3]; +}; + +Q_DECLARE_METATYPE (Vertex) + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +// StringFormatArg +// +// Converts a given value into a string that can be retrieved with ::value(). +// Used as the argument type to the formatting functions, hence its name. +// ============================================================================= +class StringFormatArg +{ + public: + StringFormatArg (const QString& a) : m_val (a) {} + StringFormatArg (const char& a) : m_val (a) {} + StringFormatArg (const uchar& a) : m_val (a) {} + StringFormatArg (const QChar& a) : m_val (a) {} + StringFormatArg (int a) : m_val (QString::number (a)) {} + StringFormatArg (const float& a) : m_val (QString::number (a)) {} + StringFormatArg (const double& a) : m_val (QString::number (a)) {} + StringFormatArg (const Vertex& a) : m_val (a.toString (false)) {} + StringFormatArg (const Matrix& a) : m_val (a.toString()) {} + StringFormatArg (const char* a) : m_val (a) {} + + StringFormatArg (const void* a) + { + m_val.sprintf ("%p", a); + } + + template StringFormatArg (const QList& a) + { + m_val = "{ "; + + for (const T& it : a) + { + if (&it != &a.first()) + m_val += ", "; + + StringFormatArg arg (it); + m_val += arg.value(); + } + + if (!a.isEmpty()) + m_val += " "; + + m_val += "}"; + } + + inline QString value() const + { + return m_val; + } + + private: + QString m_val; +}; + +// ============================================================================= +// LDBoundingBox +// +// The bounding box is the box that encompasses a given set of objects. +// v0 is the minimum vertex, v1 is the maximum vertex. +// ============================================================================= +class LDBoundingBox +{ + PROPERTY (private, bool, Empty, BOOL_OPS, STOCK_WRITE) + PROPERTY (private, Vertex, Vertex0, NO_OPS, STOCK_WRITE) + PROPERTY (private, Vertex, Vertex1, NO_OPS, STOCK_WRITE) + + public: + LDBoundingBox(); + void reset(); + void calculate(); + double size() const; + void calcObject (LDObject* obj); + void calcVertex (const Vertex& v); + Vertex center() const; + + LDBoundingBox& operator<< (LDObject* obj); + LDBoundingBox& operator<< (const Vertex& v); +}; + +// Formatter function +QString DoFormat (QList args); + +// printf replacement +void doPrint (QFile& f, QList args); +void doPrint (FILE* fp, QList args); + +// log() - universal access to the message log. Defined here so that I don't have +// to include MessageLog.h here and recompile everything every time that file changes. +void DoLog (std::initializer_list args); + +// Macros to access these functions +# ifndef IN_IDE_PARSER +#define fmt(...) DoFormat ({__VA_ARGS__}) +# define fprint(F, ...) doPrint (F, {__VA_ARGS__}) +# define log(...) DoLog({ __VA_ARGS__ }) +#else +QString fmt (const char* fmtstr, ...); +void fprint (QFile& f, const char* fmtstr, ...); +void fprint (FILE* fp, const char* fmtstr, ...); +void log (const char* fmtstr, ...); +#endif + +extern const Vertex g_origin; // Vertex at (0, 0, 0) +extern const Matrix g_identity; // Identity matrix + +static const double pi = 3.14159265358979323846; + +#endif // LDFORGE_TYPES_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Widgets.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Widgets.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,195 @@ +/* + * 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 . + */ + +// 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 +#include +#include +#include +#include + +#include "Widgets.h" +#include "moc_Widgets.cpp" + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup::RadioGroup (const QString& title, QWidget* parent) : QGroupBox (title, parent) +{ + init (Qt::Vertical); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false) +{ + return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool RadioGroup::isChecked (int n) const +{ + return m_buttonGroup->checkedId() == n; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::init (Qt::Orientation orient) +{ + m_vert = orient == Qt::Vertical; + + m_buttonGroup = new QButtonGroup; + m_oldId = m_curId = 0; + m_coreLayout = null; + + m_coreLayout = new QBoxLayout ( (orient == Qt::Vertical) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom); + setLayout (m_coreLayout); + + // Init the first row with a break + rowBreak(); + + connect (m_buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int))); + connect (m_buttonGroup, SIGNAL (buttonReleased (int)), this, SLOT (slot_buttonReleased (int))); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup::RadioGroup (const QString& title, initlist 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(); +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/Widgets.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Widgets.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,96 @@ +/* + * 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 . + */ + +#ifndef WIDGETS_H +#define WIDGETS_H + +#include +#include +#include +#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::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, initlist 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 m_objects; + QList 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); +}; + +#endif // WIDGETS_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/actions/EditActions.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/actions/EditActions.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,837 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#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->raw(); + ++num; + } + + qApp->clipboard()->setText (data); + return num; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Cut, CTRL (X)) +{ + int num = copyToClipboard(); + deleteSelection(); + log (tr ("%1 objects cut"), num); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Copy, CTRL (C)) +{ + int num = copyToClipboard(); + log (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; + } + + log (tr ("%1 objects pasted"), num); + refresh(); + scrollToSelection(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Delete, KEY (Delete)) +{ + int num = deleteSelection(); + log (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->getIndex(); + + if (idx == -1) + continue; + + LDObjectList objs; + + if (obj->getType() == LDObject::ESubfile) + objs = static_cast (obj)->inlineContents ( + (LDSubfile::InlineFlags) + ( (deep) ? LDSubfile::DeepInline : 0) | + LDSubfile::CacheInline + ); + else + continue; + + // Merge in the inlined objects + for (LDObject * inlineobj : objs) + { + QString line = inlineobj->raw(); + inlineobj->deleteSelf(); + 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->deleteSelf(); + } + + 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->getType() != LDObject::EQuad) + continue; + + // Find the index of this quad + long index = obj->getIndex(); + + if (index == -1) + return; + + QList triangles = static_cast (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->deleteSelf(); + + num++; + } + + log ("%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->raw()); + + if (obj->getType() == LDObject::EError) + ui.errorDescription->setText (static_cast (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->getType(); + if (type != LDObject::EQuad && type != LDObject::ETriangle) + continue; + + int numLines; + LDLine* lines[4]; + + if (type == LDObject::EQuad) + { + numLines = 4; + + LDQuad* quad = static_cast (obj); + lines[0] = new LDLine (quad->getVertex (0), quad->getVertex (1)); + lines[1] = new LDLine (quad->getVertex (1), quad->getVertex (2)); + lines[2] = new LDLine (quad->getVertex (2), quad->getVertex (3)); + lines[3] = new LDLine (quad->getVertex (3), quad->getVertex (0)); + } + else + { + numLines = 3; + + LDTriangle* tri = static_cast (obj); + lines[0] = new LDLine (tri->getVertex (0), tri->getVertex (1)); + lines[1] = new LDLine (tri->getVertex (1), tri->getVertex (2)); + lines[2] = new LDLine (tri->getVertex (2), tri->getVertex (0)); + } + + for (int i = 0; i < numLines; ++i) + { + long idx = obj->getIndex() + i + 1; + + lines[i]->setColor (edgecolor); + getCurrentDocument()->insertObj (idx, lines[i]); + R()->compileObject (lines[i]); + } + + num += numLines; + } + + log (tr ("Added %1 border lines"), num); + refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (CornerVerts, 0) +{ + int num = 0; + + for (LDObject* obj : selection()) + { + if (obj->vertices() < 2) + continue; + + int idx = obj->getIndex(); + + for (int i = 0; i < obj->vertices(); ++i) + { + LDVertex* vert = new LDVertex; + vert->pos = obj->getVertex (i); + vert->setColor (obj->getColor()); + + getCurrentDocument()->insertObj (++idx, vert); + R()->compileObject (vert); + ++num; + } + } + + log (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 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->getVertex (i); + rotateVertex (v, rotpoint, transform); + obj->setVertex (i, v); + } + } + elif (obj->hasMatrix()) + { + LDMatrixObject* mo = dynamic_cast (obj); + + // Transform the position + Vertex v = mo->getPosition(); + rotateVertex (v, rotpoint, transform); + mo->setPosition (v); + + // Transform the matrix + mo->setTransform (transform * mo->getTransform()); + } + elif (obj->getType() == LDObject::EVertex) + { + LDVertex* vert = static_cast (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 (obj); + + if (mo != null) + { + Vertex v = mo->getPosition(); + Matrix t = mo->getTransform(); + + for_axes (ax) + roundToDecimals (v[ax], 3); + + // Let matrix values be rounded to 4 decimals, + // they need that extra precision + for (int i = 0; i < 9; ++i) + roundToDecimals (t[i], 4); + + mo->setPosition (v); + mo->setTransform (t); + num += 10; + } + else + { + for (int i = 0; i < obj->vertices(); ++i) + { + Vertex v = obj->getVertex (i); + + for_axes (ax) + roundToDecimals (v[ax], 3); + + obj->setVertex (i, v); + R()->compileObject (obj); + num += 3; + } + } + } + + log (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->getType() == LDObject::ELine || obj->getType() == LDObject::ECondLine) + col = edgecolor; + + obj->setColor (col); + R()->compileObject (obj); + num++; + } + + log (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 sel; + int num = 0; + + if (ui.x->isChecked()) sel << X; + if (ui.y->isChecked()) sel << Y; + if (ui.z->isChecked()) sel << Z; + + for (LDObject* obj : selection()) + { + for (int i = 0; i < obj->vertices(); ++i) + { + Vertex v = obj->getVertex (i); + + for (Axis ax : sel) + { + double& coord = v[ax]; + + if (any || coord == search) + { + if (!rel) + coord = 0; + + coord += replacement; + num++; + } + } + + obj->setVertex (i, v); + R()->compileObject (obj); + } + } + + log (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 sel; + + if (ui.x->isChecked()) sel << X; + if (ui.y->isChecked()) sel << Y; + if (ui.z->isChecked()) sel << Z; + + for (LDObject* obj : selection()) + { + for (int i = 0; i < obj->vertices(); ++i) + { + Vertex v = obj->getVertex (i); + + for (Axis ax : sel) + v[ax] *= -1; + + obj->setVertex (i, v); + R()->compileObject (obj); + } + } + + refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Demote, 0) +{ + LDObjectList sel = selection(); + int num = 0; + + for (LDObject* obj : sel) + { + if (obj->getType() != LDObject::ECondLine) + continue; + + LDLine* repl = static_cast (obj)->demote(); + R()->compileObject (repl); + ++num; + } + + log (tr ("Demoted %1 conditional lines"), num); + refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static bool isColorUsed (int colnum) +{ + for (LDObject* obj : getCurrentDocument()->getObjects()) + if (obj->isColored() && obj->getColor() == colnum) + return true; + + return false; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Autocolor, 0) +{ + int colnum = 0; + + while (colnum < MAX_COLORS && (getColor (colnum) == null || isColorUsed (colnum))) + colnum++; + + if (colnum >= MAX_COLORS) + { + log (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); + } + + log (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 = fmt ("!HISTORY %1 [%2] %3", + ui->m_date->date().toString ("yyyy-MM-dd"), + ui->m_username->text(), + ui->m_comment->text()); + + LDComment* comm = new LDComment (commentText); + + // Find a spot to place the new comment + for ( + obj = getCurrentDocument()->getObject (0); + obj && obj->next() && !obj->next()->isScemantic(); + obj = obj->next() + ) + { + LDComment* comm = dynamic_cast (obj); + + if (comm && comm->text.startsWith ("!HISTORY ")) + ishistory = true; + + if (prevIsHistory && !ishistory) + { + // Last line was history, this isn't, thus insert the new history + // line here. + break; + } + + prevIsHistory = ishistory; + } + + int idx = obj ? obj->getIndex() : 0; + getCurrentDocument()->insertObj (idx++, comm); + + // If we're adding a history line right before a scemantic object, pad it + // an empty line + if (obj && obj->next() && obj->next()->isScemantic()) + getCurrentDocument()->insertObj (idx, new LDEmpty); + + buildObjList(); + delete ui; +} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/actions/MainActions.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/actions/MainActions.cc Tue Jan 21 02:03:27 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +#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 (fmt (" [%1]", ld_defaultuser)); + + ui.le_author->setText (authortext); + + switch (ld_defaultlicense) + { + case 0: + ui.rb_license_ca->setChecked (true); + break; + + case 1: + ui.rb_license_nonca->setChecked (true); + break; + + case 2: + ui.rb_license_none->setChecked (true); + break; + + default: + QMessageBox::warning (null, "Warning", + fmt ("Unknown ld_defaultlicense value %1!", ld_defaultlicense)); + break; + } + + if (dlg->exec() == false) + return; + + newFile(); + + const LDBFC::Type BFCType = + ui.rb_bfc_ccw->isChecked() ? LDBFC::CertifyCCW : + ui.rb_bfc_cw->isChecked() ? LDBFC::CertifyCW : LDBFC::NoCertify; + + const QString license = + ui.rb_license_ca->isChecked() ? CALicense : + ui.rb_license_nonca->isChecked() ? NonCALicense : ""; + + getCurrentDocument()->addObjects ( + { + new LDComment (ui.le_title->text()), + new LDComment ("Name: .dat"), + new LDComment (fmt ("Author: %1", ui.le_author->text())), + new LDComment (fmt ("!LDRAW_ORG Unofficial_Part")), + (license != "" ? new LDComment (license) : null), + new LDEmpty, + new LDBFC (BFCType), + new LDEmpty, + }); + + 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->getType(), obj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Help, KEY (F1)) +{ +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (About, 0) +{ + AboutDialog().exec(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (AboutQt, 0) +{ + QMessageBox::aboutQt (g_win); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SelectAll, CTRL (A)) +{ + for (LDObject* obj : getCurrentDocument()->getObjects()) + obj->select(); + + updateSelection(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SelectByColor, CTRL_SHIFT (A)) +{ + int colnum = getSelectedColor(); + + if (colnum == -1) + return; // no consensus on color + + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : getCurrentDocument()->getObjects()) + if (obj->getColor() == 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 (selection()[0])->getFileInfo()->getName(); + + for (LDObject* obj : selection()) + if (static_cast (obj)->getFileInfo()->getName() != refName) + return; + } + + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : getCurrentDocument()->getObjects()) + { + if (obj->getType() != type) + continue; + + if (type == LDObject::ESubfile && static_cast (obj)->getFileInfo()->getName() != 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 (fmt ("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 (fmt ("Unable to open %1 for writing (%2)", fname, file.errorString())); + return; + } + + for (LDObject* obj : selection()) + { + QString contents = obj->raw(); + 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()->getName()); + + if (root.right (4) == ".dat") + root.chop (4); + + QString defaultname = (root.length() > 0) ? fmt ("%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 (fmt ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno))); + + delete[] imgdata; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +extern_cfg (Bool, gl_axes); +DEFINE_ACTION (Axes, 0) +{ + gl_axes = !gl_axes; + updateActions(); + R()->update(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (VisibilityToggle, 0) +{ + for (LDObject* obj : selection()) + obj->toggleHidden(); + + 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", + fmt ("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]->getIndex(); + + int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval, + 1, getCurrentDocument()->getObjectCount(), 1, &ok); + + if (!ok || (obj = getCurrentDocument()->getObject (idx - 1)) == null) + return; + + getCurrentDocument()->clearSelection(); + obj->select(); + updateSelection(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SubfileSelection, 0) +{ + if (selection().size() == 0) + return; + + QString parentpath = getCurrentDocument()->getFullPath(); + + // BFC type of the new subfile - it shall inherit the BFC type of the parent document + LDBFC::Type 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 (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]->getIndex(); + + // 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()->getFullPath())); + + if (topdirname != "s") + { + QString desiredPath = subdirname + "/s"; + QString title = tr ("Create subfile directory?"); + QString text = fmt (tr ("The directory %1 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()->getObjects()) + { + LDBFC* bfc = dynamic_cast (obj); + + if (!bfc) + continue; + + LDBFC::Type a = bfc->type; + + 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->raw(); + + // 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 (fmt ("Author: %1 [%2]", ld_defaultname, ld_defaultuser)), + new LDComment (fmt ("!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->deleteSelf(); + + // Compile all objects in the new subfile + for (LDObject* obj : doc->getObjects()) + 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 diff -r 6b13e4c2e97b -r b75c6cce02e2 src/addObjectDialog.cc --- a/src/addObjectDialog.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,444 +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 . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "gui.h" -#include "addObjectDialog.h" -#include "document.h" -#include "colors.h" -#include "colorSelectDialog.h" -#include "history.h" -#include "widgets.h" -#include "misc.h" -#include "primitives.h" -#include "moc_addObjectDialog.cpp" - -// ============================================================================= -// ----------------------------------------------------------------------------- -class SubfileListItem : public QTreeWidgetItem -{ - PROPERTY (public, Primitive*, PrimitiveInfo, NO_OPS, STOCK_WRITE) - - public: - SubfileListItem (QTreeWidgetItem* parent, Primitive* info) : - QTreeWidgetItem (parent), - m_PrimitiveInfo (info) {} - - SubfileListItem (QTreeWidget* parent, Primitive* info) : - QTreeWidgetItem (parent), - m_PrimitiveInfo (info) {} -}; - -// ============================================================================= -// ----------------------------------------------------------------------------- -AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) : - QDialog (parent) -{ - setlocale (LC_ALL, "C"); - - int coordCount = 0; - QString typeName = LDObject::typeName (type); - - switch (type) - { - case LDObject::EComment: - { - le_comment = new QLineEdit; - - if (obj) - le_comment->setText (static_cast (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::statements[i]); - } - - if (obj) - rb_bfcType->setValue ( (int) static_cast (obj)->type); - } 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->getName()); - QList subfileItems; - - for (Primitive& prim : cat->prims) - { - SubfileListItem* item = new SubfileListItem (parentItem, &prim); - item->setText (0, fmt ("%1 - %2", prim.name, prim.title)); - subfileItems << item; - - // If this primitive is the one the current object points to, - // select it by default - if (obj && static_cast (obj)->getFileInfo()->getName() == prim.name) - tw_subfileList->setCurrentItem (item); - } - - tw_subfileList->addTopLevelItem (parentItem); - } - - connect (tw_subfileList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_subfileTypeChanged())); - lb_subfileName = new QLabel ("File:"); - le_subfileName = new QLineEdit; - le_subfileName->setFocus(); - - if (obj) - { - LDSubfile* ref = static_cast (obj); - le_subfileName->setText (ref->getFileInfo()->getName()); - } - } break; - - default: - { - critical (fmt ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName)); - } return; - } - - QPixmap icon = getIcon (fmt ("add-%1", typeName)); - LDObject* defaults = LDObject::getDefault (type); - - lb_typeIcon = new QLabel; - lb_typeIcon->setPixmap (icon); - - // Show a color edit dialog for the types that actually use the color - if (defaults->isColored()) - { - if (obj != null) - colnum = obj->getColor(); - else - colnum = (type == LDObject::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->getVertex (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 (obj); - - QLabel* lb_matrix = new QLabel ("Matrix:"); - le_matrix = new QLineEdit; - // le_matrix->setValidator (new QDoubleValidator); - Matrix defaultMatrix = g_identity; - - if (mo) - { - for_axes (ax) - dsb_coords[ax]->setValue (mo->getPosition()[ax]); - - defaultMatrix = mo->getTransform(); - } - - le_matrix->setText (defaultMatrix.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 (fmt (tr ("Edit %1"), typeName)); - - setWindowIcon (icon); - defaults->deleteSelf(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddObjectDialog::setButtonBackground (QPushButton* button, int colnum) -{ - LDColor* col = getColor (colnum); - - button->setIcon (getIcon ("palette")); - button->setAutoFillBackground (true); - - if (col) - button->setStyleSheet (fmt ("background-color: %1", col->hexcode)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString AddObjectDialog::currentSubfileName() -{ - SubfileListItem* item = static_cast (tw_subfileList->currentItem()); - - if (item->getPrimitiveInfo() == null) - return ""; // selected a heading - - return item->getPrimitiveInfo()->name; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddObjectDialog::slot_colorButtonClicked() -{ - ColorSelector::selectColor (colnum, colnum, this); - setButtonBackground (pb_color, colnum); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddObjectDialog::slot_subfileTypeChanged() -{ - QString name = currentSubfileName(); - - if (name.length() > 0) - le_subfileName->setText (name); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -template static T* initObj (LDObject*& obj) -{ - if (obj == null) - obj = new T; - - return static_cast (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddObjectDialog::staticDialog (const LDObject::Type type, LDObject* obj) -{ - setlocale (LC_ALL, "C"); - - // FIXME: Redirect to Edit Raw - if (obj && obj->getType() == 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 || obj->getType() == 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 (obj); - comm->text = 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 (obj); - bfc->type = (LDBFC::Type) dlg.rb_bfcType->value(); - } break; - - case LDObject::EVertex: - { - LDVertex* vert = initObj (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 (fmt ("Couldn't open `%1': %2", name, strerror (errno))); - return; - } - - LDSubfile* ref = initObj (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(); -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/addObjectDialog.h --- a/src/addObjectDialog.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +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 . - */ - -#ifndef LDFORGE_ADDOBJECTDIALOG_H -#define LDFORGE_ADDOBJECTDIALOG_H - -#include -#include "ldtypes.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(); -}; - -#endif // LDFORGE_ADDOBJECTDIALOG_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/colorSelectDialog.cc --- a/src/colorSelectDialog.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,211 +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 . - * ===================================================================== - * - * colorSelectDialog.cpp: Color selector box. - */ - -#include -#include -#include -#include - -#include "main.h" -#include "gui.h" -#include "colorSelectDialog.h" -#include "colors.h" -#include "config.h" -#include "misc.h" -#include "ui_colorsel.h" -#include "moc_colorSelectDialog.cpp" - -static const int g_numColumns = 16; -static const int g_squareSize = 32; - -extern_cfg (String, gl_maincolor); -extern_cfg (Float, gl_maincolor_alpha); - -// ============================================================================= -// ----------------------------------------------------------------------------- -ColorSelector::ColorSelector (int defval, QWidget* parent) : QDialog (parent) -{ - // Remove the default color if it's invalid - if (!getColor (defval)) - defval = -1; - - m_firstResize = true; - ui = new Ui_ColorSelUI; - ui->setupUi (this); - - m_scene = new QGraphicsScene; - ui->viewport->setScene (m_scene); - setSelection (getColor (defval)); - - // not really an icon but eh - m_scene->setBackgroundBrush (getIcon ("checkerboard")); - drawScene(); - - int width = viewportWidth(); - ui->viewport->setMinimumWidth (width); - ui->viewport->setMaximumWidth (width); - - drawColorInfo(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -ColorSelector::~ColorSelector() -{ - delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ColorSelector::drawScene() -{ - const int numCols = g_numColumns; - const int square = g_squareSize; - const int g_maxHeight = (numRows() * square); - QRect sceneRect (0, 0, viewportWidth(), g_maxHeight); - - m_scene->setSceneRect (sceneRect); - ui->viewport->setSceneRect (sceneRect); - - const double penWidth = 1.0f; - - // Draw the color rectangles. - m_scene->clear(); - - for (int i = 0; i < MAX_COLORS; ++i) - { - LDColor* info = ::getColor (i); - - if (!info) - continue; - - const double x = (i % numCols) * square; - const double y = (i / numCols) * square; - const double w = square - (penWidth / 2); - - QColor col = info->faceColor; - - if (i == maincolor) - { - // Use the user preferences for main color here - col = QColor (gl_maincolor); - col.setAlpha (gl_maincolor_alpha * 255.0f); - } - - QPen pen (info->edgeColor, penWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); - m_scene->addRect (x, y, w, w, pen, col); - QGraphicsTextItem* numtext = m_scene->addText (fmt ("%1", i)); - numtext->setDefaultTextColor ( (luma (col) < 80) ? Qt::white : Qt::black); - numtext->setPos (x, y); - - if (getSelection() && i == getSelection()->index) - { - auto curspic = m_scene->addPixmap (getIcon ("colorcursor")); - curspic->setPos (x, y); - } - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int ColorSelector::numRows() const -{ - return (MAX_COLORS / g_numColumns); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int ColorSelector::viewportWidth() const -{ - return g_numColumns * g_squareSize + 21; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ColorSelector::drawColorInfo() -{ - if (!getSelection()) - { - ui->colorLabel->setText ("---"); - return; - } - - ui->colorLabel->setText (fmt ("%1 - %2", getSelection()->index, getSelection()->name)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ColorSelector::resizeEvent (QResizeEvent* ev) -{ - // If this is the first resize, check if we need to scroll down to see the - // currently selected color. We cannot do this in the constructor because the - // height is not set properly there. - if (m_firstResize) - { - int visibleColors = (ui->viewport->height() / g_squareSize) * g_numColumns; - - if (getSelection() && getSelection()->index >= visibleColors) - { - int y = (getSelection()->index / g_numColumns) * g_squareSize; - ui->viewport->verticalScrollBar()->setValue (y); - } - - m_firstResize = false; - } - - (void) ev; - drawScene(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ColorSelector::mousePressEvent (QMouseEvent* event) -{ - QPointF scenepos = ui->viewport->mapToScene (event->pos()); - - int x = (scenepos.x() - (g_squareSize / 2)) / g_squareSize; - int y = (scenepos.y() - (g_squareSize / 2)) / g_squareSize; - int idx = (y * g_numColumns) + x; - - LDColor* col = ::getColor (idx); - - if (!col) - return; - - setSelection (col); - drawScene(); - drawColorInfo(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool ColorSelector::selectColor (int& val, int defval, QWidget* parent) -{ - ColorSelector dlg (defval, parent); - - if (dlg.exec() && dlg.getSelection() != null) - { - val = dlg.getSelection()->index; - return true; - } - - return false; -} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/colorSelectDialog.h --- a/src/colorSelectDialog.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +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 . - */ - -#ifndef LDFORGE_COLORSELECTOR_H -#define LDFORGE_COLORSELECTOR_H - -#include -#include "main.h" - -class LDColor; -class Ui_ColorSelUI; -class QGraphicsScene; - -class ColorSelector : public QDialog -{ - Q_OBJECT - PROPERTY (private, LDColor*, Selection, NO_OPS, 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(); -}; - -#endif // LDFORGE_COLORSELECTOR_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/colors.cc --- a/src/colors.cc Mon Jan 20 23:44:22 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 . - * ===================================================================== - * - * colors.cpp: LDraw color management. LDConfig.ldr parsing is not here! - * TODO: Make LDColor more full-fledged, add support for direct colors. - * TODO: g_LDColors should probably be a map. - */ - -#include "main.h" -#include "colors.h" -#include "document.h" -#include "misc.h" -#include "gui.h" -#include "ldconfig.h" -#include - -static LDColor* g_LDColors[MAX_COLORS]; - -// ============================================================================= -// ----------------------------------------------------------------------------- -void initColors() -{ - LDColor* col; - log ("%1: initializing color information.\n", __func__); - - // Always make sure there's 16 and 24 available. They're special like that. - col = new LDColor; - col->faceColor = col->hexcode = "#AAAAAA"; - col->edgeColor = Qt::black; - g_LDColors[maincolor] = col; - - col = new LDColor; - col->faceColor = col->edgeColor = col->hexcode = "#000000"; - g_LDColors[edgecolor] = col; - - parseLDConfig(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDColor* getColor (int colnum) -{ - // Check bounds - if (colnum < 0 || colnum >= MAX_COLORS) - return null; - - return g_LDColors[colnum]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void setColor (int colnum, LDColor* col) -{ - if (colnum < 0 || colnum >= MAX_COLORS) - return; - - g_LDColors[colnum] = col; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int luma (QColor& col) -{ - return (0.2126f * col.red()) + - (0.7152f * col.green()) + - (0.0722f * col.blue()); -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/colors.h --- a/src/colors.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +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 . - */ - -#ifndef LDFORGE_COLORS_H -#define LDFORGE_COLORS_H - -#include -#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; - -#endif // LDFORGE_COLORS_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/config.cc --- a/src/config.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +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 . - * ===================================================================== - * - * config.cpp: Configuration management. I don't like how unsafe QSettings - * is so this implements a type-safer and identifer-safer wrapping system of - * configuration variables. QSettings is used underlyingly, this is a matter - * of interface. - */ - -#include -#include -#include -#include -#include "main.h" -#include "config.h" -#include "misc.h" -#include "gui.h" -#include "document.h" - -#ifdef _WIN32 -# define EXTENSION ".ini" -#else -# define EXTENSION ".cfg" -#endif // _WIN32 - -Config* g_configPointers[MAX_CONFIG]; -static int g_cfgPointerCursor = 0; -static QMap g_configsByName; -static QList 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(); - log ("config::load: Loading configuration file from %1\n", settings->fileName()); - - for (Config* cfg : g_configPointers) - { - if (!cfg) - break; - - QVariant val = settings->value (cfg->getName(), cfg->getDefaultAsVariant()); - cfg->loadFromVariant (val); - g_configsByName[cfg->getName()] = cfg; - g_configs << cfg; - } - - settings->deleteLater(); - return true; -} - -// ============================================================================= -// Save the configuration to disk -// ----------------------------------------------------------------------------- -bool Config::save() -{ - QSettings* settings = getSettingsObject(); - log ("Saving configuration to %1...\n", settings->fileName()); - - for (Config* cfg : g_configs) - { - if (!cfg->isDefault()) - settings->setValue (cfg->getName(), cfg->toVariant()); - else - settings->remove (cfg->getName()); - } - - settings->sync(); - 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 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 (cfg); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -#undef IMPLEMENT_CONFIG - -#define IMPLEMENT_CONFIG(NAME) \ - NAME##Config* NAME##Config::getByName (QString name) \ - { \ - return getConfigByName (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) diff -r 6b13e4c2e97b -r b75c6cce02e2 src/config.h --- a/src/config.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,192 +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 . - */ - -#ifndef LDFORGE_CONFIG_H -#define LDFORGE_CONFIG_H - -#include "property.h" -#include "types.h" - -// ============================================================================= -#include -#include -#include -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, STR_OPS, 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; - 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(); \ - } \ - \ - virtual QVariant toVariant() const \ - { \ - return QVariant::fromValue (*m_valueptr); \ - } \ - \ - virtual QVariant getDefaultAsVariant() const \ - { \ - return QVariant::fromValue (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) -}; - -#endif // LDFORGE_CONFIG_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/configDialog.cc --- a/src/configDialog.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,800 +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 . - * ===================================================================== - * - * configDialog.cpp: Settings dialog and everything related to it. - * Actual configuration core is in config.cpp. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "main.h" -#include "configDialog.h" -#include "document.h" -#include "config.h" -#include "misc.h" -#include "colors.h" -#include "colorSelectDialog.h" -#include "gldraw.h" -#include "ui_config.h" -#include "moc_configDialog.cpp" - -extern_cfg (String, gl_bgcolor); -extern_cfg (String, gl_maincolor); -extern_cfg (Bool, lv_colorize); -extern_cfg (Bool, gl_colorbfc); -extern_cfg (Float, gl_maincolor_alpha); -extern_cfg (Int, gl_linethickness); -extern_cfg (String, gui_colortoolbar); -extern_cfg (Bool, edit_schemanticinline); -extern_cfg (Bool, gl_blackedges); -extern_cfg (Bool, gl_aa); -extern_cfg (Bool, gui_implicitfiles); -extern_cfg (String, net_downloadpath); -extern_cfg (Bool, net_guesspaths); -extern_cfg (Bool, net_autoclose); -extern_cfg (Bool, gl_logostuds); -extern_cfg (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()) - { - 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 : initlist ({xlabel, ylabel, zlabel, anglabel})) - { - label->setAlignment (Qt::AlignCenter); - gridlayout->addWidget (label, 0, i++); - } - - for (int i = 0; i < g_NumGrids; ++i) - { - // Icon - lb_gridIcons[i] = new QLabel; - lb_gridIcons[i]->setPixmap (getIcon (fmt ("grid-%1", QString (g_GridInfo[i].name).toLower()))); - - // Text label - lb_gridLabels[i] = new QLabel (fmt ("%1:", g_GridInfo[i].name)); - - QHBoxLayout* labellayout = new QHBoxLayout; - labellayout->addWidget (lb_gridIcons[i]); - labellayout->addWidget (lb_gridLabels[i]); - gridlayout->addLayout (labellayout, i + 1, 0); - - // Add the widgets - for (int j = 0; j < 4; ++j) - { - dsb_gridData[i][j] = new QDoubleSpinBox; - - // 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->updateToolBars(); - g_win->updateDocumentList(); -} - -// ============================================================================= -// A dialog button was clicked -// ----------------------------------------------------------------------------- -void ConfigDialog::buttonClicked (QAbstractButton* button) -{ - typedef QDialogButtonBox QDDB; - QDialogButtonBox* dbb = ui->buttonBox; - - if (button == dbb->button (QDDB::Ok)) - { - applySettings(); - accept(); - } elif (button == dbb->button (QDDB::Apply)) - { - applySettings(); - } elif (button == dbb->button (QDDB::Cancel)) - { - reject(); - } -} - -// ============================================================================= -// Update the list of color toolbar items in the quick color tab. -// ----------------------------------------------------------------------------- -void ConfigDialog::updateQuickColorList (LDQuickColor* sel) -{ - for (QListWidgetItem * item : quickColorItems) - delete item; - - quickColorItems.clear(); - - // Init table items - for (LDQuickColor& entry : quickColors) - { - QListWidgetItem* item = new QListWidgetItem; - - if (entry.isSeparator()) - { - item->setText ("--------"); - item->setIcon (getIcon ("empty")); - } - else - { - LDColor* col = entry.getColor(); - - if (col == null) - { - item->setText ("[[unknown color]]"); - item->setIcon (getIcon ("error")); - } - else - { - item->setText (col->name); - item->setIcon (makeColorIcon (col, 16)); - } - } - - ui->quickColorList->addItem (item); - quickColorItems << item; - - if (sel && &entry == sel) - { - ui->quickColorList->setCurrentItem (item); - ui->quickColorList->scrollToItem (item); - } - } -} - -// ============================================================================= -// Quick colors: add or edit button was clicked. -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_setColor() -{ - LDQuickColor* entry = null; - QListWidgetItem* item = null; - const bool isNew = static_cast (sender()) == ui->quickColor_add; - - if (isNew == false) - { - item = getSelectedQuickColor(); - - if (!item) - return; - - int i = getItemRow (item, quickColorItems); - entry = &quickColors[i]; - - if (entry->isSeparator() == true) - return; // don't color separators - } - - int defval = entry ? entry->getColor()->index : -1; - int val; - - if (ColorSelector::selectColor (val, defval, this) == false) - return; - - if (entry) - entry->setColor (getColor (val)); - else - { - LDQuickColor entry (getColor (val), null); - - item = getSelectedQuickColor(); - int idx = (item) ? getItemRow (item, quickColorItems) + 1 : quickColorItems.size(); - - quickColors.insert (idx, entry); - entry = quickColors[idx]; - } - - updateQuickColorList (entry); -} - -// ============================================================================= -// Remove a quick color -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_delColor() -{ - if (ui->quickColorList->selectedItems().isEmpty()) - return; - - QListWidgetItem* item = ui->quickColorList->selectedItems() [0]; - quickColors.removeAt (getItemRow (item, quickColorItems)); - updateQuickColorList(); -} - -// ============================================================================= -// Move a quick color up/down -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_moveColor() -{ - const bool up = (static_cast (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 (fmt ("background-color: %1", value)); -} - -// ============================================================================= -// Finds the given list widget item in the list of widget items given. -// ----------------------------------------------------------------------------- -int ConfigDialog::getItemRow (QListWidgetItem* item, QList& 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 ConfigDialog::getShortcutSelection() -{ - QList out; - - for (QListWidgetItem* entry : ui->shortcutsList->selectedItems()) - out << static_cast (entry); - - return out; -} - -// ============================================================================= -// Edit the shortcut of a given action. -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_setShortcut() -{ - QList sel = getShortcutSelection(); - - if (sel.size() < 1) - return; - - ShortcutListItem* item = sel[0]; - - if (KeySequenceDialog::staticDialog (item->getKeyConfig(), this)) - setShortcutText (item); -} - -// ============================================================================= -// Reset a shortcut to defaults -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_resetShortcut() -{ - QList sel = getShortcutSelection(); - - for (ShortcutListItem* item : sel) - { - item->getKeyConfig()->reset(); - setShortcutText (item); - } -} - -// ============================================================================= -// Remove the shortcut of an action. -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_clearShortcut() -{ - QList sel = getShortcutSelection(); - - for (ShortcutListItem* item : sel) - { - item->getKeyConfig()->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, fmt ("Path to %1", info->name), *info->path, g_extProgPathFilter); - - if (fpath.isEmpty()) - return; - - info->input->setText (fpath); -} - -// ============================================================================= -// '...' button pressed for the download path -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_findDownloadFolder() -{ - 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->getAction(); - QString label = act->iconText(); - QString keybind = item->getKeyConfig()->getValue().toString(); - item->setText (fmt ("%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 += fmt ("%1", entry.getColor()->index); - } - - return val; -} - -// =============================================================================================== -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// =============================================================================================== -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// =============================================================================================== -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// =============================================================================================== -KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f), seq (seq) -{ - lb_output = new QLabel; - IMPLEMENT_DIALOG_BUTTONS - - setWhatsThis (tr ("Into this dialog you can input a key sequence for use as a " - "shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to " - "dismiss.")); - - QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (lb_output); - layout->addWidget (bbx_buttons); - setLayout (layout); - - updateOutput(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool KeySequenceDialog::staticDialog (KeySequenceConfig* cfg, QWidget* parent) -{ - KeySequenceDialog dlg (cfg->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 = fmt ("

%1
", shortcut); - lb_output->setText (text); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) -{ - seq = ev->key() + ev->modifiers(); - updateOutput(); -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/configDialog.h --- a/src/configDialog.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,123 +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 . - */ - -#ifndef LDFORGE_CONFIGDIALOG_H -#define LDFORGE_CONFIGDIALOG_H - -#include "gui.h" -#include - -class Ui_ConfigUI; -class QLabel; -class QDoubleSpinBox; - -// ============================================================================= -class ShortcutListItem : public QListWidgetItem -{ - PROPERTY (public, KeySequenceConfig*, KeyConfig, NO_OPS, STOCK_WRITE) - PROPERTY (public, QAction*, Action, NO_OPS, 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 quickColors; - QDoubleSpinBox* dsb_gridData[3][4]; - - private: - Ui_ConfigUI* ui; - QLabel* lb_gridLabels[3]; - QLabel* lb_gridIcons[3]; - QList 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& haystack); - QString quickColorString(); - QListWidgetItem* getSelectedQuickColor(); - QList 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; -}; - -#endif // LDFORGE_CONFIGDIALOG_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/crashcatcher.cc --- a/src/crashcatcher.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,132 +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 . - */ - -#ifdef __unix__ - -#include -#include -#include -#include -#include -#include -#include -#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 g_signalsToCatch ({ - SIGSEGV, // segmentation fault - SIGABRT, // abort() calls - SIGFPE, // floating point exceptions (e.g. division by zero) - SIGILL, // illegal instructions -}); - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void handleCrash (int sig) -{ - printf ("%s: crashed with signal %d, launching gdb\n", __func__, sig); - - if (g_crashCatcherActive) - { - printf ("caught signal while crash catcher is active!\n"); - exit (149); - } - - const pid_t pid = getpid(); - QProcess proc; - QTemporaryFile commandsFile; - - g_crashCatcherActive = true; - - if (commandsFile.open()) - { - commandsFile.write (fmt ("attach %1\n", pid).toLocal8Bit()); - commandsFile.write (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. - prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0); - - proc.waitForFinished (1000); - QString output = QString (proc.readAllStandardOutput()); - QString err = QString (proc.readAllStandardError()); - - bombBox (fmt ("

Program crashed with signal %1

\n\n" - "%2" - "

GDB stdout:

%3
\n" - "

GDB stderr:

%4
", - sig, (!g_assertionFailure.isEmpty()) ? g_assertionFailure : "", output, err)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void initCrashCatcher() -{ - struct sigaction sighandler; - sighandler.sa_handler = &handleCrash; - sighandler.sa_flags = 0; - sigemptyset (&sighandler.sa_mask); - - for (int sig : g_signalsToCatch) - sigaction (sig, &sighandler, null); - - log ("%1: crash catcher hooked to signals: %2\n", __func__, g_signalsToCatch); -} -#endif // #ifdef __unix__ - -// ============================================================================= -// This function must be readily available in both Windows and Linux. We display -// the bomb box straight in Windows while in Linux we let abort() trigger the -// signal handler, which will cause the usual bomb box with GDB diagnostics. -// Said prompt will embed the assertion failure information. -// ----------------------------------------------------------------------------- -void assertionFailure (const char* file, int line, const char* funcname, const char* expr) -{ - QString errmsg = fmt ( - "

File: %1
" - "Line: %2
" - "Function: %3

" - "

Assertion `%4' failed.

", - file, line, funcname, expr); - - g_assertionFailure = errmsg; - -#ifndef __unix__ - bombBox (errmsg); -#endif - - abort(); -} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/crashcatcher.h --- a/src/crashcatcher.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +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 . - */ - -#ifndef LDFORGE_CRASHCATCHER_H -#define LDFORGE_CRASHCATCHER_H - -#ifdef __unix__ - -void initCrashCatcher(); - -#else // ifdef __unix__ -#define initCrashCatcher() -#endif // ifdef __unix__ -#endif // ifndef LDFORGE_CRASHCATCHER_H \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/dialogs.cc --- a/src/dialogs.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,379 +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 . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "dialogs.h" -#include "widgets.h" -#include "gui.h" -#include "gldraw.h" -#include "docs.h" -#include "document.h" -#include "dialogs.h" -#include "ui_overlay.h" -#include "ui_ldrawpath.h" -#include "ui_openprogress.h" -#include "ui_extprogpath.h" -#include "ui_about.h" -#include "ui_bombbox.h" -#include "moc_dialogs.cpp" - -extern const char* g_extProgPathFilter; -extern_cfg (String, io_ldpath); - -// ============================================================================= -// ----------------------------------------------------------------------------- -OverlayDialog::OverlayDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) -{ - ui = new Ui_OverlayUI; - ui->setupUi (this); - - m_cameraArgs = - { - { ui->top, GL::ETopCamera }, - { ui->bottom, GL::EBottomCamera }, - { ui->front, GL::EFrontCamera }, - { ui->back, GL::EBackCamera }, - { ui->left, GL::ELeftCamera }, - { ui->right, GL::ERightCamera } - }; - - GL::EFixedCamera cam = g_win->R()->camera(); - - if (cam == GL::EFreeCamera) - cam = GL::ETopCamera; - - connect (ui->width, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged())); - connect (ui->height, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged())); - connect (ui->buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_help())); - connect (ui->fileSearchButton, SIGNAL (clicked (bool)), this, SLOT (slot_fpath())); - - slot_dimensionsChanged(); - fillDefaults (cam); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -OverlayDialog::~OverlayDialog() -{ - delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void OverlayDialog::fillDefaults (int newcam) -{ - LDGLOverlay& info = g_win->R()->getOverlay (newcam); - radioDefault (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 (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 (fmt ("%1", LDPaths::getError())); - okButton()->setEnabled (false); - return; - } - - ui->status->setText ("OK!"); - okButton()->setEnabled (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDrawPathDialog::slot_accept() -{ - Config::save(); - accept(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) -{ - ui = new Ui_OpenProgressUI; - ui->setupUi (this); - ui->progressText->setText ("Parsing..."); - setNumLines (0); - m_Progress = 0; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -OpenProgressDialog::~OpenProgressDialog() -{ - delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void OpenProgressDialog::setNumLines (int const& a) -{ - m_NumLines = a; - ui->progressBar->setRange (0, getNumLines()); - updateValues(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void OpenProgressDialog::updateValues() -{ - ui->progressText->setText (fmt ("Parsing... %1 / %2", getProgress(), getNumLines())); - ui->progressBar->setValue (getProgress()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void OpenProgressDialog::updateProgress (int progress) -{ - setProgress (progress); - updateValues(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -ExtProgPathPrompt::ExtProgPathPrompt (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 ("", 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 " " + fullVersionString()); - - QPushButton* mailButton = new QPushButton; - mailButton->setText (tr ("Contact")); - mailButton->setIcon (getIcon ("mail")); - ui.buttonBox->addButton (static_cast (mailButton), QDialogButtonBox::HelpRole); - connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail())); - - setWindowTitle (fmt (tr ("About %1"), APPNAME)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AboutDialog::slot_mail() -{ - QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo ?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(); -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/dialogs.h --- a/src/dialogs.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,145 +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 . - */ - -#ifndef LDFORGE_DIALOGS_H -#define LDFORGE_DIALOGS_H - -#include -#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> 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, NUM_OPS, STOCK_WRITE) - PROPERTY (public, int, NumLines, NUM_OPS, 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); - -#endif // LDFORGE_DIALOGS_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/docs.cc --- a/src/docs.cc Mon Jan 20 23:44:22 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 . - */ - -#include -#include -#include -#include -#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 = - "

Overlay images


" - "

" 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.

" - "

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.

" - "

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.

" - "

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(); -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/docs.h --- a/src/docs.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +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 . - */ - -#ifndef LDFORGE_DOCS_H -#define LDFORGE_DOCS_H - -extern const char* g_docs_overlays; - -void showDocumentation (const char* text); - -#endif // LDFORGE_DOCS_H \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/document.cc --- a/src/document.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1435 +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 . - */ - -#include -#include -#include -#include -#include "main.h" -#include "config.h" -#include "document.h" -#include "misc.h" -#include "gui.h" -#include "history.h" -#include "dialogs.h" -#include "gldraw.h" -#include "misc/invokationDeferer.h" -#include "moc_document.cpp" - -cfg (String, io_ldpath, ""); -cfg (List, io_recentfiles, {}); -extern_cfg (String, net_downloadpath); -extern_cfg (Bool, gl_logostuds); - -static bool g_loadingMainFile = false; -static const int g_maxRecentFiles = 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
have LDConfig.ldr, parts/ and p/."; - return false; - } - - pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path); - pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path); - pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path); - - return true; - } - - // Accessors - 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); - setListItem (null); - setHistory (new History); - m_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 : getObjects()) - obj->deleteSelf(); - - // Clear the cache as well - for (LDObject* obj : getCache()) - obj->deleteSelf(); - - 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(); - log ("Closed %1", getName()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument* findDocument (QString name) -{ - for (LDDocument * file : g_loadedFiles) - if (!file->getName().isEmpty() && file->getName() == 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->getFullPath().isEmpty()) - continue; - - QString partpath = fmt ("%1/%2", dirname (doc->getFullPath()), 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 = fmt ("%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 : initlist ({ io_ldpath, net_downloadpath })) - { - for (const QString& subdir : initlist ({ "parts", "p" })) - { - fullPath = fmt ("%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) -{ - log ("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 (getLines().size()); - dlg->setModal (true); - dlg->show(); - - // Connect the loader in so we can show updates - connect (this, SIGNAL (workDone()), dlg, SLOT (accept())); - connect (dlg, SIGNAL (rejected()), this, SLOT (abort())); - } - else - dlg = null; - - // Begin working - work (0); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFileLoader::work (int i) -{ - // User wishes to abort, so stop here now. - if (isAborted()) - { - for (LDObject* obj : m_Objects) - obj->deleteSelf(); - - m_Objects.clear(); - setDone (true); - return; - } - - // Parse up to 300 lines per iteration - int max = i + 300; - - for (; i < max && i < (int) getLines().size(); ++i) - { - QString line = getLines()[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->getType() == LDObject::EError) - { - log ("Couldn't parse line #%1: %2", getProgress() + 1, static_cast (obj)->reason); - - if (getWarnings() != null) - (*getWarnings())++; - } - - m_Objects << obj; - setProgress (i); - - // If we have a dialog pointer, update the progress now - if (isOnForeground()) - dlg->updateProgress (i); - } - - // If we're done now, tell the environment we're done and stop. - if (i >= ((int) getLines().size()) - 1) - { - emit workDone(); - setDone (true); - return; - } - - // Otherwise, continue, by recursing back. - if (!isDone()) - { - // If we have a dialog to show progress output to, we cannot just call - // work() again immediately as the dialog needs some processor cycles as - // well. Thus, take a detour through the event loop by using the - // meta-object system. - // - // This terminates the loop here and control goes back to the function - // which called the file loader. It will keep processing the event loop - // until we're ready (see loadFileContents), thus the event loop will - // eventually catch the invokation we throw here and send us back. Though - // it's not technically recursion anymore, more like a for loop. :P - if (isOnForeground()) - QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i)); - else - work (i); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -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->getObjects(); - 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->getFullPath())); - dlog ("name: %1 (%2)", load->getName(), load->getFullPath()); - g_loadedFiles << load; - - // Don't take the file loading as actual edits to the file - load->getHistory()->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()->setFile (load); - log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); - } - - load->getHistory()->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 = fmt (tr ("There are unsaved changes to %1. Should it be saved?"), - (getName().length() > 0) ? getName() : tr ("")); - - 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 (getName().length() == 0) - { - QString newpath = QFileDialog::getSaveFileName (g_win, tr ("Save As"), - getCurrentDocument()->getName(), tr ("LDraw files (*.dat *.ldr)")); - - if (newpath.length() == 0) - return false; - - setName (newpath); - } - - if (!save()) - { - message = fmt (tr ("Failed to save %1 (%2)\nDo you still want to close?"), - getName(), 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 files = g_loadedFiles; - - for (LDDocument* file : files) - delete file; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void newFile() -{ - // Create a new anonymous file and set it to our current - LDDocument* f = new LDDocument; - f->setName (""); - f->setImplicit (false); - g_loadedFiles << f; - LDDocument::setCurrent (f); - LDDocument::closeInitialFile(); - g_win->R()->setFile (f); - g_win->doFullRefresh(); - g_win->updateTitle(); - g_win->updateActions(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void addRecentFile (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->getName() == 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 (fmt (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->getReferences()) - { dlog ("ptr: %1 (%2)\n", - ptr, ptr->getPointer() ? ptr->getPointer()->getName() : ""); - - ptr->operator= (file); - } - - assert (documentToReplace->countReferences() == 0); - 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 = getFullPath(); - - 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->getType() == LDObject::EComment) - { - LDComment* nameComment = static_cast (nameObject); - - if (nameComment->text.left (6) == "Name: ") - { - QString newname = shortenName (savepath); - nameComment->text = fmt ("Name: %1", newname); - g_win->buildObjList(); - } - } - - // File is open, now save the model to it. Note that LDraw requires files to - // have DOS line endings, so we terminate the lines with \r\n. - for (LDObject* obj : getObjects()) - f.write ((obj->raw() + "\r\n").toUtf8()); - - // File is saved, now clean up. - f.close(); - - // We have successfully saved, update the save position now. - setSavePosition (getHistory()->getPosition()); - setFullPath (savepath); - setName (shortenName (savepath)); - - g_win->updateDocumentListItem (this); - g_win->updateTitle(); - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -class LDParseError : public std::exception -{ - PROPERTY (private, QString, Error, STR_OPS, STOCK_WRITE) - PROPERTY (private, QString, Line, STR_OPS, STOCK_WRITE) - - public: - LDParseError (QString line, QString a) : m_Error (a), m_Line (line) {} - - const char* what() const throw() - { - return getError().toLocal8Bit().constData(); - } -}; - -// ============================================================================= -// ----------------------------------------------------------------------------- -void checkTokenCount (QString line, const QStringList& tokens, int num) -{ - if (tokens.size() != num) - throw LDParseError (line, fmt ("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, fmt ("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 == fmt ("BFC %1", LDBFC::statements [i])) - return new LDBFC ( (LDBFC::Type) i); - - // MLCAD is notorious for stuffing these statements in parts it - // creates. The above block only handles valid statements, so we - // need to handle MLCAD-style invertnext, clip and noclip separately. - struct - { - QString a; - LDBFC::Type b; - } BFCData[] = - { - { "INVERTNEXT", LDBFC::InvertNext }, - { "NOCLIP", LDBFC::NoClip }, - { "CLIP", LDBFC::Clip } - }; - - for (const auto& i : BFCData) - if (comm == "BFC CERTIFY " + i.a) - return new LDBFC (i.b); - } - - if (tokens.size() > 2 && tokens[1] == "!LDFORGE") - { - // Handle LDForge-specific types, they're embedded into comments too - if (tokens[2] == "VERTEX") - { - // Vertex (0 !LDFORGE VERTEX) - 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->text = 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, fmt ("Could not open %1", tokens[14])); - obj->setFileReferenced (tokens[14]); - return obj; - } - - LDSubfile* obj = new LDSubfile; - obj->setColor (tokens[1].toLong()); - obj->setPosition (parseVertex (tokens, 2)); // 2 - 4 - - Matrix transform; - - for (int i = 0; i < 9; ++i) - transform[i] = tokens[i + 5].toDouble(); // 5 - 13 - - obj->setTransform (transform); - obj->setFileInfo (load); - return obj; - } - - case 2: - { - 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.getLine(), e.getError()); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -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()->getObjects()) - { - if (obj->getType() == LDObject::ESubfile) - { - LDSubfile* ref = static_cast (obj); - LDDocument* fileInfo = getDocument (ref->getFileInfo()->getName()); - - if (fileInfo) - ref->setFileInfo (fileInfo); - else - ref->replace (new LDError (ref->raw(), fmt ("Could not open %1", ref->getFileInfo()->getName()))); - } - - // Reparse gibberish files. It could be that they are invalid because - // of loading errors. Circumstances may be different now. - if (obj->getType() == LDObject::EError) - obj->replace (parseLine (static_cast (obj)->contents)); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int LDDocument::addObject (LDObject* obj) -{ - getHistory()->add (new AddHistory (getObjects().size(), obj)); - m_Objects << obj; - - if (obj->getType() == LDObject::EVertex) - m_Vertices << obj; - -#ifdef DEBUG - if (!isImplicit()) - dlog ("Added object #%1 (%2)\n", obj->getID(), obj->getTypeName()); -#endif - - obj->setFile (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) -{ - getHistory()->add (new AddHistory (pos, obj)); - m_Objects.insert (pos, obj); - obj->setFile (this); - -#ifdef DEBUG - if (!isImplicit()) - dlog ("Inserted object #%1 (%2) at %3\n", obj->getID(), obj->getTypeName(), pos); -#endif -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::forgetObject (LDObject* obj) -{ - int idx = obj->getIndex(); - obj->unselect(); - assert (m_Objects[idx] == obj); - - if (!getHistory()->isIgnoring()) - getHistory()->add (new DelHistory (idx, obj)); - - m_Objects.removeAt (idx); - obj->setFile (null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool safeToCloseAll() -{ - for (LDDocument* f : g_loadedFiles) - if (!f->isSafeToClose()) - return false; - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::setObject (int idx, LDObject* obj) -{ - assert (idx >= 0 && idx < m_Objects.size()); - - // Mark this change to history - if (!m_History->isIgnoring()) - { - QString oldcode = getObject (idx)->raw(); - QString newcode = obj->raw(); - *m_History << new EditHistory (idx, oldcode, newcode); - } - - m_Objects[idx]->unselect(); - m_Objects[idx]->setFile (null); - obj->setFile (this); - m_Objects[idx] = obj; -} - -// ============================================================================= -// Close all implicit files with no references -// ----------------------------------------------------------------------------- -void LDDocument::closeUnused() -{ - for (LDDocument* file : g_loadedFiles) - if (file->isImplicit() && file->countReferences() == 0) - delete file; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDDocument::getObject (int pos) const -{ - if (m_Objects.size() <= pos) - return null; - - return m_Objects[pos]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int LDDocument::getObjectCount() const -{ - return getObjects().size(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDDocument::hasUnsavedChanges() const -{ - return !isImplicit() && getHistory()->getPosition() != getSavePosition(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString LDDocument::getDisplayName() -{ - if (!getName().isEmpty()) - return getName(); - - if (!getDefaultName().isEmpty()) - return "[" + getDefaultName() + "]"; - - return tr (""); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -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 (getName() == "stud.dat" && g_logoedStud) - return g_logoedStud->inlineContents (flags); - elif (getName() == "stud2.dat" && g_logoedStud2) - return g_logoedStud2->inlineContents (flags); - } - - LDObjectList objs, objcache; - - bool deep = flags & LDSubfile::DeepInline, - doCache = flags & LDSubfile::CacheInline; - - if (m_needsCache) - { - clearCache(); - doCache = true; - } - - // If we have this cached, just create a copy of that - if (deep && getCache().isEmpty() == false) - { - for (LDObject* obj : getCache()) - objs << obj->createCopy(); - } - else - { - if (!deep) - doCache = false; - - for (LDObject* obj : getObjects()) - { - // Skip those without scemantic meaning - if (!obj->isScemantic()) - continue; - - // Got another sub-file reference, inline it if we're deep-inlining. If not, - // just add it into the objects normally. Also, we only cache immediate - // subfiles and this is not one. Yay, recursion! - if (deep && obj->getType() == LDObject::ESubfile) - { - LDSubfile* ref = static_cast (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()->setFile (f); - g_win->R()->repaint(); - log ("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]->getName().isEmpty() && - g_loadedFiles[1]->getName().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); - - log (LDDocument::tr ("Logoed studs loaded.\n")); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::addToSelection (LDObject* obj) // [protected] -{ - if (obj->isSelected()) - return; - - assert (obj->getFile() == this); - m_sel << obj; - obj->setSelected (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::removeFromSelection (LDObject* obj) // [protected] -{ - if (!obj->isSelected()) - return; - - assert (obj->getFile() == this); - m_sel.removeOne (obj); - obj->setSelected (false); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::clearSelection() -{ - for (LDObject* obj : m_sel) - removeFromSelection (obj); - - assert (m_sel.isEmpty()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const 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->getID(), other->getID())); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -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) -{ - pushToReferences (ptr); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocument::removeReference (LDDocumentPointer* ptr) -{ - removeFromReferences (ptr); - - if (getReferences().size() == 0) - invokeLater (closeUnused); -} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/document.h --- a/src/document.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,245 +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 . - */ - -#ifndef LDFORGE_DOCUMENT_H -#define LDFORGE_DOCUMENT_H - -#include -#include "main.h" -#include "ldtypes.h" -#include "history.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(); -} - -// ============================================================================= -// LDDocument -// -// The LDDocument class stores a document opened in LDForge 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 -{ - properties: - Q_OBJECT - PROPERTY (private, LDObjectList, Objects, LIST_OPS, STOCK_WRITE) - PROPERTY (private, History*, History, NO_OPS, STOCK_WRITE) - PROPERTY (private, LDObjectList, Vertices, LIST_OPS, STOCK_WRITE) - PROPERTY (private, QList, References, LIST_OPS, STOCK_WRITE) - PROPERTY (public, QString, Name, STR_OPS, STOCK_WRITE) - PROPERTY (public, QString, FullPath, STR_OPS, STOCK_WRITE) - PROPERTY (public, QString, DefaultName, STR_OPS, STOCK_WRITE) - PROPERTY (public, bool, Implicit, BOOL_OPS, STOCK_WRITE) - PROPERTY (public, LDObjectList, Cache, LIST_OPS, STOCK_WRITE) - PROPERTY (public, long, SavePosition, NUM_OPS, STOCK_WRITE) - PROPERTY (public, QListWidgetItem*, ListItem, NO_OPS, 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() - { - m_History->addStep(); - } - - inline void undo() - { - m_History->undo(); - } - - inline void redo() - { - m_History->redo(); - } - - inline void clearHistory() - { - m_History->clear(); - } - - inline void addToHistory (AbstractHistoryEntry* entry) - { - *m_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 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 g_loadedFiles; // Vector of all currently opened files. - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -// FileLoader -// -// 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, NO_OPS, STOCK_WRITE) - PROPERTY (private, bool, Done, BOOL_OPS, STOCK_WRITE) - PROPERTY (private, int, Progress, NUM_OPS, STOCK_WRITE) - PROPERTY (private, bool, Aborted, BOOL_OPS, STOCK_WRITE) - PROPERTY (public, QStringList, Lines, NO_OPS, STOCK_WRITE) - PROPERTY (public, int*, Warnings, NO_OPS, STOCK_WRITE) - PROPERTY (public, bool, OnForeground, BOOL_OPS, STOCK_WRITE) - - public slots: - void start(); - void abort(); - - private: - OpenProgressDialog* dlg; - - private slots: - void work (int i); - - signals: - void progressUpdate (int progress); - void workDone(); -}; - -#endif // LDFORGE_DOCUMENT_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/download.cc --- a/src/download.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,542 +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 . - */ - -#include -#include -#include -#include -#include -#include -#include "download.h" -#include "ui_downloadfrom.h" -#include "types.h" -#include "gui.h" -#include "document.h" -#include "gldraw.h" -#include "configDialog.h" -#include "moc_download.cpp" - -cfg (String, net_downloadpath, ""); -cfg (Bool, net_guesspaths, true); -cfg (Bool, net_autoclose, true); - -const 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); - getInterface()->setupUi (this); - getInterface()->fname->setFocus(); - getInterface()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); - - setDownloadButton (new QPushButton (tr ("Download"))); - getInterface()->buttonBox->addButton (getDownloadButton(), QDialogButtonBox::ActionRole); - getButton (Abort)->setEnabled (false); - - connect (getInterface()->source, SIGNAL (currentIndexChanged (int)), - this, SLOT (sourceChanged (int))); - connect (getInterface()->buttonBox, SIGNAL (clicked (QAbstractButton*)), - this, SLOT (buttonClicked (QAbstractButton*))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloader::~PartDownloader() -{ - delete getInterface(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString PartDownloader::getURL() const -{ - const Source src = getSource(); - QString dest; - - switch (src) - { - case PartsTracker: - dest = getInterface()->fname->text(); - modifyDestination (dest); - return g_unofficialLibraryURL + dest; - - case CustomURL: - return getInterface()->fname->text(); - } - - // Shouldn't happen - return ""; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::modifyDestination (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) getInterface()->source->currentIndex(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::sourceChanged (int i) -{ - if (i == CustomURL) - getInterface()->fileNameLabel->setText (tr ("URL:")); - else - getInterface()->fileNameLabel->setText (tr ("File name:")); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::buttonClicked (QAbstractButton* btn) -{ - if (btn == getButton (Close)) - { - reject(); - } - elif (btn == getButton (Abort)) - { - setAborted (true); - - for (PartDownloadRequest* req : getRequests()) - req->abort(); - } - elif (btn == getButton (Download)) - { - QString dest = getInterface()->fname->text(); - setPrimaryFile (null); - setAborted (false); - - if (getSource() == CustomURL) - dest = basename (getURL()); - - modifyDestination (dest); - - if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest)) - { - const QString overwritemsg = fmt (tr ("%1 already exists in download directory. Overwrite?"), dest); - if (!confirm (tr ("Overwrite?"), overwritemsg)) - return; - } - - getDownloadButton()->setEnabled (false); - getInterface()->progress->setEnabled (true); - getInterface()->fname->setEnabled (false); - getInterface()->source->setEnabled (false); - downloadFile (dest, getURL(), true); - getButton (Close)->setEnabled (false); - getButton (Abort)->setEnabled (true); - getButton (Download)->setEnabled (false); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::downloadFile (QString dest, QString url, bool primary) -{ - const int row = getInterface()->progress->rowCount(); - - // Don't download files repeadetly. - if (getFilesToDownload().indexOf (dest) != -1) - return; - - modifyDestination (dest); - log ("DOWNLOAD: %1 -> %2\n", url, PartDownloader::getDownloadPath() + DIRSLASH + dest); - PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); - - pushToFilesToDownload (dest); - pushToRequests (req); - getInterface()->progress->insertRow (row); - req->setTableRow (row); - req->updateToTable(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::checkIfFinished() -{ - bool failed = isAborted(); - - // If there is some download still working, we're not finished. - for (PartDownloadRequest* req : getRequests()) - { - if (!req->isFinished()) - return; - - if (req->getState() == PartDownloadRequest::EFailed) - failed = true; - } - - for (PartDownloadRequest* req : getRequests()) - delete req; - - clearRequests(); - - // Update everything now - if (getPrimaryFile()) - { - LDDocument::setCurrent (getPrimaryFile()); - reloadAllSubfiles(); - g_win->doFullRefresh(); - g_win->R()->resetAngles(); - } - - if (net_autoclose && !failed) - { - // Close automatically if desired. - accept(); - } - else - { - // Allow the prompt be closed now. - getButton (Abort)->setEnabled (false); - getButton (Close)->setEnabled (true); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QPushButton* PartDownloader::getButton (PartDownloader::Button i) -{ - switch (i) - { - case Download: - return getDownloadButton(); - - case Abort: - return qobject_cast (getInterface()->buttonBox->button (QDialogButtonBox::Abort)); - - case Close: - return qobject_cast (getInterface()->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_NAM (new QNetworkAccessManager), - m_FirstUpdate (true), - m_Primary (primary), - m_FilePointer (null) -{ - // Make sure that we have a valid destination. - QString dirpath = dirname (getFilePath()); - - QDir dir (dirpath); - - if (!dir.exists()) - { - log ("Creating %1...\n", dirpath); - - if (!dir.mkpath (dirpath)) - critical (fmt (tr ("Couldn't create the directory %1!"), dirpath)); - } - - setReply (getNAM()->get (QNetworkRequest (QUrl (url)))); - connect (getReply(), SIGNAL (finished()), this, SLOT (downloadFinished())); - connect (getReply(), SIGNAL (readyRead()), this, SLOT (readyRead())); - connect (getReply(), SIGNAL (downloadProgress (qint64, qint64)), - this, SLOT (downloadProgress (qint64, qint64))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloadRequest::~PartDownloadRequest() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::updateToTable() -{ - const int labelcol = PartDownloader::PartLabelColumn, - progcol = PartDownloader::ProgressColumn; - QTableWidget* table = getPrompt()->getInterface()->progress; - QProgressBar* prog; - - switch (getState()) - { - case ERequesting: - case EDownloading: - { - prog = qobject_cast (table->cellWidget (getTableRow(), progcol)); - - if (!prog) - { - prog = new QProgressBar; - table->setCellWidget (getTableRow(), progcol, prog); - } - - prog->setRange (0, getBytesTotal()); - prog->setValue (getBytesRead()); - } break; - - case EFinished: - case EFailed: - { - const QString text = (getState() == EFinished) - ? "FINISHED" - : "FAILED"; - - QLabel* lb = new QLabel (text); - lb->setAlignment (Qt::AlignCenter); - table->setCellWidget (getTableRow(), progcol, lb); - } break; - } - - QLabel* lb = qobject_cast (table->cellWidget (getTableRow(), labelcol)); - - if (isFirstUpdate()) - { - lb = new QLabel (fmt ("%1", getDestinaton()), table); - table->setCellWidget (getTableRow(), labelcol, lb); - } - - // Make sure that the cell is big enough to contain the label - if (table->columnWidth (labelcol) < lb->width()) - table->setColumnWidth (labelcol, lb->width()); - - setFirstUpdate (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::downloadFinished() -{ - if (getReply()->error() != QNetworkReply::NoError) - { - if (isPrimary() && !getPrompt()->isAborted()) - critical (getReply()->errorString()); - - setState (EFailed); - } - elif (getState() != EFailed) - setState (EFinished); - - setBytesRead (getBytesTotal()); - updateToTable(); - - if (getFilePointer()) - { - getFilePointer()->close(); - delete getFilePointer(); - setFilePointer (null); - - if (getState() == EFailed) - QFile::remove (getFilePath()); - } - - if (getState() != EFinished) - { - getPrompt()->checkIfFinished(); - return; - } - - // Try to load this file now. - LDDocument* f = openDocument (getFilePath(), false); - - if (!f) - return; - - f->setImplicit (!isPrimary()); - - // Iterate through this file and check for errors. If there's any that stems - // from unknown file references, try resolve that by downloading the reference. - // This is why downloading a part may end up downloading multiple files, as - // it resolves dependencies. - for (LDObject* obj : f->getObjects()) - { - LDError* err = dynamic_cast (obj); - - if (!err || err->getFileReferenced().isEmpty()) - continue; - - QString dest = err->getFileReferenced(); - getPrompt()->modifyDestination (dest); - getPrompt()->downloadFile (dest, g_unofficialLibraryURL + dest, false); - } - - if (isPrimary()) - { - addRecentFile (getFilePath()); - getPrompt()->setPrimaryFile (f); - } - - getPrompt()->checkIfFinished(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::downloadProgress (int64 recv, int64 total) -{ - setBytesRead (recv); - setBytesTotal (total); - setState (EDownloading); - updateToTable(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::readyRead() -{ - if (getState() == EFailed) - return; - - if (getFilePointer() == null) - { - replaceInFilePath ("\\", "/"); - - // We have already asked the user whether we can overwrite so we're good - // to go here. - setFilePointer (new QFile (getFilePath().toLocal8Bit())); - - if (!getFilePointer()->open (QIODevice::WriteOnly)) - { - critical (fmt (tr ("Couldn't open %1 for writing: %2"), getFilePath(), strerror (errno))); - setState (EFailed); - getReply()->abort(); - updateToTable(); - getPrompt()->checkIfFinished(); - return; - } - } - - getFilePointer()->write (getReply()->readAll()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool PartDownloadRequest::isFinished() const -{ - return getState() == EFinished || getState() == EFailed; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::abort() -{ - getReply()->abort(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (DownloadFrom, 0) -{ - PartDownloader::staticBegin(); -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/download.h --- a/src/download.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +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 . - */ - -#ifndef LDFORGE_DOWNLOAD_H -#define LDFORGE_DOWNLOAD_H - -#include -#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 -{ - typedefs: - enum Source - { - PartsTracker, - CustomURL, - }; - - enum Button - { - Download, - Abort, - Close - }; - - enum TableColumn - { - PartLabelColumn, - ProgressColumn, - }; - - using RequestList = QList; - - properties: - Q_OBJECT - PROPERTY (public, LDDocument*, PrimaryFile, NO_OPS, STOCK_WRITE) - PROPERTY (public, bool, Aborted, BOOL_OPS, STOCK_WRITE) - PROPERTY (private, Ui_DownloadFrom*, Interface, NO_OPS, STOCK_WRITE) - PROPERTY (private, QStringList, FilesToDownload, LIST_OPS, STOCK_WRITE) - PROPERTY (private, RequestList, Requests, LIST_OPS, STOCK_WRITE) - PROPERTY (private, QPushButton*, DownloadButton, NO_OPS, 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 -{ - typedefs: - enum EState - { - ERequesting, - EDownloading, - EFinished, - EFailed, - }; - - properties: - Q_OBJECT - PROPERTY (public, int, TableRow, NUM_OPS, STOCK_WRITE) - PROPERTY (private, EState, State, NO_OPS, STOCK_WRITE) - PROPERTY (private, PartDownloader*, Prompt, NO_OPS, STOCK_WRITE) - PROPERTY (private, QString, URL, STR_OPS, STOCK_WRITE) - PROPERTY (private, QString, Destinaton, STR_OPS, STOCK_WRITE) - PROPERTY (private, QString, FilePath, STR_OPS, STOCK_WRITE) - PROPERTY (private, QNetworkAccessManager*, NAM, NO_OPS, STOCK_WRITE) - PROPERTY (private, QNetworkReply*, Reply, NO_OPS, STOCK_WRITE) - PROPERTY (private, bool, FirstUpdate, BOOL_OPS, STOCK_WRITE) - PROPERTY (private, int64, BytesRead, NUM_OPS, STOCK_WRITE) - PROPERTY (private, int64, BytesTotal, NUM_OPS, STOCK_WRITE) - PROPERTY (private, bool, Primary, BOOL_OPS, STOCK_WRITE) - PROPERTY (private, QFile*, FilePointer, NO_OPS, 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(); -}; - -#endif // LDFORGE_DOWNLOAD_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/extprogs.cc --- a/src/extprogs.cc Mon Jan 20 23:44:22 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 . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "main.h" -#include "config.h" -#include "misc.h" -#include "gui.h" -#include "document.h" -#include "widgets.h" -#include "history.h" -#include "ui_ytruder.h" -#include "ui_intersector.h" -#include "ui_rectifier.h" -#include "ui_coverer.h" -#include "ui_isecalc.h" -#include "ui_edger2.h" -#include "dialogs.h" - -enum extprog -{ - Isecalc, - Intersector, - Coverer, - Ytruder, - Rectifier, - Edger2, -}; - -// ============================================================================= -// ----------------------------------------------------------------------------- -cfg (String, prog_isecalc, ""); -cfg (String, prog_intersector, ""); -cfg (String, prog_coverer, ""); -cfg (String, prog_ytruder, ""); -cfg (String, prog_rectifier, ""); -cfg (String, prog_edger2, ""); - -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 fmt ("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 fmt ("Timed out (30 seconds)"); - } - - return ""; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void writeObjects (const LDObjectList& objects, QFile& f) -{ - for (LDObject* obj : objects) - { - if (obj->getType() == LDObject::ESubfile) - { - LDSubfile* ref = static_cast (obj); - LDObjectList objs = ref->inlineContents (LDSubfile::DeepInline); - - writeObjects (objs, f); - - for (LDObject* obj : objs) - obj->deleteSelf(); - } - else - f.write ((obj->raw() + "\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 (fmt ("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()->getObjects()) - { - if (obj->isColored() == false || obj->getColor() != 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 - - log ("cmdline: %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 (fmt ("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 = fmt ("Program exited abnormally (return code %1).", proc.exitCode()); - - if (!err.isEmpty()) - { - critical (fmt ("%1 failed: %2\n", g_extProgNames[prog], err)); - return false; - } - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void insertOutput (QString fname, bool replace, QList colorsToReplace) -{ -#ifdef DEBUG - QFile::copy (fname, "./debug_lastOutput"); -#endif // RELEASE - - // Read the output file - QFile f (fname); - - if (!f.open (QIODevice::ReadOnly)) - { - critical (fmt ("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->deleteSelf(); - 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 ? fmt ("-l %1", ui.dsb_segsplit->value()) : ""), - (ui.sb_bias->value() != 0 ? fmt ("-s %1", ui.sb_bias->value()) : ""), - in1DATName, - in2DATName, - outDATName - }); - - writeColorGroup (in1Col, in1DATName); - writeColorGroup (in2Col, in2DATName); - - if (!runUtilityProcess (Coverer, prog_coverer, argv)) - return; - - insertOutput (outDATName, false, {}); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Isecalc, 0) -{ - setlocale (LC_ALL, "C"); - - if (!checkProgPath (Isecalc)) - return; - - Ui::IsecalcUI ui; - QDialog* dlg = new QDialog; - ui.setupUi (dlg); - - makeColorComboBox (ui.cmb_col1); - makeColorComboBox (ui.cmb_col2); - - int in1Col, in2Col; - - // Run the dialog and validate input - forever - { - if (!dlg->exec()) - return; - - in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt(), - in2Col = ui.cmb_col1->itemData (ui.cmb_col2->currentIndex()).toInt(); - - if (in1Col == in2Col) - { - critical ("Cannot use the same color group for both input and cutter!"); - continue; - } - - break; - } - - QTemporaryFile in1dat, in2dat, outdat; - 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 ( - { - fmt ("-p %1", ui.precision->value()), - fmt ("-af %1", ui.flatAngle->value()), - fmt ("-ac %1", ui.condAngle->value()), - fmt ("-ae %1", ui.edgeAngle->value()), - ui.delLines->isChecked() ? "-de" : "", - ui.delCondLines->isChecked() ? "-dc" : "", - ui.colored->isChecked() ? "-c" : "", - ui.bfc->isChecked() ? "-b" : "", - ui.convex->isChecked() ? "-cx" : "", - ui.concave->isChecked() ? "-cv" : "", - unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""), - inName, - outName, - }); - - writeSelection (inName); - - if (!runUtilityProcess (Edger2, prog_edger2, argv)) - return; - - insertOutput (outName, true, {}); -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/gldraw.cc --- a/src/gldraw.cc Mon Jan 20 23:44:22 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 . - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "config.h" -#include "document.h" -#include "gldraw.h" -#include "colors.h" -#include "gui.h" -#include "misc.h" -#include "history.h" -#include "dialogs.h" -#include "addObjectDialog.h" -#include "messagelog.h" -#include "primitives.h" -#include "misc/ringFinder.h" -#include "moc_gldraw.cpp" - -static const LDFixedCameraInfo g_FixedCameras[6] = -{ - {{ 1, 0, 0 }, X, Z, false, false }, - {{ 0, 0, 0 }, X, Y, false, true }, - {{ 0, 1, 0 }, Z, Y, true, true }, - {{ -1, 0, 0 }, X, Z, false, true }, - {{ 0, 0, 0 }, X, Y, true, true }, - {{ 0, -1, 0 }, Z, Y, false, true }, -}; - -// 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 g_warnedColors; - -// ============================================================================= -// -GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) -{ - m_Picking = m_rangepick = false; - m_camera = (GL::EFixedCamera) gl_camera; - m_drawToolTip = false; - m_EditMode = ESelectMode; - m_rectdraw = false; - m_panning = false; - setFile (null); - setDrawOnly (false); - setMessageLog (null); - m_width = m_height = -1; - m_hoverpos = g_origin; - - m_toolTipTimer = new QTimer (this); - m_toolTipTimer->setSingleShot (true); - connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer())); - - m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); - m_thinBorderPen = m_thickBorderPen; - m_thinBorderPen.setWidth (1); - - // Init camera icons - for (const GL::EFixedCamera cam : g_Cameras) - { - QString iconname = fmt ("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()->getID(); - - // Calculate a color based from this index. This method caters for - // 16777216 objects. I don't think that'll be exceeded anytime soon. :) - // ATM biggest is 53588.dat with 12600 lines. - double r = (i / 0x10000) % 0x100, - g = (i / 0x100) % 0x100, - b = i % 0x100; - - qglColor (QColor (r, g, b)); - return; - } - - if ((list == BFCFrontList || list == BFCBackList) && - obj->getType() != LDObject::ELine && - obj->getType() != LDObject::ECondLine) - { - if (list == GL::BFCFrontList) - qcol = QColor (40, 192, 0); - else - qcol = QColor (224, 0, 0); - } - else - { - if (obj->getColor() == maincolor) - qcol = getMainColor(); - else - { - LDColor* col = getColor (obj->getColor()); - - if (col) - qcol = col->faceColor; - } - - if (obj->getColor() == edgecolor) - { - LDColor* col; - - if (!gl_blackedges && obj->getParent() && (col = getColor (obj->getParent()->getColor()))) - 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->getColor() != edgecolor) - qcol = getMainColor(); - - // Warn about the unknown colors, but only once. - for (int i : g_warnedColors) - if (obj->getColor() == i) - return; - - log ("%1: Unknown color %2!\n", __func__, obj->getColor()); - g_warnedColors << obj->getColor(); - return; - } - } - - 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 (getFile() == null) - return; - - if (gl_wireframe && !isPicking()) - glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); - - glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable (GL_DEPTH_TEST); - - if (m_camera != EFreeCamera) - { - glMatrixMode (GL_PROJECTION); - glPushMatrix(); - - glLoadIdentity(); - glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f); - glTranslatef (pan (X), pan (Y), 0.0f); - - if (m_camera != EFrontCamera && m_camera != EBackCamera) - { - glRotatef (90.0f, g_FixedCameras[camera()].glrotate[0], - g_FixedCameras[camera()].glrotate[1], - g_FixedCameras[camera()].glrotate[2]); - } - - // Back camera needs to be handled differently - if (m_camera == GLRenderer::EBackCamera) - { - glRotatef (180.0f, 1.0f, 0.0f, 0.0f); - glRotatef (180.0f, 0.0f, 0.0f, 1.0f); - } - } - else - { - glMatrixMode (GL_MODELVIEW); - glPushMatrix(); - glLoadIdentity(); - - glTranslatef (0.0f, 0.0f, -2.0f); - glTranslatef (pan (X), pan (Y), -zoom()); - glRotatef (rot (X), 1.0f, 0.0f, 0.0f); - glRotatef (rot (Y), 0.0f, 1.0f, 0.0f); - glRotatef (rot (Z), 0.0f, 0.0f, 1.0f); - } - - const GL::ListType list = (!isDrawOnly() && isPicking()) ? PickList : NormalList; - - if (gl_colorbfc && !isPicking() && !isDrawOnly()) - { - glEnable (GL_CULL_FACE); - - for (LDObject* obj : getFile()->getObjects()) - { - if (obj->isHidden()) - continue; - - glCullFace (GL_BACK); - glCallList (obj->glLists[BFCFrontList]); - - glCullFace (GL_FRONT); - glCallList (obj->glLists[BFCBackList]); - } - - glDisable (GL_CULL_FACE); - } - else - { - for (LDObject* obj : getFile()->getObjects()) - { - if (obj->isHidden()) - continue; - - glCallList (obj->glLists[list]); - } - } - - if (gl_axes && !isPicking() && !isDrawOnly()) - glCallList (m_axeslist); - - glPopMatrix(); - glMatrixMode (GL_MODELVIEW); - glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); -} - -// ============================================================================= -// -// This converts a 2D point on the screen to a 3D point in the model. If 'snap' -// is true, the 3D point will snap to the current grid. -// -Vertex GLRenderer::coordconv2_3 (const QPoint& pos2d, bool snap) const -{ - assert (camera() != EFreeCamera); - - Vertex pos3d; - const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; - const Axis axisX = cam->axisX; - const Axis axisY = cam->axisY; - const int negXFac = cam->negX ? -1 : 1, - negYFac = cam->negY ? -1 : 1; - - // Calculate cx and cy - these are the LDraw unit coords the cursor is at. - double cx = (-m_virtWidth + ((2 * pos2d.x() * m_virtWidth) / m_width) - pan (X)); - double cy = (m_virtHeight - ((2 * pos2d.y() * m_virtHeight) / m_height) - pan (Y)); - - if (snap) - { - cx = Grid::snap (cx, (Grid::Config) axisX); - cy = Grid::snap (cy, (Grid::Config) axisY); - } - - cx *= negXFac; - cy *= negYFac; - - 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 = fmt (tr ("X: %1, Y: %2, Z: %3"), m_hoverpos[X], m_hoverpos[Y], m_hoverpos[Z]); - QFontMetrics metrics = QFontMetrics (font()); - QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); - paint.setPen (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 (getEditMode() == 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 (getEditMode() == ECircleMode) - { - // If we have not specified the center point of the circle yet, preview it on the screen. - if (m_drawedVerts.isEmpty()) - drawBlip (paint, coordconv3_2 (m_hoverpos)); - else - { - QVector verts, verts2; - const double dist0 = getCircleDrawDist (0), - dist1 = (m_drawedVerts.size() >= 2) ? getCircleDrawDist (1) : -1; - const int segs = lores; - const double angleUnit = (2 * pi) / segs; - Axis relX, relY; - QVector 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] && getEditMode() != ESelectMode) - continue; - - paint.drawPixmap (info.destRect, *info.img, info.srcRect); - } - - QString fmtstr = tr ("%1 Camera"); - - // Draw a label for the current camera in the bottom left corner - { - const int margin = 4; - - QString label; - label = fmt (fmtstr, tr (g_CameraNames[camera()])); - paint.setPen (textpen); - paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label); - } - - // Tool tips - if (m_drawToolTip) - { - if (m_cameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false) - m_drawToolTip = false; - else - { - QString label = fmt (fmtstr, tr (g_CameraNames[m_toolTipCamera])); - QToolTip::showText (m_globalpos, label); - } - } - } - - // Message log - if (getMessageLog()) - { - int y = 0; - const int margin = 2; - QColor penColor = textpen.color(); - - for (const MessageManager::Line& line : getMessageLog()->getLines()) - { - penColor.setAlphaF (line.alpha); - paint.setPen (penColor); - paint.drawText (QPoint (margin, y + margin + metrics.ascent()), line.text); - y += metrics.height(); - } - } - - // If we're range-picking, draw a rectangle encompassing the selection area. - if (m_rangepick && !isPicking() && m_totalmove >= 10) - { - int x0 = m_rangeStart.x(), - y0 = m_rangeStart.y(), - x1 = m_pos.x(), - y1 = m_pos.y(); - - QRect rect (x0, y0, x1 - x0, y1 - y0); - QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF"); - fillColor.setAlphaF (0.2f); - - paint.setPen (m_thickBorderPen); - paint.setBrush (QBrush (fillColor)); - paint.drawRect (rect); - } -} - -// ============================================================================= -// -void GLRenderer::drawBlip (QPainter& paint, QPoint pos) const -{ - QPen pen = m_thinBorderPen; - const int blipsize = 8; - pen.setWidth (1); - paint.setPen (pen); - paint.setBrush (QColor (64, 192, 0)); - paint.drawEllipse (pos.x() - blipsize / 2, pos.y() - blipsize / 2, blipsize, blipsize); -} - -// ============================================================================= -// -void GLRenderer::compileAllObjects() -{ - if (!getFile()) - return; - - // Compiling all is a big job, use a busy cursor - setCursor (Qt::BusyCursor); - - m_knownVerts.clear(); - - for (LDObject* obj : getFile()->getObjects()) - compileObject (obj); - - // Compile axes - glDeleteLists (m_axeslist, 1); - m_axeslist = glGenLists (1); - glNewList (m_axeslist, GL_COMPILE); - glBegin (GL_LINES); - - for (const LDGLAxis& ax : g_GLAxes) - { - qglColor (ax.col); - compileVertex (ax.vert); - compileVertex (-ax.vert); - } - - glEnd(); - glEndList(); - - setCursor (Qt::ArrowCursor); -} - -// ============================================================================= -// -void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype) -{ - glBegin (gltype); - - const int numverts = (obj->getType() != LDObject::ECondLine) ? obj->vertices() : 2; - - if (g_glInvert == false) - for (int i = 0; i < numverts; ++i) - compileVertex (obj->getVertex (i)); - else - for (int i = numverts - 1; i >= 0; --i) - compileVertex (obj->getVertex (i)); - - glEnd(); -} - -// ============================================================================= -// -void GLRenderer::compileList (LDObject* obj, const GLRenderer::ListType list) -{ - setObjectColor (obj, list); - - switch (obj->getType()) - { - case LDObject::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 (obj); - LDObjectList objs; - - objs = ref->inlineContents (LDSubfile::DeepCacheInline | LDSubfile::RendererInline); - bool oldinvert = g_glInvert; - - if (ref->getTransform().getDeterminant() < 0) - g_glInvert = !g_glInvert; - - LDObject* prev = ref->prev(); - - if (prev && prev->getType() == LDObject::EBFC && static_cast (prev)->type == LDBFC::InvertNext) - g_glInvert = !g_glInvert; - - for (LDObject* obj : objs) - { - compileList (obj, list); - obj->deleteSelf(); - } - - g_glInvert = oldinvert; - } break; - - default: - break; - } -} - -// ============================================================================= -// -void GLRenderer::compileVertex (const Vertex& vrt) -{ - glVertex3d (vrt[X], -vrt[Y], -vrt[Z]); -} - -// ============================================================================= -// -void GLRenderer::clampAngle (double& angle) const -{ - while (angle < 0) - angle += 360.0; - - while (angle > 360.0) - angle -= 360.0; -} - -// ============================================================================= -// -void GLRenderer::addDrawnVertex (Vertex pos) -{ - // If we picked an already-existing vertex, stop drawing - if (getEditMode() == 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 (getEditMode()) - { - case EDrawMode: - { - if (m_rectdraw) - { - if (m_drawedVerts.size() == 2) - { - endDraw (true); - return; - } - } else - { - // If we have 4 verts, stop drawing. - if (m_drawedVerts.size() >= 4) - { - endDraw (true); - return; - } - - if (m_drawedVerts.isEmpty() && ev->modifiers() & Qt::ShiftModifier) - { - m_rectdraw = true; - updateRectVerts(); - } - } - - addDrawnVertex (m_hoverpos); - } break; - - case ECircleMode: - { - if (m_drawedVerts.size() == 3) - { - endDraw (true); - return; - } - - addDrawnVertex (m_hoverpos); - } break; - - case ESelectMode: - { - if (!isDrawOnly()) - { - if (m_totalmove < 10) - m_rangepick = false; - - if (!m_rangepick) - m_addpick = (m_keymods & Qt::ControlModifier); - - if (m_totalmove < 10 || m_rangepick) - pick (ev->x(), ev->y()); - } - } break; - } - - m_rangepick = false; - } - - if (wasMid && getEditMode() != ESelectMode && m_drawedVerts.size() < 4 && m_totalmove < 10) - { - // Find the closest vertex to our cursor - double mindist = 1024.0f; - Vertex closest; - bool valid = false; - - QPoint curspos = coordconv3_2 (m_hoverpos); - - for (const Vertex& pos3d: m_knownVerts) - { - QPoint pos2d = coordconv3_2 (pos3d); - - // Measure squared distance - const double dx = abs (pos2d.x() - curspos.x()), - dy = abs (pos2d.y() - curspos.y()), - distsq = (dx * dx) + (dy * dy); - - if (distsq >= 1024.0f) // 32.0f ** 2 - continue; // too far away - - if (distsq < mindist) - { - mindist = distsq; - closest = pos3d; - valid = true; - - // If it's only 4 pixels away, I think we found our vertex now. - if (distsq <= 16.0f) // 4.0f ** 2 - break; - } - } - - if (valid) - addDrawnVertex (closest); - } - - if (wasRight && !m_drawedVerts.isEmpty()) - { - // Remove the last vertex - m_drawedVerts.removeLast(); - - if (m_drawedVerts.isEmpty()) - m_rectdraw = false; - } - -end: - update(); - m_totalmove = 0; -} - -// ============================================================================= -// -void GLRenderer::mousePressEvent (QMouseEvent* ev) -{ - m_totalmove = 0; - - if (ev->modifiers() & Qt::ControlModifier) - { - m_rangepick = true; - m_rangeStart.setX (ev->x()); - m_rangeStart.setY (ev->y()); - m_addpick = (m_keymods & Qt::AltModifier); - ev->accept(); - } - - m_lastButtons = ev->buttons(); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::mouseMoveEvent (QMouseEvent* ev) -{ - int dx = ev->x() - m_pos.x(); - int dy = ev->y() - m_pos.y(); - m_totalmove += abs (dx) + abs (dy); - - const bool left = ev->buttons() & Qt::LeftButton, - mid = ev->buttons() & Qt::MidButton, - shift = ev->modifiers() & Qt::ShiftModifier; - - if (mid || (left && shift)) - { - pan (X) += 0.03f * dx * (zoom() / 7.5f); - pan (Y) -= 0.03f * dy * (zoom() / 7.5f); - m_panning = true; - } elif (left && !m_rangepick && camera() == EFreeCamera) - { - rot (X) = rot (X) + dy; - rot (Y) = rot (Y) + dx; - - clampAngle (rot (X)); - clampAngle (rot (Y)); - } - - // Start the tool tip timer - if (!m_drawToolTip) - m_toolTipTimer->start (500); - - // Update 2d position - m_pos = ev->pos(); - m_globalpos = ev->globalPos(); - - // Calculate 3d position of the cursor - m_hoverpos = (camera() != EFreeCamera) ? coordconv2_3 (m_pos, true) : g_origin; - - // Update rect vertices since m_hoverpos may have changed - updateRectVerts(); - - update(); -} - -// ============================================================================= -// -void GLRenderer::keyPressEvent (QKeyEvent* ev) -{ - m_keymods = ev->modifiers(); -} - -// ============================================================================= -// -void GLRenderer::keyReleaseEvent (QKeyEvent* ev) -{ - m_keymods = ev->modifiers(); -} - -// ============================================================================= -// -void GLRenderer::wheelEvent (QWheelEvent* ev) -{ - makeCurrent(); - - zoomNotch (ev->delta() > 0); - zoom() = clamp (zoom(), 0.01, 10000.0); - - update(); - ev->accept(); -} - -// ============================================================================= -// -void GLRenderer::leaveEvent (QEvent* ev) -{ - (void) ev; - m_drawToolTip = false; - m_toolTipTimer->stop(); - update(); -} - -// ============================================================================= -// -void GLRenderer::contextMenuEvent (QContextMenuEvent* ev) -{ - g_win->spawnContextMenu (ev->globalPos()); -} - -// ============================================================================= -// -void GLRenderer::setCamera (const GLRenderer::EFixedCamera cam) -{ - m_camera = cam; - gl_camera = (int) cam; - g_win->updateEditModeActions(); -} - -// ============================================================================= -// -void GLRenderer::pick (int mouseX, int mouseY) -{ - makeCurrent(); - - // Use particularly thick lines while picking ease up selecting lines. - glLineWidth (max (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::setFile (LDDocument* const& a) -{ - m_File = 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& verts = m_drawedVerts; - LDObjectList objs; - - switch (getEditMode()) - { - case EDrawMode: - { - if (m_rectdraw) - { - LDQuad* quad = new LDQuad; - - // Copy the vertices from m_rectverts - updateRectVerts(); - - for (int i = 0; i < quad->vertices(); ++i) - quad->setVertex (i, m_rectverts[i]); - - quad->setColor (maincolor); - objs << quad; - } - else - { - switch (verts.size()) - { - case 1: - { - // 1 vertex - add a vertex object - LDVertex* obj = new LDVertex; - obj->pos = verts[0]; - obj->setColor (maincolor); - objs << obj; - } break; - - case 2: - { - // 2 verts - make a line - LDLine* obj = new LDLine (verts[0], verts[1]); - obj->setColor (edgecolor); - objs << obj; - } break; - - case 3: - case 4: - { - LDObject* obj = (verts.size() == 3) ? - static_cast (new LDTriangle) : - static_cast (new LDQuad); - - obj->setColor (maincolor); - - for (int i = 0; i < obj->vertices(); ++i) - obj->setVertex (i, verts[i]); - - objs << obj; - } break; - } - } - } break; - - case ECircleMode: - { - const int segs = lores, divs = lores; // TODO: make customizable - double dist0 = getCircleDrawDist (0), - dist1 = getCircleDrawDist (1); - LDDocument* refFile = null; - Matrix transform; - bool circleOrDisc = false; - - if (dist1 < dist0) - std::swap (dist0, dist1); - - if (dist0 == dist1) - { - // If the radii are the same, there's no ring space to fill. Use a circle. - refFile = ::getDocument ("4-4edge.dat"); - transform = getCircleDrawMatrix (dist0); - circleOrDisc = true; - } - elif (dist0 == 0 || dist1 == 0) - { - // If either radii is 0, use a disc. - refFile = ::getDocument ("4-4disc.dat"); - transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); - circleOrDisc = true; - } - elif (g_RingFinder (dist0, dist1)) - { - // The ring finder found a solution, use that. Add the component rings to the file. - for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents()) - { - // Get a ref file for this primitive. If we cannot find it in the - // LDraw library, generate it. - if ((refFile = ::getDocument (radialFileName (::Ring, lores, lores, cmp.num))) == null) - { - refFile = generatePrimitive (::Ring, lores, lores, cmp.num); - refFile->setImplicit (false); - } - - LDSubfile* ref = new LDSubfile; - ref->setFileInfo (refFile); - ref->setTransform (getCircleDrawMatrix (cmp.scale)); - ref->setPosition (m_drawedVerts[0]); - ref->setColor (maincolor); - objs << ref; - } - } - else - { - // Ring finder failed, last resort: draw the ring with quads - QList 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) - { - getFile()->addObject (obj); - compileObject (obj); - } - - g_win->refresh(); - g_win->endAction(); - } - - m_drawedVerts.clear(); - m_rectdraw = false; -} - -// ============================================================================= -// -double GLRenderer::getCircleDrawDist (int pos) const -{ - assert (m_drawedVerts.size() >= pos + 1); - const Vertex& v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : m_hoverpos; - Axis relX, relY; - getRelativeAxes (relX, relY); - - const double dx = m_drawedVerts[0][relX] - v1[relX]; - const double dy = m_drawedVerts[0][relY] - v1[relY]; - return sqrt ((dx * dx) + (dy * dy)); -} - -// ============================================================================= -// -void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const -{ - const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; - relX = cam->axisX; - relY = cam->axisY; -} - -// ============================================================================= -// -static QList getVertices (LDObject* obj) -{ - QList verts; - - if (obj->vertices() >= 2) - { - for (int i = 0; i < obj->vertices(); ++i) - verts << obj->getVertex (i); - } elif (obj->getType() == LDObject::ESubfile) - { - LDSubfile* ref = static_cast (obj); - LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline); - - for (LDObject* obj : objs) - { - verts << getVertices (obj); - obj->deleteSelf(); - } - } - - 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 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 (getFile() == null || m_width == -1 || m_height == -1) - { - zoom() = 30.0f; - return; - } - - bool lastfilled = false; - bool firstrun = true; - const uint32 white = 0xFFFFFFFF; - bool inward = true; - const int w = m_width, h = m_height; - 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 (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) || getEditMode() != ESelectMode) - return; - - pick (ev->x(), ev->y()); - - if (selection().isEmpty()) - return; - - LDObject* obj = selection().first(); - AddObjectDialog::staticDialog (obj->getType(), obj); - g_win->endAction(); - ev->accept(); -} - -// ============================================================================= -// -LDOverlay* GLRenderer::findOverlayObject (EFixedCamera cam) -{ - LDOverlay* ovlobj = null; - - for (LDObject* obj : getFile()->getObjects()) - { - if (obj->getType() == LDObject::EOverlay && static_cast (obj)->getCamera() == cam) - { - ovlobj = static_cast (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->getFileName())) - setupOverlay (cam, ovlobj->getFileName(), ovlobj->getX(), - ovlobj->getY(), ovlobj->getWidth(), ovlobj->getHeight()); - } -} - -// ============================================================================= -// -void GLRenderer::updateOverlayObjects() -{ - for (EFixedCamera cam : g_Cameras) - { - if (cam == EFreeCamera) - continue; - - LDGLOverlay& meta = 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->getType() == LDObject::EEmpty) - nextobj->deleteSelf(); - - // If the overlay object was there and the overlay itself is - // not, remove the object. - ovlobj->deleteSelf(); - } elif (meta.img && !ovlobj) - { - // Inverse case: image is there but the overlay object is - // not, thus create the object. - ovlobj = new LDOverlay; - - // Find a suitable position to place this object. We want to place - // this into the header, which is everything up to the first scemantic - // object. If we find another overlay object, place this object after - // the last one found. Otherwise, place it before the first schemantic - // object and put an empty object after it (though don't do this if - // there was no schemantic elements at all) - int i, lastOverlay = -1; - bool found = false; - - for (i = 0; i < getFile()->getObjectCount(); ++i) - { - LDObject* obj = getFile()->getObject (i); - - if (obj->isScemantic()) - { - found = true; - break; - } - - if (obj->getType() == LDObject::EOverlay) - lastOverlay = i; - } - - if (lastOverlay != -1) - getFile()->insertObj (lastOverlay + 1, ovlobj); - else - { - getFile()->insertObj (i, ovlobj); - - if (found) - getFile()->insertObj (i + 1, new LDEmpty); - } - } - - if (meta.img && ovlobj) - { - ovlobj->setCamera (cam); - ovlobj->setFileName (meta.fname); - ovlobj->setX (meta.ox); - ovlobj->setY (meta.oy); - ovlobj->setWidth (meta.lw); - ovlobj->setHeight (meta.lh); - } - } - - if (g_win->R() == this) - g_win->refresh(); -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/gldraw.h --- a/src/gldraw.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,309 +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 . - */ - -#ifndef LDFORGE_GLDRAW_H -#define LDFORGE_GLDRAW_H - -#include -#include "main.h" -#include "ldtypes.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 -{ - typedefs: - 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; - }; - - properties: - Q_OBJECT - PROPERTY (public, bool, DrawOnly, BOOL_OPS, STOCK_WRITE) - PROPERTY (public, MessageManager*, MessageLog, NO_OPS, STOCK_WRITE) - PROPERTY (private, bool, Picking, BOOL_OPS, STOCK_WRITE) - PROPERTY (public, LDDocument*, File, NO_OPS, CUSTOM_WRITE) - PROPERTY (public, EditMode, EditMode, NO_OPS, 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 m_drawedVerts; - bool m_rectdraw; - Vertex m_rectverts[4]; - QColor m_bgcolor; - QList 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 *getFile()->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()]; - } - - 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]; - -#endif // LDFORGE_GLDRAW_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/gui.cc --- a/src/gui.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1011 +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 . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "gldraw.h" -#include "gui.h" -#include "document.h" -#include "config.h" -#include "misc.h" -#include "colors.h" -#include "history.h" -#include "widgets.h" -#include "addObjectDialog.h" -#include "messagelog.h" -#include "config.h" -#include "ui_ldforge.h" -#include "moc_gui.cpp" - -static bool g_isSelectionLocked = false; - -cfg (Bool, lv_colorize, true); -cfg (String, gui_colortoolbar, "16:24:|: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); - -// ============================================================================= -// ----------------------------------------------------------------------------- -ForgeWindow::ForgeWindow() -{ - g_win = this; - m_renderer = new GLRenderer; - - ui = new Ui_LDForgeUI; - ui->setupUi (this); - - // Stuff the renderer into its frame - QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame); - rendererLayout->addWidget (R()); - - connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged())); - connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*))); - connect (ui->fileList, SIGNAL (currentItemChanged (QListWidgetItem*, QListWidgetItem*)), this, SLOT (changeCurrentFile())); - - // Init message log manager - m_msglog = new MessageManager; - m_msglog->setRenderer (R()); - m_renderer->setMessageLog (m_msglog); - m_quickColors = quickColorsFromConfig(); - slot_selectionChanged(); - setStatusBar (new QStatusBar); - - // Make certain actions checkable - ui->actionAxes->setChecked (gl_axes); - ui->actionWireframe->setChecked (gl_wireframe); - ui->actionBFCView->setChecked (gl_colorbfc); - updateGridToolBar(); - updateEditModeActions(); - updateRecentFilesMenu(); - updateToolBars(); - updateTitle(); - updateActionShortcuts(); - - setMinimumSize (300, 200); - - connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup())); - - // Connect all actions - for (QAction* act : findChildren()) - if (!act->objectName().isEmpty()) - connect (act, SIGNAL (triggered()), this, SLOT (slot_action())); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -KeySequenceConfig* ForgeWindow::shortcutForAction (QAction* act) -{ - QString keycfgname = fmt ("key_%1", act->objectName()); - return KeySequenceConfig::getByName (keycfgname); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateActionShortcuts() -{ - for (QAction* act : findChildren()) - { - KeySequenceConfig* cfg = shortcutForAction (act); - - if (cfg) - act->setShortcut (cfg->getValue()); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_action() -{ - // Get the name of the sender object and use it to compose the slot name. - QString methodName = fmt ("slot_%1", sender()->objectName()); - -#ifdef DEBUG - log ("Action %1 triggered", sender()->objectName()); -#endif - - // Now invoke this slot to call the action. - QMetaObject::invokeMethod (this, methodName.toAscii().constData(), Qt::DirectConnection); - endAction(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::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 ForgeWindow::slot_lastSecondCleanup() -{ - delete m_renderer; - delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateRecentFilesMenu() -{ - // First, clear any items in the recent files menu -for (QAction * recent : m_recentFiles) - delete recent; - - m_recentFiles.clear(); - - QAction* first = null; - - for (const QVariant& it : io_recentfiles) - { - 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 quickColorsFromConfig() -{ - QList 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 ForgeWindow::updateToolBars() -{ - m_colorButtons.clear(); - ui->colorToolbar->clear(); - - for (LDQuickColor& entry : m_quickColors) - { - if (entry.isSeparator()) - ui->colorToolbar->addSeparator(); - else - { - QToolButton* colorButton = new QToolButton; - colorButton->setIcon (makeColorIcon (entry.getColor(), 22)); - colorButton->setIconSize (QSize (22, 22)); - colorButton->setToolTip (entry.getColor()->name); - - connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor())); - ui->colorToolbar->addWidget (colorButton); - m_colorButtons << colorButton; - - entry.setToolButton (colorButton); - } - } - - updateGridToolBar(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateGridToolBar() -{ - // Ensure that the current grid - and only the current grid - is selected. - ui->actionGridCoarse->setChecked (grid == Grid::Coarse); - ui->actionGridMedium->setChecked (grid == Grid::Medium); - ui->actionGridFine->setChecked (grid == Grid::Fine); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateTitle() -{ - QString title = fmt (APPNAME " %1", fullVersionString()); - - // Append our current file if we have one - if (getCurrentDocument()) - { - if (getCurrentDocument()->getName().length() > 0) - title += fmt (": %1", basename (getCurrentDocument()->getName())); - else - title += fmt (": "); - - if (getCurrentDocument()->getObjectCount() > 0 && - getCurrentDocument()->getObject (0)->getType() == LDObject::EComment) - { - // Append title - LDComment* comm = static_cast (getCurrentDocument()->getObject (0)); - title += fmt (": %1", comm->text); - } - - if (getCurrentDocument()->getHistory()->getPosition() != getCurrentDocument()->getSavePosition()) - title += '*'; - } - -#ifdef DEBUG - title += " [debug build]"; -#elif BUILD_ID != BUILD_RELEASE - title += " [pre-release build]"; -#endif // DEBUG - -#ifdef COMPILE_DATE - title += " (built " COMPILE_DATE ")"; -#endif // COMPILE_DATE - - setWindowTitle (title); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int ForgeWindow::deleteSelection() -{ - if (selection().isEmpty()) - return 0; - - LDObjectList selCopy = selection(); - - // Delete the objects that were being selected - for (LDObject* obj : selCopy) - obj->deleteSelf(); - - refresh(); - return selCopy.size(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::buildObjList() -{ - if (!getCurrentDocument()) - return; - - // Lock the selection while we do this so that refreshing the object list - // doesn't trigger selection updating so that the selection doesn't get lost - // while this is done. - g_isSelectionLocked = true; - - for (int i = 0; i < ui->objectList->count(); ++i) - delete ui->objectList->item (i); - - ui->objectList->clear(); - - for (LDObject* obj : getCurrentDocument()->getObjects()) - { - QString descr; - - switch (obj->getType()) - { - case LDObject::EComment: - { - descr = static_cast (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->getVertex (i).toString (true); - } - } break; - - case LDObject::EError: - { - descr = fmt ("ERROR: %1", obj->raw()); - } break; - - case LDObject::EVertex: - { - descr = static_cast (obj)->pos.toString (true); - } break; - - case LDObject::ESubfile: - { - LDSubfile* ref = static_cast (obj); - - descr = fmt ("%1 %2, (", ref->getFileInfo()->getDisplayName(), ref->getPosition().toString (true)); - - for (int i = 0; i < 9; ++i) - descr += fmt ("%1%2", ref->getTransform()[i], (i != 8) ? " " : ""); - - descr += ')'; - } break; - - case LDObject::EBFC: - { - descr = LDBFC::statements[static_cast (obj)->type]; - } break; - - case LDObject::EOverlay: - { - LDOverlay* ovl = static_cast (obj); - descr = fmt ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->getCamera()], - basename (ovl->getFileName()), ovl->getX(), ovl->getY(), - ovl->getWidth(), ovl->getHeight()); - } - break; - - default: - { - descr = obj->getTypeName(); - } break; - } - - QListWidgetItem* item = new QListWidgetItem (descr); - item->setIcon (getIcon (obj->getTypeName())); - - // Use italic font if hidden - if (obj->isHidden()) - { - QFont font = item->font(); - font.setItalic (true); - item->setFont (font); - } - - // Color gibberish orange on red so it stands out. - if (obj->getType() == LDObject::EError) - { - item->setBackground (QColor ("#AA0000")); - item->setForeground (QColor ("#FFAA00")); - } - elif (lv_colorize && obj->isColored() && obj->getColor() != maincolor && obj->getColor() != edgecolor) - { - // If the object isn't in the main or edge color, draw this - // list entry in said color. - LDColor* col = getColor (obj->getColor()); - - if (col) - item->setForeground (col->faceColor); - } - - obj->qObjListEntry = item; - ui->objectList->insertItem (ui->objectList->count(), item); - } - - g_isSelectionLocked = false; - updateSelection(); - scrollToSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::scrollToSelection() -{ - if (selection().isEmpty()) - return; - - LDObject* obj = selection().last(); - ui->objectList->scrollToItem (obj->qObjListEntry); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_selectionChanged() -{ - if (g_isSelectionLocked == true || getCurrentDocument() == null) - return; - - // Update the shared selection array, though don't do this if this was - // called during GL picking, in which case the GL renderer takes care - // of the selection. - if (m_renderer->isPicking()) - return; - - LDObjectList priorSelection = selection(); - - // Get the objects from the object list selection - getCurrentDocument()->clearSelection(); - const QList items = ui->objectList->selectedItems(); - - for (LDObject* obj : getCurrentDocument()->getObjects()) - { - 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 ForgeWindow::slot_recentFile() -{ - QAction* qAct = static_cast (sender()); - openMainFile (qAct->text()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_quickColor() -{ - QToolButton* button = static_cast (sender()); - LDColor* col = null; - - for (const LDQuickColor& entry : m_quickColors) - { - if (entry.getToolButton() == button) - { - col = entry.getColor(); - break; - } - } - - if (col == null) - return; - - int newColor = col->index; - - for (LDObject* obj : selection()) - { - if (obj->isColored() == false) - continue; // uncolored object - - obj->setColor (newColor); - R()->compileObject (obj); - } - - endAction(); - refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int ForgeWindow::getInsertionPoint() -{ - // If we have a selection, put the item after it. - if (!selection().isEmpty()) - return selection().last()->getIndex() + 1; - - // Otherwise place the object at the end. - return getCurrentDocument()->getObjectCount(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::doFullRefresh() -{ - buildObjList(); - m_renderer->hardRefresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::refresh() -{ - buildObjList(); - m_renderer->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateSelection() -{ - g_isSelectionLocked = true; - - for (LDObject* obj : getCurrentDocument()->getObjects()) - obj->setSelected (false); - - ui->objectList->clearSelection(); - - for (LDObject* obj : selection()) - { - if (obj->qObjListEntry == null) - continue; - - obj->qObjListEntry->setSelected (true); - obj->setSelected (true); - } - - g_isSelectionLocked = false; - slot_selectionChanged(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int ForgeWindow::getSelectedColor() -{ - int result = -1; - - for (LDObject* obj : selection()) - { - if (obj->isColored() == false) - continue; // doesn't use color - - if (result != -1 && obj->getColor() != result) - return -1; // No consensus in object color - - if (result == -1) - result = obj->getColor(); - } - - return result; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject::Type ForgeWindow::getUniformSelectedType() -{ - LDObject::Type result = LDObject::EUnidentified; - - for (LDObject* obj : selection()) - { - if (result != LDObject::EUnidentified && obj->getColor() != result) - return LDObject::EUnidentified; - - if (result == LDObject::EUnidentified) - result = obj->getType(); - } - - return result; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::closeEvent (QCloseEvent* ev) -{ - // Check whether it's safe to close all files. - if (!safeToCloseAll()) - { - ev->ignore(); - return; - } - - // Save the configuration before leaving so that, for instance, grid choice - // is preserved across instances. - Config::save(); - - ev->accept(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::spawnContextMenu (const QPoint pos) -{ - const bool single = (selection().size() == 1); - LDObject* singleObj = (single) ? selection()[0] : null; - - QMenu* contextMenu = new QMenu; - - if (single && singleObj->getType() != LDObject::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); -} - -// ============================================================================= -// TODO: what the heh? -// ----------------------------------------------------------------------------- -void ForgeWindow::deleteObjects (LDObjectList objs) -{ - for (LDObject* obj : objs) - obj->deleteSelf(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::deleteByColor (const int colnum) -{ - LDObjectList objs; - - for (LDObject* obj : getCurrentDocument()->getObjects()) - { - if (!obj->isColored() || obj->getColor() != colnum) - continue; - - objs << obj; - } - - deleteObjects (objs); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateEditModeActions() -{ - const EditMode mode = R()->getEditMode(); - ui->actionModeSelect->setChecked (mode == ESelectMode); - ui->actionModeDraw->setChecked (mode == EDrawMode); - ui->actionModeCircle->setChecked (mode == ECircleMode); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_editObject (QListWidgetItem* listitem) -{ - LDObject* obj = null; - - for (LDObject* it : getCurrentDocument()->getObjects()) - { - if (it->qObjListEntry == listitem) - { - obj = it; - break; - } - } - - AddObjectDialog::staticDialog (obj->getType(), obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool ForgeWindow::save (LDDocument* f, bool saveAs) -{ - QString path = f->getFullPath(); - - if (saveAs || path.isEmpty()) - { - QString name = f->getDefaultName(); - - if (!f->getFullPath().isEmpty()) - name = f->getFullPath(); - elif (!f->getName().isEmpty()) - name = f->getName(); - - 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 (f->save (path)) - { - if (f == getCurrentDocument()) - updateTitle(); - - log ("Saved to %1.", path); - - // Add it to recent files - addRecentFile (path); - return true; - } - - QString message = fmt (tr ("Failed to save to %1: %2"), path, strerror (errno)); - - // Tell the user the save failed, and give the option for saving as with it. - QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win); - - // Add a save-as button - QPushButton* saveAsBtn = new QPushButton (tr ("Save As")); - saveAsBtn->setIcon (getIcon ("file-save-as")); - dlg.addButton (saveAsBtn, QMessageBox::ActionRole); - dlg.setDefaultButton (QMessageBox::Close); - dlg.exec(); - - if (dlg.clickedButton() == saveAsBtn) - return save (f, true); // yay recursion! - - return false; -} - -void ForgeWindow::addMessage (QString msg) -{ - m_msglog->addLine (msg); -} - -// ============================================================================ -void ObjectList::contextMenuEvent (QContextMenuEvent* ev) -{ - g_win->spawnContextMenu (ev->globalPos()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QPixmap getIcon (QString iconName) -{ - return (QPixmap (fmt (":/icons/%1.png", iconName))); -} - -// ============================================================================= -bool confirm (QString msg) -{ - return confirm (ForgeWindow::tr ("Confirm"), msg); -} - -bool confirm (QString title, QString msg) -{ - return QMessageBox::question (g_win, title, msg, - (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes; -} - -// ============================================================================= -void critical (QString msg) -{ - QMessageBox::critical (g_win, ForgeWindow::tr ("Error"), msg, - (QMessageBox::Close), QMessageBox::Close); -} - -// ============================================================================= -QIcon makeColorIcon (LDColor* colinfo, const int size) -{ - // Create an image object and link a painter to it. - QImage img (size, size, QImage::Format_ARGB32); - QPainter paint (&img); - QColor col = colinfo->faceColor; - - if (colinfo->index == maincolor) - { - // Use the user preferences for main color here - col = gl_maincolor; - 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 counts; - - for (LDObject* obj : getCurrentDocument()->getObjects()) - { - if (!obj->isColored()) - continue; - - if (counts.find (obj->getColor()) == counts.end()) - counts[obj->getColor()] = 1; - else - counts[obj->getColor()]++; - } - - box->clear(); - int row = 0; - - for (const auto& pair : counts) - { - LDColor* col = getColor (pair.first); - assert (col != null); - - QIcon ico = makeColorIcon (col, 16); - box->addItem (ico, fmt ("[%1] %2 (%3 object%4)", - pair.first, col->name, pair.second, plural (pair.second))); - box->setItemData (row, pair.first); - - ++row; - } -} - -void ForgeWindow::updateDocumentList() -{ - ui->fileList->clear(); - - for (LDDocument* f : g_loadedFiles) - { - // Don't list implicit files unless explicitly desired. - if (f->isImplicit() && !gui_implicitfiles) - continue; - - // Add an item to the list for this file and store a pointer to it in - // the file, so we can find files by the list item. - ui->fileList->addItem (""); - QListWidgetItem* item = ui->fileList->item (ui->fileList->count() - 1); - f->setListItem (item); - updateDocumentListItem (f); - } -} - -void ForgeWindow::updateDocumentListItem (LDDocument* f) -{ - if (f->getListItem() == null) - { - // We don't have a list item for this file, so the list either doesn't - // exist yet or is out of date. Build the list now. - updateDocumentList(); - return; - } - - // If this is the current file, it also needs to be the selected item on - // the list. - if (f == getCurrentDocument()) - ui->fileList->setCurrentItem (f->getListItem()); - - // If we list implicit files, draw them with a shade of gray to make them - // distinct. - if (f->isImplicit()) - f->getListItem()->setForeground (QColor (96, 96, 96)); - - f->getListItem()->setText (f->getDisplayName()); - - // If the document has unsaved changes, draw a little icon next to it to mark that. - f->getListItem()->setIcon (f->hasUnsavedChanges() ? getIcon ("file-save") : QIcon()); -} - -// ============================================================================= -// A file is selected from the list of files on the left of the screen. Find out -// which file was picked and change to it. -void ForgeWindow::changeCurrentFile() -{ - LDDocument* f = null; - QListWidgetItem* item = ui->fileList->currentItem(); - - // Find the file pointer of the item that was selected. - for (LDDocument* it : g_loadedFiles) - { - if (it->getListItem() == item) - { - f = it; - break; - } - } - - // If we picked the same file we're currently on, we don't need to do - // anything. - if (!f || f == getCurrentDocument()) - return; - - LDDocument::setCurrent (f); -} - -void ForgeWindow::refreshObjectList() -{ -#if 0 - ui->objectList->clear(); - LDDocument* f = getCurrentDocument(); - -for (LDObject* obj : *f) - ui->objectList->addItem (obj->qObjListEntry); - -#endif - - buildObjList(); -} - -void ForgeWindow::updateActions() -{ - History* his = getCurrentDocument()->getHistory(); - int pos = his->getPosition(); - ui->actionUndo->setEnabled (pos != -1); - ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1); - ui->actionAxes->setChecked (gl_axes); - ui->actionBFCView->setChecked (gl_colorbfc); - 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 getColor() == null; -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/gui.h --- a/src/gui.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,302 +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 . - */ - -#ifndef LDFORGE_GUI_H -#define LDFORGE_GUI_H - -#include -#include -#include -#include -#include "config.h" -#include "ldtypes.h" -#include "ui_ldforge.h" - -class MessageManager; -class ForgeWindow; -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 ForgeWindow::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, NO_OPS, STOCK_WRITE) - PROPERTY (public, QToolButton*, ToolButton, NO_OPS, STOCK_WRITE) - - public: - LDQuickColor (LDColor* color, QToolButton* toolButton); - bool isSeparator() const; - - static LDQuickColor getSeparator(); -}; - -// ============================================================================= -// ObjectList -// -// Object list class for ForgeWindow -// ============================================================================= -class ObjectList : public QListWidget -{ - Q_OBJECT - - protected: - void contextMenuEvent (QContextMenuEvent* ev); -}; - -// ============================================================================= -// ForgeWindow -// -// The one main GUI class. Hosts the renderer, object list, message log. Contains -// slot_action, which is what all actions connect to. Manages menus and toolbars. -// Large and in charge. -// ============================================================================= -class ForgeWindow : public QMainWindow -{ - Q_OBJECT - - public: - ForgeWindow(); - void buildObjList(); - void updateTitle(); - void doFullRefresh(); - void refresh(); - int getInsertionPoint(); - void updateToolBars(); - void updateRecentFilesMenu(); - void updateSelection(); - void updateGridToolBar(); - void updateEditModeActions(); - void updateDocumentList(); - void updateDocumentListItem (LDDocument* f); - int getSelectedColor(); - LDObject::Type getUniformSelectedType(); - void scrollToSelection(); - void spawnContextMenu (const QPoint pos); - void deleteObjects (LDObjectList objs); - int deleteSelection(); - void deleteByColor (const int colnum); - bool save (LDDocument* f, bool saveAs); - void updateActions(); - - inline GLRenderer* R() - { - return m_renderer; - } - - inline void setQuickColors (QList& colors) - { - m_quickColors = colors; - } - - void addMessage (QString msg); - void refreshObjectList(); - void updateActionShortcuts(); - KeySequenceConfig* shortcutForAction (QAction* act); - 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 m_quickColors; - QList m_colorButtons; - QList m_recentFiles; - MessageManager* m_msglog; - Ui_LDForgeUI* ui; - - private slots: - void slot_selectionChanged(); - void slot_recentFile(); - void slot_quickColor(); - void slot_lastSecondCleanup(); - void slot_editObject (QListWidgetItem* listitem); -}; - -// ----------------------------------------------------------------------------- -// Pointer to the instance of ForgeWindow. -extern ForgeWindow* g_win; - -// ----------------------------------------------------------------------------- -// Other GUI-related stuff not directly part of ForgeWindow: -QPixmap getIcon (QString iconName); // Get an icon from the resource dir -QList quickColorsFromConfig(); // Make a list of quick colors based on config -bool confirm (QString title, QString msg); // Generic confirm prompt -bool confirm (QString msg); // Generic confirm prompt -void critical (QString msg); // Generic error prompt -QIcon makeColorIcon (LDColor* colinfo, const int size); // Makes an icon for the given color -void makeColorComboBox (QComboBox* box); // Fills the given combo-box with color information -QImage imageFromScreencap (uchar* data, int w, int h); - -// ============================================================================= -// ----------------------------------------------------------------------------- -// Takes in pairs of radio buttons and respective values and returns the value of -// the first found radio button that was checked. -// ============================================================================= -template -T radioSwitch (const T& defval, QList> haystack) -{ - for (pair 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 to have the given value. -// ============================================================================= -template -void radioDefault (const T& expr, QList> haystack) -{ - for (pair i : haystack) - { - if (i.second == expr) - { - i.first->setChecked (true); - return; - } - } -} - -#endif // LDFORGE_GUI_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/gui_actions.cc --- a/src/gui_actions.cc Mon Jan 20 23:44:22 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 . - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "gui.h" -#include "document.h" -#include "history.h" -#include "configDialog.h" -#include "addObjectDialog.h" -#include "misc.h" -#include "gldraw.h" -#include "dialogs.h" -#include "primitives.h" -#include "ui_newpart.h" -#include "widgets.h" -#include "colors.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 (fmt (" [%1]", ld_defaultuser)); - - ui.le_author->setText (authortext); - - switch (ld_defaultlicense) - { - case 0: - ui.rb_license_ca->setChecked (true); - break; - - case 1: - ui.rb_license_nonca->setChecked (true); - break; - - case 2: - ui.rb_license_none->setChecked (true); - break; - - default: - QMessageBox::warning (null, "Warning", - fmt ("Unknown ld_defaultlicense value %1!", ld_defaultlicense)); - break; - } - - if (dlg->exec() == false) - return; - - newFile(); - - const LDBFC::Type BFCType = - ui.rb_bfc_ccw->isChecked() ? LDBFC::CertifyCCW : - ui.rb_bfc_cw->isChecked() ? LDBFC::CertifyCW : LDBFC::NoCertify; - - const QString license = - ui.rb_license_ca->isChecked() ? CALicense : - ui.rb_license_nonca->isChecked() ? NonCALicense : ""; - - getCurrentDocument()->addObjects ( - { - new LDComment (ui.le_title->text()), - new LDComment ("Name: .dat"), - new LDComment (fmt ("Author: %1", ui.le_author->text())), - new LDComment (fmt ("!LDRAW_ORG Unofficial_Part")), - (license != "" ? new LDComment (license) : null), - new LDEmpty, - new LDBFC (BFCType), - new LDEmpty, - }); - - 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->getType(), obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Help, KEY (F1)) -{ -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (About, 0) -{ - AboutDialog().exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (AboutQt, 0) -{ - QMessageBox::aboutQt (g_win); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SelectAll, CTRL (A)) -{ - for (LDObject* obj : getCurrentDocument()->getObjects()) - obj->select(); - - updateSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SelectByColor, CTRL_SHIFT (A)) -{ - int colnum = getSelectedColor(); - - if (colnum == -1) - return; // no consensus on color - - getCurrentDocument()->clearSelection(); - - for (LDObject* obj : getCurrentDocument()->getObjects()) - if (obj->getColor() == 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 (selection()[0])->getFileInfo()->getName(); - - for (LDObject* obj : selection()) - if (static_cast (obj)->getFileInfo()->getName() != refName) - return; - } - - getCurrentDocument()->clearSelection(); - - for (LDObject* obj : getCurrentDocument()->getObjects()) - { - if (obj->getType() != type) - continue; - - if (type == LDObject::ESubfile && static_cast (obj)->getFileInfo()->getName() != 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 (fmt ("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 (fmt ("Unable to open %1 for writing (%2)", fname, file.errorString())); - return; - } - - for (LDObject* obj : selection()) - { - QString contents = obj->raw(); - 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()->getName()); - - if (root.right (4) == ".dat") - root.chop (4); - - QString defaultname = (root.length() > 0) ? fmt ("%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 (fmt ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno))); - - delete[] imgdata; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -extern_cfg (Bool, gl_axes); -DEFINE_ACTION (Axes, 0) -{ - gl_axes = !gl_axes; - updateActions(); - R()->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (VisibilityToggle, 0) -{ - for (LDObject* obj : selection()) - obj->toggleHidden(); - - 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", - fmt ("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]->getIndex(); - - int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval, - 1, getCurrentDocument()->getObjectCount(), 1, &ok); - - if (!ok || (obj = getCurrentDocument()->getObject (idx - 1)) == null) - return; - - getCurrentDocument()->clearSelection(); - obj->select(); - updateSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SubfileSelection, 0) -{ - if (selection().size() == 0) - return; - - QString parentpath = getCurrentDocument()->getFullPath(); - - // BFC type of the new subfile - it shall inherit the BFC type of the parent document - LDBFC::Type 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 (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]->getIndex(); - - // 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()->getFullPath())); - - if (topdirname != "s") - { - QString desiredPath = subdirname + "/s"; - QString title = tr ("Create subfile directory?"); - QString text = fmt (tr ("The directory %1 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()->getObjects()) - { - LDBFC* bfc = dynamic_cast (obj); - - if (!bfc) - continue; - - LDBFC::Type a = bfc->type; - - 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->raw(); - - // 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 (fmt ("Author: %1 [%2]", ld_defaultname, ld_defaultuser)), - new LDComment (fmt ("!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->deleteSelf(); - - // Compile all objects in the new subfile - for (LDObject* obj : doc->getObjects()) - 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 diff -r 6b13e4c2e97b -r b75c6cce02e2 src/gui_editactions.cc --- a/src/gui_editactions.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,838 +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 . - */ - -#include -#include -#include -#include - -#include "gui.h" -#include "main.h" -#include "document.h" -#include "colorSelectDialog.h" -#include "misc.h" -#include "widgets.h" -#include "gldraw.h" -#include "dialogs.h" -#include "colors.h" -#include "ui_replcoords.h" -#include "ui_editraw.h" -#include "ui_flip.h" -#include "ui_addhistoryline.h" - -cfg (Bool, edit_schemanticinline, false); -extern_cfg (String, ld_defaultuser); - -// ============================================================================= -// ----------------------------------------------------------------------------- -static int copyToClipboard() -{ - 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->raw(); - ++num; - } - - qApp->clipboard()->setText (data); - return num; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Cut, CTRL (X)) -{ - int num = copyToClipboard(); - deleteSelection(); - log (tr ("%1 objects cut"), num); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Copy, CTRL (C)) -{ - int num = copyToClipboard(); - log (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; - } - - log (tr ("%1 objects pasted"), num); - refresh(); - scrollToSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Delete, KEY (Delete)) -{ - int num = deleteSelection(); - log (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->getIndex(); - - if (idx == -1) - continue; - - LDObjectList objs; - - if (obj->getType() == LDObject::ESubfile) - objs = static_cast (obj)->inlineContents ( - (LDSubfile::InlineFlags) - ( (deep) ? LDSubfile::DeepInline : 0) | - LDSubfile::CacheInline - ); - else - continue; - - // Merge in the inlined objects - for (LDObject * inlineobj : objs) - { - QString line = inlineobj->raw(); - inlineobj->deleteSelf(); - 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->deleteSelf(); - } - - 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->getType() != LDObject::EQuad) - continue; - - // Find the index of this quad - long index = obj->getIndex(); - - if (index == -1) - return; - - QList triangles = static_cast (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->deleteSelf(); - - num++; - } - - log ("%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->raw()); - - if (obj->getType() == LDObject::EError) - ui.errorDescription->setText (static_cast (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->getType(); - if (type != LDObject::EQuad && type != LDObject::ETriangle) - continue; - - int numLines; - LDLine* lines[4]; - - if (type == LDObject::EQuad) - { - numLines = 4; - - LDQuad* quad = static_cast (obj); - lines[0] = new LDLine (quad->getVertex (0), quad->getVertex (1)); - lines[1] = new LDLine (quad->getVertex (1), quad->getVertex (2)); - lines[2] = new LDLine (quad->getVertex (2), quad->getVertex (3)); - lines[3] = new LDLine (quad->getVertex (3), quad->getVertex (0)); - } - else - { - numLines = 3; - - LDTriangle* tri = static_cast (obj); - lines[0] = new LDLine (tri->getVertex (0), tri->getVertex (1)); - lines[1] = new LDLine (tri->getVertex (1), tri->getVertex (2)); - lines[2] = new LDLine (tri->getVertex (2), tri->getVertex (0)); - } - - for (int i = 0; i < numLines; ++i) - { - long idx = obj->getIndex() + i + 1; - - lines[i]->setColor (edgecolor); - getCurrentDocument()->insertObj (idx, lines[i]); - R()->compileObject (lines[i]); - } - - num += numLines; - } - - log (tr ("Added %1 border lines"), num); - refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (CornerVerts, 0) -{ - int num = 0; - - for (LDObject* obj : selection()) - { - if (obj->vertices() < 2) - continue; - - int idx = obj->getIndex(); - - for (int i = 0; i < obj->vertices(); ++i) - { - LDVertex* vert = new LDVertex; - vert->pos = obj->getVertex (i); - vert->setColor (obj->getColor()); - - getCurrentDocument()->insertObj (++idx, vert); - R()->compileObject (vert); - ++num; - } - } - - log (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 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->getVertex (i); - rotateVertex (v, rotpoint, transform); - obj->setVertex (i, v); - } - } - elif (obj->hasMatrix()) - { - LDMatrixObject* mo = dynamic_cast (obj); - - // Transform the position - Vertex v = mo->getPosition(); - rotateVertex (v, rotpoint, transform); - mo->setPosition (v); - - // Transform the matrix - mo->setTransform (transform * mo->getTransform()); - } - elif (obj->getType() == LDObject::EVertex) - { - LDVertex* vert = static_cast (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 (obj); - - if (mo != null) - { - Vertex v = mo->getPosition(); - Matrix t = mo->getTransform(); - - for_axes (ax) - roundToDecimals (v[ax], 3); - - // Let matrix values be rounded to 4 decimals, - // they need that extra precision - for (int i = 0; i < 9; ++i) - roundToDecimals (t[i], 4); - - mo->setPosition (v); - mo->setTransform (t); - num += 10; - } - else - { - for (int i = 0; i < obj->vertices(); ++i) - { - Vertex v = obj->getVertex (i); - - for_axes (ax) - roundToDecimals (v[ax], 3); - - obj->setVertex (i, v); - R()->compileObject (obj); - num += 3; - } - } - } - - log (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->getType() == LDObject::ELine || obj->getType() == LDObject::ECondLine) - col = edgecolor; - - obj->setColor (col); - R()->compileObject (obj); - num++; - } - - log (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 sel; - int num = 0; - - if (ui.x->isChecked()) sel << X; - if (ui.y->isChecked()) sel << Y; - if (ui.z->isChecked()) sel << Z; - - for (LDObject* obj : selection()) - { - for (int i = 0; i < obj->vertices(); ++i) - { - Vertex v = obj->getVertex (i); - - for (Axis ax : sel) - { - double& coord = v[ax]; - - if (any || coord == search) - { - if (!rel) - coord = 0; - - coord += replacement; - num++; - } - } - - obj->setVertex (i, v); - R()->compileObject (obj); - } - } - - log (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 sel; - - if (ui.x->isChecked()) sel << X; - if (ui.y->isChecked()) sel << Y; - if (ui.z->isChecked()) sel << Z; - - for (LDObject* obj : selection()) - { - for (int i = 0; i < obj->vertices(); ++i) - { - Vertex v = obj->getVertex (i); - - for (Axis ax : sel) - v[ax] *= -1; - - obj->setVertex (i, v); - R()->compileObject (obj); - } - } - - refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Demote, 0) -{ - LDObjectList sel = selection(); - int num = 0; - - for (LDObject* obj : sel) - { - if (obj->getType() != LDObject::ECondLine) - continue; - - LDLine* repl = static_cast (obj)->demote(); - R()->compileObject (repl); - ++num; - } - - log (tr ("Demoted %1 conditional lines"), num); - refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static bool isColorUsed (int colnum) -{ - for (LDObject* obj : getCurrentDocument()->getObjects()) - if (obj->isColored() && obj->getColor() == colnum) - return true; - - return false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Autocolor, 0) -{ - int colnum = 0; - - while (colnum < MAX_COLORS && (getColor (colnum) == null || isColorUsed (colnum))) - colnum++; - - if (colnum >= MAX_COLORS) - { - log (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); - } - - log (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 = fmt ("!HISTORY %1 [%2] %3", - ui->m_date->date().toString ("yyyy-MM-dd"), - ui->m_username->text(), - ui->m_comment->text()); - - LDComment* comm = new LDComment (commentText); - - // Find a spot to place the new comment - for ( - obj = getCurrentDocument()->getObject (0); - obj && obj->next() && !obj->next()->isScemantic(); - obj = obj->next() - ) - { - LDComment* comm = dynamic_cast (obj); - - if (comm && comm->text.startsWith ("!HISTORY ")) - ishistory = true; - - if (prevIsHistory && !ishistory) - { - // Last line was history, this isn't, thus insert the new history - // line here. - break; - } - - prevIsHistory = ishistory; - } - - int idx = obj ? obj->getIndex() : 0; - getCurrentDocument()->insertObj (idx++, comm); - - // If we're adding a history line right before a scemantic object, pad it - // an empty line - if (obj && obj->next() && obj->next()->isScemantic()) - getCurrentDocument()->insertObj (idx, new LDEmpty); - - buildObjList(); - delete ui; -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/history.cc --- a/src/history.cc Mon Jan 20 23:44:22 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 . - */ - -#include "history.h" -#include "ldtypes.h" -#include "document.h" -#include "misc.h" -#include "gui.h" -#include "gldraw.h" - -// ============================================================================= -// -History::History() : - m_Position (-1) {} - -// ============================================================================= -// -void History::undo() -{ - if (m_changesets.isEmpty() || getPosition() == -1) - return; - - // Don't take the changes done here as actual edits to the document - setIgnoring (true); - - const Changeset& set = getChangeset (getPosition()); - - // Iterate the list in reverse and undo all actions - for (int i = set.size() - 1; i >= 0; --i) - { - AbstractHistoryEntry* change = set[i]; - change->undo(); - } - - decreasePosition(); - g_win->refresh(); - g_win->updateActions(); - dlog ("Position is now %1", getPosition()); - setIgnoring (false); -} - -// ============================================================================= -// -void History::redo() -{ - if (getPosition() == m_changesets.size()) - return; - - setIgnoring (true); - const Changeset& set = getChangeset (getPosition() + 1); - - // Redo things - in the order as they were done in the first place - for (const AbstractHistoryEntry* change : set) - change->redo(); - - setPosition (getPosition() + 1); - g_win->refresh(); - g_win->updateActions(); - dlog ("Position is now %1", getPosition()); - setIgnoring (false); -} - -// ============================================================================= -// -void History::clear() -{ - for (Changeset set : m_changesets) - for (AbstractHistoryEntry* change : set) - delete change; - - m_changesets.clear(); - dlog ("History: cleared"); -} - -// ============================================================================= -// -void History::addStep() -{ - if (m_currentChangeset.isEmpty()) - return; - - while (getPosition() < getSize() - 1) - { - Changeset last = m_changesets.last(); - - for (AbstractHistoryEntry* entry : last) - delete entry; - - m_changesets.removeLast(); - } - - dlog ("History: step added (%1 changes)", m_currentChangeset.size()); - m_changesets << m_currentChangeset; - m_currentChangeset.clear(); - setPosition (getPosition() + 1); - g_win->updateActions(); -} - -// ============================================================================= -// -void History::add (AbstractHistoryEntry* entry) -{ - if (isIgnoring()) - { - delete entry; - return; - } - - entry->setParent (this); - m_currentChangeset << entry; - dlog ("History: added entry of type %1", entry->getTypeName()); -} - -// ============================================================================= -// -void AddHistory::undo() const -{ - LDObject* obj = getParent()->getDocument()->getObject (getIndex()); - obj->deleteSelf(); -} - -// ============================================================================= -// -void AddHistory::redo() const -{ - LDObject* obj = parseLine (getCode()); - getParent()->getDocument()->insertObj (getIndex(), obj); - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// -DelHistory::DelHistory (int idx, LDObject* obj) : - m_Index (idx), - m_Code (obj->raw()) {} - -// ============================================================================= -// heh -// -void DelHistory::undo() const -{ - LDObject* obj = parseLine (getCode()); - getParent()->getDocument()->insertObj (getIndex(), obj); - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// -void DelHistory::redo() const -{ - LDObject* obj = getParent()->getDocument()->getObject (getIndex()); - obj->deleteSelf(); -} - -// ============================================================================= -// -void EditHistory::undo() const -{ - LDObject* obj = getCurrentDocument()->getObject (getIndex()); - LDObject* newobj = parseLine (getOldCode()); - obj->replace (newobj); - g_win->R()->compileObject (newobj); -} - -// ============================================================================= -// -void EditHistory::redo() const -{ - LDObject* obj = getCurrentDocument()->getObject (getIndex()); - LDObject* newobj = parseLine (getNewCode()); - obj->replace (newobj); - g_win->R()->compileObject (newobj); -} - -// ============================================================================= -// -void SwapHistory::undo() const -{ - LDObject::fromID (a)->swap (LDObject::fromID (b)); -} - -// ============================================================================= -// -void SwapHistory::redo() const -{ - undo(); -} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/history.h --- a/src/history.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,184 +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 . - */ - -#ifndef LDFORGE_HISTORY_H -#define LDFORGE_HISTORY_H - -#include "main.h" -#include "ldtypes.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, NUM_OPS, STOCK_WRITE) - PROPERTY (public, LDDocument*, Document, NO_OPS, STOCK_WRITE) - PROPERTY (public, bool, Ignoring, BOOL_OPS, STOCK_WRITE) - - public: - typedef QList 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 m_changesets; -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class AbstractHistoryEntry -{ - PROPERTY (public, History*, Parent, NO_OPS, 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, NO_OPS, STOCK_WRITE) - PROPERTY (private, QString, Code, NO_OPS, STOCK_WRITE) - - public: - IMPLEMENT_HISTORY_TYPE (Del) - DelHistory (int idx, LDObject* obj); -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class EditHistory : public AbstractHistoryEntry -{ - PROPERTY (private, int, Index, NO_OPS, STOCK_WRITE) - PROPERTY (private, QString, OldCode, NO_OPS, STOCK_WRITE) - PROPERTY (private, QString, NewCode, NO_OPS, 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, NO_OPS, STOCK_WRITE) - PROPERTY (private, QString, Code, NO_OPS, STOCK_WRITE) - - public: - IMPLEMENT_HISTORY_TYPE (Add) - - AddHistory (int idx, LDObject* obj) : - m_Index (idx), - m_Code (obj->raw()) {} -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class MoveHistory : public AbstractHistoryEntry -{ - public: - IMPLEMENT_HISTORY_TYPE (Move) - - QList indices; - Vertex dest; - - MoveHistory (QList 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; -}; - -#endif // LDFORGE_HISTORY_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/ldconfig.cc --- a/src/ldconfig.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,214 +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 . - */ - -#include -#include "document.h" -#include "ldconfig.h" -#include "gui.h" -#include "misc.h" -#include "colors.h" - -// ============================================================================= -// Helper function for parseLDConfig -// ----------------------------------------------------------------------------- -static bool parseLDConfigTag (LDConfigParser& pars, char const* tag, 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); -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/ldconfig.h --- a/src/ldconfig.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +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 . - */ - -#ifndef LDFORGE_LDCONFIG_H -#define LDFORGE_LDCONFIG_H - -#include "types.h" -#include - -// ============================================================================= -// LDConfigParser -// -// 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(); - -#endif // LDFORGE_LDCONFIG_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/ldtypes.cc --- a/src/ldtypes.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,835 +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 . - */ - -#include "main.h" -#include "ldtypes.h" -#include "document.h" -#include "misc.h" -#include "gui.h" -#include "history.h" -#include "gldraw.h" -#include "colors.h" - -cfg (String, ld_defaultname, ""); -cfg (String, ld_defaultuser, ""); -cfg (Int, ld_defaultlicense, 0); - -// List of all LDObjects -static LDObjectList g_LDObjects; - -// ============================================================================= -// LDObject constructors -// ----------------------------------------------------------------------------- -LDObject::LDObject() : - m_Hidden (false), - m_Selected (false), - m_Parent (null), - m_File (null), - m_GLInit (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->getID() >= id) - id = obj->getID() + 1; - } - - setID (id); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::setVertexCoord (int i, Axis ax, double value) -{ - Vertex v = getVertex (i); - v[ax] = value; - setVertex (i, v); -} - -LDError::LDError() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString LDComment::raw() const -{ - return fmt ("0 %1", text); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString LDSubfile::raw() const -{ - QString val = fmt ("1 %1 %2 ", getColor(), getPosition()); - val += getTransform().toString(); - val += ' '; - val += getFileInfo()->getName(); - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString LDLine::raw() const -{ - QString val = fmt ("2 %1", getColor()); - - for (int i = 0; i < 2; ++i) - val += fmt (" %1", getVertex (i)); - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString LDTriangle::raw() const -{ - QString val = fmt ("3 %1", getColor()); - - for (int i = 0; i < 3; ++i) - val += fmt (" %1", getVertex (i)); - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString LDQuad::raw() const -{ - QString val = fmt ("4 %1", getColor()); - - for (int i = 0; i < 4; ++i) - val += fmt (" %1", getVertex (i)); - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString LDCondLine::raw() const -{ - QString val = fmt ("5 %1", getColor()); - - // Add the coordinates - for (int i = 0; i < 4; ++i) - val += fmt (" %1", getVertex (i)); - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString LDError::raw() const -{ - return contents; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString LDVertex::raw() const -{ - return fmt ("0 !LDFORGE VERTEX %1 %2", getColor(), pos); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString LDEmpty::raw() const -{ - return ""; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const char* LDBFC::statements[] = -{ - "CERTIFY CCW", - "CCW", - "CERTIFY CW", - "CW", - "NOCERTIFY", - "INVERTNEXT", - "CLIP", - "CLIP CCW", - "CLIP CW", - "NOCLIP", -}; - -QString LDBFC::raw() const -{ - return fmt ("0 BFC %1", LDBFC::statements[type]); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList LDQuad::splitToTriangles() -{ - // Create the two triangles based on this quadrilateral: - // 0---3 0---3 3 - // | | | / /| - // | | ==> | / / | - // | | |/ / | - // 1---2 1 1---2 - LDTriangle* tri1 = new LDTriangle (getVertex (0), getVertex (1), getVertex (3)); - LDTriangle* tri2 = new LDTriangle (getVertex (1), getVertex (2), getVertex (3)); - - // The triangles also inherit the quad's color - tri1->setColor (getColor()); - tri2->setColor (getColor()); - - QList triangles; - triangles << tri1; - triangles << tri2; - return triangles; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::replace (LDObject* other) -{ - long idx = getIndex(); - assert (idx != -1); - - // Replace the instance of the old object with the new object - getFile()->setObject (idx, other); - - // Remove the old object - deleteSelf(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::swap (LDObject* other) -{ - assert (getFile() == other->getFile()); - getFile()->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::deleteSelf() -{ - // If this object was selected, unselect it now - if (isSelected()) - unselect(); - - // If this object was associated to a file, remove it off it now - if (getFile()) - getFile()->forgetObject (this); - - // Delete the GL lists - GL::deleteLists (this); - - // Remove this object from the list of LDObjects - g_LDObjects.removeOne (this); - - delete this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void transformObject (LDObject* obj, Matrix transform, Vertex pos, int parentcolor) -{ - switch (obj->getType()) - { - case LDObject::ELine: - case LDObject::ECondLine: - case LDObject::ETriangle: - case LDObject::EQuad: - - for (int i = 0; i < obj->vertices(); ++i) - { - Vertex v = obj->getVertex (i); - v.transform (transform, pos); - obj->setVertex (i, v); - } - - break; - - case LDObject::ESubfile: - { - LDSubfile* ref = static_cast (obj); - Matrix newMatrix = transform * ref->getTransform(); - Vertex newpos = ref->getPosition(); - - newpos.transform (transform, pos); - ref->setPosition (newpos); - ref->setTransform (newMatrix); - } - break; - - default: - break; - } - - if (obj->getColor() == maincolor) - obj->setColor (parentcolor); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObjectList LDSubfile::inlineContents (InlineFlags flags) -{ - LDObjectList objs = getFileInfo()->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, getTransform(), getPosition(), getColor()); - } - - return objs; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -long LDObject::getIndex() const -{ - assert (getFile() != null); - - for (int i = 0; i < getFile()->getObjectCount(); ++i) - if (getFile()->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]->getFile(); - - for (long i = start; i != end; i += incr) - { - LDObject* obj = objs[i]; - - const long idx = obj->getIndex(), - target = idx + (up ? -1 : 1); - - if ( (up && idx == 0) || (!up && idx == (long) (file->getObjects().size() - 1))) - { - // One of the objects hit the extrema. If this happens, this should be the first - // object to be iterated on. Thus, nothing has changed yet and it's safe to just - // abort the entire operation. - assert (i == start); - return; - } - - objsToCompile << obj; - objsToCompile << file->getObject (target); - - obj->swap (file->getObject (target)); - } - - removeDuplicates (objsToCompile); - - // The objects need to be recompiled, otherwise their pick lists are left with - // the wrong index colors which messes up selection. - for (LDObject* obj : objsToCompile) - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString LDObject::typeName (LDObject::Type type) -{ - LDObject* obj = LDObject::getDefault (type); - QString name = obj->getTypeName(); - obj->deleteSelf(); - 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->getType() == objType) - count++; - - if (count == 0) - continue; - - if (!firstDetails) - text += ", "; - - QString noun = fmt ("%1%2", typeName (objType), plural (count)); - - // Plural of "vertex" is "vertices", correct that - if (objType == EVertex && count != 1) - noun = "vertices"; - - text += fmt ("%1 %2", count, noun); - firstDetails = false; - } - - return text; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDObject::topLevelParent() -{ - if (!getParent()) - return this; - - LDObject* it = this; - - while (it->getParent()) - it = it->getParent(); - - return it; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDObject::next() const -{ - long idx = getIndex(); - assert (idx != -1); - - if (idx == (long) getFile()->getObjectCount() - 1) - return null; - - return getFile()->getObject (idx + 1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDObject::prev() const -{ - long idx = getIndex(); - assert (idx != -1); - - if (idx == 0) - return null; - - return getFile()->getObject (idx - 1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::move (Vertex vect) -{ - if (hasMatrix()) - { - LDMatrixObject* mo = dynamic_cast (this); - mo->setPosition (mo->getPosition() + vect); - } - elif (getType() == LDObject::EVertex) - { - // ugh - static_cast (this)->pos += vect; - } - else - { - for (int i = 0; i < vertices(); ++i) - setVertex (i, getVertex (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 = getVertex (1); - setVertex (1, getVertex (2)); - setVertex (2, tmp); - - return; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDQuad::invert() -{ - // Quad: 0 -> 1 -> 2 -> 3 - // rev: 0 -> 3 -> 2 -> 1 - // Thus, we swap 1 and 3. - Vertex tmp = getVertex (1); - setVertex (1, getVertex (3)); - setVertex (3, tmp); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDSubfile::invert() -{ - // Subfiles are inverted when they're prefixed with - // a BFC INVERTNEXT statement. Thus we need to toggle this status. - // For flat primitives it's sufficient that the determinant is - // flipped but I don't have a method for checking flatness yet. - // Food for thought... - - int idx = getIndex(); - - if (idx > 0) - { - LDBFC* bfc = dynamic_cast (prev()); - - if (bfc && bfc->type == LDBFC::InvertNext) - { - // This is prefixed with an invertnext, thus remove it. - bfc->deleteSelf(); - return; - } - } - - // Not inverted, thus prefix it with a new invertnext. - LDBFC* bfc = new LDBFC (LDBFC::InvertNext); - getFile()->insertObj (idx, bfc); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void invertLine (LDObject* line) -{ - // For lines, we swap the vertices. I don't think that a - // cond-line's control points need to be swapped, do they? - Vertex tmp = line->getVertex (0); - line->setVertex (0, line->getVertex (1)); - line->setVertex (1, tmp); -} - -void LDLine::invert() -{ - invertLine (this); -} - -void LDCondLine::invert() -{ - invertLine (this); -} - -void LDVertex::invert() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDLine* LDCondLine::demote() -{ - LDLine* repl = new LDLine; - - for (int i = 0; i < repl->vertices(); ++i) - repl->setVertex (i, getVertex (i)); - - repl->setColor (getColor()); - - replace (repl); - return repl; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDObject::fromID (int id) -{ - for (LDObject* obj : g_LDObjects) - if (obj->getID() == id) - return obj; - - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString LDOverlay::raw() const -{ - return fmt ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6", - getFileName(), getCamera(), getX(), getY(), getWidth(), getHeight()); -} - -void LDOverlay::invert() {} - -// ============================================================================= -// Hook the set accessors of certain properties to this changeProperty function. -// It takes care of history management so we can capture low-level changes, this -// makes history stuff work out of the box. -// ----------------------------------------------------------------------------- -template static void changeProperty (LDObject* obj, T* ptr, const T& val) -{ - long idx; - - if (*ptr == val) - return; - - if (obj->getFile() && (idx = obj->getIndex()) != -1) - { - QString before = obj->raw(); - *ptr = val; - QString after = obj->raw(); - - if (before != after) - obj->getFile()->addToHistory (new EditHistory (idx, before, after)); - } - else - *ptr = val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::setColor (const int& val) -{ - changeProperty (this, &m_Color, val); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const Vertex& LDObject::getVertex (int i) const -{ - return m_coords[i]->data(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::setVertex (int i, const Vertex& vert) -{ - changeProperty (this, &m_coords[i], LDSharedVertex::getSharedVertex (vert)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDMatrixObject::setPosition (const Vertex& a) -{ - changeProperty (getLinkPointer(), &m_Position, LDSharedVertex::getSharedVertex (a)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDMatrixObject::setTransform (const Matrix& val) -{ - changeProperty (getLinkPointer(), &m_Transform, val); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static QMap g_sharedVerts; - -LDSharedVertex* LDSharedVertex::getSharedVertex (const Vertex& a) -{ - auto it = g_sharedVerts.find (a); - - if (it == g_sharedVerts.end()) - { - LDSharedVertex* v = new LDSharedVertex (a); - g_sharedVerts[a] = v; - return v; - } - - return *it; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDSharedVertex::addRef (LDObject* a) -{ - m_refs << a; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDSharedVertex::delRef (LDObject* a) -{ - m_refs.removeOne (a); - - if (m_refs.empty()) - { - g_sharedVerts.remove (m_data); - delete this; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::select() -{ - if (!getFile()) - { - log ("Warning: Object #%1 cannot be selected as it is not assigned a file!\n", getID()); - return; - } - - getFile()->addToSelection (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::unselect() -{ - if (!getFile()) - { - log ("Warning: Object #%1 cannot be unselected as it is not assigned a file!\n", getID()); - return; - } - - getFile()->removeFromSelection (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString getLicenseText (int id) -{ - switch (id) - { - case 0: - return CALicense; - - case 1: - return 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 (getColor()); - - if (hasMatrix()) - { - LDMatrixObject* copyMo = static_cast (copy); - const LDMatrixObject* mo = static_cast (this); - copyMo->setPosition (mo->getPosition()); - copyMo->setTransform (mo->getTransform()); - } - else - { - for (int i = 0; i < vertices(); ++i) - copy->setVertex (getVertex (i)); - } - - switch (getType()) - { - case Subfile: - { - LDSubfile* copyRef = static_cast (copy); - const LDSubfile* ref = static_cast (this); - - copyRef->setFileInfo (ref->getFileInfo()); - } - } - */ - - LDObject* copy = parseLine (raw()); - return copy; -} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/ldtypes.h --- a/src/ldtypes.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,570 +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 . - */ - -#ifndef LDFORGE_LDTYPES_H -#define LDFORGE_LDTYPES_H - -#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 getType() const override \ - { \ - return LDObject::E##T; \ - } \ - virtual QString raw() const override; \ - virtual void invert() override; - -#define LDOBJ_NAME(N) virtual QString getTypeName() 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, Hidden, BOOL_OPS, STOCK_WRITE) - PROPERTY (public, bool, Selected, BOOL_OPS, STOCK_WRITE) - PROPERTY (public, LDObject*, Parent, NO_OPS, STOCK_WRITE) - PROPERTY (public, LDDocument*, File, NO_OPS, STOCK_WRITE) // TODO: rename~ - PROPERTY (private, int, ID, NUM_OPS, STOCK_WRITE) - PROPERTY (public, int, Color, NUM_OPS, CUSTOM_WRITE) - PROPERTY (public, bool, GLInit, BOOL_OPS, 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 deleteSelf(); - - // Index (i.e. line number) of this object - long getIndex() const; - - // Type enumerator of this object - virtual Type getType() const = 0; - - // Get a vertex by index - const Vertex& getVertex (int i) const; - - // Type name of this object - virtual QString getTypeName() 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* prev() const; - - // This object as LDraw code - virtual QString raw() 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; -}; - -// ============================================================================= -// LDMatrixObject -// ============================================================================= -// -// 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, NO_OPS, STOCK_WRITE) - PROPERTY (public, Matrix, Transform, NO_OPS, 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& getPosition() const - { - return m_Position->data(); - } - - void setCoordinate (const Axis ax, double value) - { - Vertex v = getPosition(); - v[ax] = value; - setPosition (v); - } - - void setPosition (const Vertex& a); - - private: - LDSharedVertex* m_Position; -}; - -// ============================================================================= -// LDError -// -// 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. The member -// zContent contains the contents of the unparsable line. -// ============================================================================= -class LDError : public LDObject -{ - LDOBJ (Error) - LDOBJ_NAME (error) - LDOBJ_VERTICES (0) - LDOBJ_UNCOLORED - LDOBJ_SCEMANTIC - LDOBJ_NO_MATRIX - PROPERTY (public, QString, FileReferenced, STR_OPS, STOCK_WRITE) - - public: - LDError(); - LDError (QString contents, QString reason) : contents (contents), reason (reason) {} - - // Content of this unknown line - QString contents; - - // Why is this gibberish? - QString reason; -}; - -// ============================================================================= -// LDEmpty -// -// 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 -}; - -// ============================================================================= -// LDComment -// -// Represents a code-0 comment in the LDraw code file. Member text contains -// the text of the comment. -// ============================================================================= -class LDComment : public LDObject -{ - LDOBJ (Comment) - LDOBJ_NAME (comment) - LDOBJ_VERTICES (0) - LDOBJ_UNCOLORED - LDOBJ_NON_SCEMANTIC - LDOBJ_NO_MATRIX - - public: - LDComment() {} - LDComment (QString text) : text (text) {} - - QString text; // The text of this comment -}; - -// ============================================================================= -// LDBFC -// -// Represents a 0 BFC statement in the LDraw code. eStatement contains the type -// of this statement. -// ============================================================================= -class LDBFC : public LDObject -{ - public: - enum Type - { - 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 (type == InvertNext); } - LDOBJ_NO_MATRIX - - public: - LDBFC() {} - LDBFC (const LDBFC::Type type) : - type (type) {} - - // Statement strings - static const char* statements[]; - - Type type; -}; - -// ============================================================================= -// 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, NO_OPS, 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 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, NUM_OPS, STOCK_WRITE) - PROPERTY (public, int, X, NUM_OPS, STOCK_WRITE) - PROPERTY (public, int, Y, NUM_OPS, STOCK_WRITE) - PROPERTY (public, int, Width, NUM_OPS, STOCK_WRITE) - PROPERTY (public, int, Height, NUM_OPS, STOCK_WRITE) - PROPERTY (public, QString, FileName, STR_OPS, STOCK_WRITE) -}; - -// Other common LDraw stuff -static const QString CALicense = "!LICENSE Redistributable under CCAL version 2.0 : see CAreadme.txt", - NonCALicense = "!LICENSE Not redistributable : see NonCAreadme.txt"; -static const int lores = 16; -static const int hires = 48; - -QString getLicenseText (int id); - -#endif // LDFORGE_LDTYPES_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/main.cc --- a/src/main.cc Mon Jan 20 23:44:22 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 . - */ - -#include -#include -#include -#include -#include -#include -#include "gui.h" -#include "document.h" -#include "misc.h" -#include "config.h" -#include "colors.h" -#include "types.h" -#include "primitives.h" -#include "gldraw.h" -#include "configDialog.h" -#include "dialogs.h" -#include "crashcatcher.h" - -QList g_loadedFiles; -ForgeWindow* 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()) - { - log ("Creating configuration file...\n"); - - if (Config::save()) - log ("Configuration file successfully created.\n"); - else - log ("failed to create configuration file!\n"); - } - - LDPaths::initPaths(); - initColors(); - ForgeWindow* win = new ForgeWindow; - newFile(); - win->show(); - - // If this is the first start, get the user to configuration. Especially point - // them to the profile tab, it's the most important form to fill in. - if (firststart) - { - (new ConfigDialog (ConfigDialog::ProfileTab))->exec(); - firststart = false; - Config::save(); - } - - loadPrimitives(); - return app.exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void doPrint (QFile& f, QList args) -{ - QString msg = DoFormat (args); - f.write (msg.toUtf8()); - f.flush(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void doPrint (FILE* fp, QList args) -{ - QString msg = DoFormat (args); - fwrite (msg.toStdString().c_str(), 1, msg.length(), fp); - fflush (fp); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString versionString() -{ - if (g_versionString.length() == 0) - { -#if VERSION_PATCH == 0 - g_versionString = fmt ("%1.%2", VERSION_MAJOR, VERSION_MINOR); -#else - g_versionString = fmt ("%1.%2.%3", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); -#endif // VERSION_PATCH - } - - return g_versionString; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString fullVersionString() -{ -#if BUILD_ID != BUILD_RELEASE && defined (GIT_DESCRIBE) - return GIT_DESCRIBE; -#else - return "v" + versionString(); -#endif -} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/main.h --- a/src/main.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +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 . - */ - -// ============================================================================= -// This file is included one way or another in every source file of LDForge. -// Stuff defined and included here is universally included. - -#ifndef LDFORGE_MAIN_H -#define LDFORGE_MAIN_H - -#include -#include -#include -#include -#include -#include - -#include "property.h" -#include "config.h" - -#define APPNAME "LDForge" -#define UNIXNAME "ldforge" -#define VERSION_MAJOR 0 -#define VERSION_MINOR 2 -#define VERSION_PATCH 999 -#define BUILD_ID BUILD_INTERNAL - -#define BUILD_INTERNAL 0 -#define BUILD_RELEASE 1 - -// ============================================= -#ifdef DEBUG -# undef RELEASE -#endif // DEBUG - -#ifdef RELEASE -# undef DEBUG -#endif // RELEASE - -// ============================================= -#define alias auto& -#define elif(A) else if (A) - -// Null pointer -static const std::nullptr_t null = nullptr; - -#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__ - -#ifdef IN_IDE_PARSER -void dlog(void, ...) {} -#else -# ifdef DEBUG -# define dlog(...) log (QString (__PRETTY_FUNCTION__) + ": " __VA_ARGS__) -# else -# define dlog(...) -# endif // DEBUG -#endif // IN_IDE_PARSER - -#define dvalof(A) dlog ("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. -void assertionFailure (const char* file, int line, const char* funcname, const char* expr); - -#undef assert - -#ifdef DEBUG -# define assert(N) { ((N) ? (void) 0 : assertionFailure (__FILE__, __LINE__, FUNCNAME, #N)); } -#else -# define assert(N) {} -#endif // DEBUG - -// Version string identifier -QString versionString(); -QString fullVersionString(); - -#define properties private -#define typedefs public -#define for_axes(AX) for (const Axis AX : std::initializer_list ({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 - -#endif // LDFORGE_MAIN_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/messagelog.cc --- a/src/messagelog.cc Mon Jan 20 23:44:22 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 . - */ - -#include -#include -#include "messagelog.h" -#include "gldraw.h" -#include "gui.h" -#include "moc_messagelog.cpp" - -static const int g_maxMessages = 5; -static const int g_expiry = 5; -static const int g_fadeTime = 500; // msecs - -// ============================================================================= -// ----------------------------------------------------------------------------- -MessageManager::MessageManager (QObject* parent) : - QObject (parent) -{ - m_ticker = new QTimer; - m_ticker->start (100); - connect (m_ticker, SIGNAL (timeout()), this, SLOT (tick())); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -MessageManager::Line::Line (QString text) : - text (text), - alpha (1.0f), - expiry (QDateTime::currentDateTime().addSecs (g_expiry)) {} - -// ============================================================================= -// Check this line's expiry and update alpha accordingly. Returns true if the -// line is to still stick around, false if it expired. 'changed' is updated to -// whether the line has somehow changed since the last update. -// ----------------------------------------------------------------------------- -bool MessageManager::Line::update (bool& changed) -{ - changed = false; - QDateTime now = QDateTime::currentDateTime(); - int msec = now.msecsTo (expiry); - - if (now >= expiry) - { - // Message line has expired - changed = true; - return false; - } - - if (msec <= g_fadeTime) - { - // Message line has not expired but is fading out - alpha = ( (float) msec) / g_fadeTime; - changed = true; - } - - return true; -} - -// ============================================================================= -// Add a line to the message manager. -// ----------------------------------------------------------------------------- -void MessageManager::addLine (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 (getRenderer()) - getRenderer()->update(); -} - -// ============================================================================= -// Ticks the message manager. All lines are ticked and the renderer scene is -// redrawn if something changed. -// ----------------------------------------------------------------------------- -void MessageManager::tick() -{ - if (m_lines.isEmpty()) - return; - - bool changed = false; - - for (int i = 0; i < m_lines.size(); ++i) - { - bool lineChanged; - - if (!m_lines[i].update (lineChanged)) - m_lines.removeAt (i--); - - changed |= lineChanged; - } - - if (changed && getRenderer()) - getRenderer()->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const QList& MessageManager::getLines() const -{ - return m_lines; -} - -// ============================================================================= -// log() interface - format the argument list and add the resulting string to -// the main message manager. -// ----------------------------------------------------------------------------- -void DoLog (std::initializer_list args) -{ - const QString msg = DoFormat (args); - - for (QString& a : msg.split ("\n", QString::SkipEmptyParts)) - { - if (g_win) - g_win->addMessage (a); - - // Also print it to stdout - fprint (stdout, "%1\n", a); - } -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/messagelog.h --- a/src/messagelog.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +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 . - */ - -#ifndef LDFORGE_MESSAGELOG_H -#define LDFORGE_MESSAGELOG_H - -#include -#include -#include "main.h" -#include "types.h" - -class GLRenderer; -class QTimer; - -/* 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 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, NO_OPS, STOCK_WRITE) - - public: - // Single line of the message log. - class Line - { - public: - Line (QString text); - bool update (bool& changed); - - QString text; - float alpha; - QDateTime expiry; - }; - - explicit MessageManager (QObject* parent = 0); - void addLine (QString line); - const QList& getLines() const; - - private: - QList m_lines; - QTimer* m_ticker; - - private slots: - void tick(); -}; - -#endif // LDFORGE_MESSAGELOG_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc.cc --- a/src/misc.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,301 +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 . - */ - -#include -#include -#include -#include "main.h" -#include "misc.h" -#include "gui.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 (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 (obj)->getPosition(); - 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 (initlist vals, QString delim) -{ - QStringList list; - - for (const StringFormatArg& arg : vals) - list << arg.value(); - - 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 diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc.h --- a/src/misc.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,142 +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 . - */ - -#ifndef LDFORGE_MISC_H -#define LDFORGE_MISC_H - -#include -#include "config.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 (initlist 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 static inline const char* plural (T n) -{ - return (n != 1) ? "s" : ""; -} - -// ----------------------------------------------------------------------------- -// Templated clamp -template static inline T clamp (T a, T min, T max) -{ - return (a > max) ? max : (a < min) ? min : a; -} - -// Templated minimum -template static inline T min (T a, T b) -{ - return (a < b) ? a : b; -} - -// Templated maximum -template static inline T max (T a, T b) -{ - return (a > b) ? a : b; -} - -// Templated absolute value -template static inline T abs (T a) -{ - return (a >= 0) ? a : -a; -} - -template inline bool isZero (T a) -{ - return abs (a) < 0.0001; -} - -template inline bool isInteger (T a) -{ - return isZero (a - (int) a); -} - -template void removeDuplicates (QList& a) -{ - std::sort (a.begin(), a.end()); - a.erase (std::unique (a.begin(), a.end()), a.end()); -} - -#endif // LDFORGE_MISC_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc/DocumentPointer.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/DocumentPointer.cc Tue Jan 21 02:03:27 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 . + */ + +#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.getPointer()) +{ + addReference (); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocumentPointer::~LDDocumentPointer() +{ + removeReference(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocumentPointer::addReference() +{ + if (getPointer() != null) + getPointer()->addReference (this); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDDocumentPointer::removeReference() +{ + if (getPointer() != null) + getPointer()->removeReference (this); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocumentPointer& LDDocumentPointer::operator= (LDDocument* ptr) +{ + if (ptr != getPointer()) + { + removeReference(); + setPointer (ptr); + addReference(); + } + + return *this; +} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc/DocumentPointer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/DocumentPointer.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,58 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_DOCUMENT_POINTER_H +#define LDFORGE_DOCUMENT_POINTER_H + +#include "../Main.h" + +class LDSubfile; +class LDDocument; + +class LDDocumentPointer +{ + PROPERTY (private, LDDocument*, Pointer, NO_OPS, STOCK_WRITE) + + public: + LDDocumentPointer(); + LDDocumentPointer (LDDocument* ptr); + LDDocumentPointer (const LDDocumentPointer& other); + ~LDDocumentPointer(); + LDDocumentPointer& operator= (LDDocument* ptr); + + inline LDDocumentPointer& operator= (LDDocumentPointer& other) + { + return operator= (other.getPointer()); + } + + inline LDDocument* operator->() const + { + return getPointer(); + } + + inline operator LDDocument*() const + { + return getPointer(); + } + + private: + void addReference(); + void removeReference(); +}; + +#endif // LDFORGE_DOCUMENT_POINTER_H \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc/InvokationDeferer.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/InvokationDeferer.cc Tue Jan 21 02:03:27 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 . + */ + +#include "moc_InvokationDeferer.cpp" +#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 diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc/InvokationDeferer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/InvokationDeferer.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,46 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_MISC_INVOKATION_DEFERER_H +#define LDFORGE_MISC_INVOKATION_DEFERER_H + +#include + +class InvokationDeferer : public QObject +{ + Q_OBJECT + + public: + using FunctionType = void(*)(); + + explicit InvokationDeferer (QObject* parent = 0); + void addFunctionCall (FunctionType func); + + signals: + void functionAdded(); + + private: + QList m_funcs; + + private slots: + void invokeFunctions(); +}; + +void invokeLater (InvokationDeferer::FunctionType func); + +#endif // LDFORGE_MISC_INVOKATION_DEFERER_H \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc/RingFinder.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/RingFinder.cc Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,178 @@ +/* + * 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 . + */ + +#include "RingFinder.h" +#include "../Misc.h" + +RingFinder g_RingFinder; + +// ============================================================================= +// This is the main algorithm of the ring finder. It tries to use math to find +// the one ring between r0 and r1. If it fails (the ring number is non-integral), +// it finds an intermediate radius (ceil of the ring number times scale) and +// splits the radius at this point, calling this function again to try find the +// rings between r0 - r and r - r1. +// +// This does not always yield into usable results. If at some point r == r0 or +// r == r1, there is no hope of finding the rings, at least with this algorithm, +// as it would fall into an infinite recursion. +// +bool RingFinder::findRingsRecursor (double r0, double r1, Solution& currentSolution) +{ + // Don't recurse too deep. + if (m_stack >= 5) + return false; + + // Find the scale and number of a ring between r1 and r0. + assert (r1 >= r0); + double scale = r1 - r0; + double num = r0 / scale; + + // If the ring number is integral, we have found a fitting ring to r0 -> r1! + if (isInteger (num)) + { + Component cmp; + cmp.scale = scale; + cmp.num = (int) round (num); + currentSolution.addComponent (cmp); + + // If we're still at the first recursion, this is the only + // ring and there's nothing left to do. Guess we found the winner. + if (m_stack == 0) + { + m_solutions.push_back (currentSolution); + return true; + } + } + else + { + // Try find solutions by splitting the ring in various positions. + if (isZero (r1 - r0)) + return false; + + double interval; + + // Determine interval. The smaller delta between radii, the more precise + // interval should be used. We can't really use a 0.5 increment when + // calculating rings to 10 -> 105... that would take ages to process! + if (r1 - r0 < 0.5) + interval = 0.1; + else if (r1 - r0 < 10) + interval = 0.5; + else if (r1 - r0 < 50) + interval = 1; + else + interval = 5; + + // Now go through possible splits and try find rings for both segments. + for (double r = r0 + interval; r < r1; r += interval) + { + Solution sol = currentSolution; + + m_stack++; + bool res = findRingsRecursor (r0, r, sol) && findRingsRecursor (r, r1, sol); + m_stack--; + + if (res) + { + // We succeeded in finding radii for this segment. If the stack is 0, this + // is the first recursion to this function. Thus there are no more ring segments + // to process and we can add the solution. + // + // If not, when this function ends, it will be called again with more arguments. + // Accept the solution to this segment by setting currentSolution to sol, and + // return true to continue processing. + if (m_stack == 0) + m_solutions.push_back (sol); + else + { + currentSolution = sol; + return true; + } + } + } + + return false; + } + + return true; +} + +// ============================================================================= +// Main function. Call this with r0 and r1. If this returns true, use bestSolution +// for the solution that was presented. +// +bool RingFinder::findRings (double r0, double r1) +{ + m_solutions.clear(); + Solution sol; + + // Recurse in and try find solutions. + findRingsRecursor (r0, r1, sol); + + // Compare the solutions and find the best one. The solution class has an operator> + // overload to compare two solutions. + m_bestSolution = null; + + for (QVector::iterator solp = m_solutions.begin(); solp != m_solutions.end(); ++solp) + { + const Solution& sol = *solp; + + if (m_bestSolution == null || sol.isBetterThan (m_bestSolution)) + m_bestSolution = / + } + + return (m_bestSolution != null); +} + +// ============================================================================= +// +bool RingFinder::Solution::isBetterThan (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 diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc/RingFinder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/RingFinder.h Tue Jan 21 02:03:27 2014 +0200 @@ -0,0 +1,88 @@ +/* + * 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 . + */ + +#ifndef LDFORGE_MISC_RINGFINDER_H +#define LDFORGE_MISC_RINGFINDER_H + +#include "../Main.h" + +// ============================================================================= +// RingFinder +// +// Provides an algorithm for finding a solution of rings between radii r0 and r1. +// ============================================================================= +class RingFinder +{ + public: + struct Component + { + int num; + double scale; + }; + + class Solution + { + public: + // Components of this solution + inline const QVector& getComponents() const + { + return m_components; + } + + // Add a component to this solution + inline void addComponent (const Component& a) + { + m_components.push_back (a); + } + + // Compare solutions + bool isBetterThan (const Solution* other) const; + + private: + QVector m_components; + }; + + RingFinder() {} + bool findRings (double r0, double r1); + + inline const Solution* bestSolution() + { + return m_bestSolution; + } + + inline const QVector& allSolutions() const + { + return m_solutions; + } + + inline bool operator() (double r0, double r1) + { + return findRings (r0, r1); + } + + private: + QVector m_solutions; + const Solution* m_bestSolution; + int m_stack; + + bool findRingsRecursor (double r0, double r1, Solution& currentSolution); +}; + +extern RingFinder g_RingFinder; + +#endif // LDFORGE_MISC_RINGFINDER_H \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc/documentPointer.cc --- a/src/misc/documentPointer.cc Mon Jan 20 23:44:22 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 . - */ - -#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.getPointer()) -{ - addReference (); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocumentPointer::~LDDocumentPointer() -{ - removeReference(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocumentPointer::addReference() -{ - if (getPointer() != null) - getPointer()->addReference (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDDocumentPointer::removeReference() -{ - if (getPointer() != null) - getPointer()->removeReference (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocumentPointer& LDDocumentPointer::operator= (LDDocument* ptr) -{ - if (ptr != getPointer()) - { - removeReference(); - setPointer (ptr); - addReference(); - } - - return *this; -} \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc/documentPointer.h --- a/src/misc/documentPointer.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +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 . - */ - -#ifndef LDFORGE_DOCUMENT_POINTER_H -#define LDFORGE_DOCUMENT_POINTER_H - -#include "../main.h" - -class LDSubfile; -class LDDocument; - -class LDDocumentPointer -{ - PROPERTY (private, LDDocument*, Pointer, NO_OPS, STOCK_WRITE) - - public: - LDDocumentPointer(); - LDDocumentPointer (LDDocument* ptr); - LDDocumentPointer (const LDDocumentPointer& other); - ~LDDocumentPointer(); - LDDocumentPointer& operator= (LDDocument* ptr); - - inline LDDocumentPointer& operator= (LDDocumentPointer& other) - { - return operator= (other.getPointer()); - } - - inline LDDocument* operator->() const - { - return getPointer(); - } - - inline operator LDDocument*() const - { - return getPointer(); - } - - private: - void addReference(); - void removeReference(); -}; - -#endif // LDFORGE_DOCUMENT_POINTER_H \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc/invokationDeferer.cc --- a/src/misc/invokationDeferer.cc Mon Jan 20 23:44:22 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 . - */ - -#include "moc_invokationDeferer.cpp" -#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 diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc/invokationDeferer.h --- a/src/misc/invokationDeferer.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +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 . - */ - -#ifndef LDFORGE_MISC_INVOKATION_DEFERER_H -#define LDFORGE_MISC_INVOKATION_DEFERER_H - -#include - -class InvokationDeferer : public QObject -{ - Q_OBJECT - - public: - using FunctionType = void(*)(); - - explicit InvokationDeferer (QObject* parent = 0); - void addFunctionCall (FunctionType func); - - signals: - void functionAdded(); - - private: - QList m_funcs; - - private slots: - void invokeFunctions(); -}; - -void invokeLater (InvokationDeferer::FunctionType func); - -#endif // LDFORGE_MISC_INVOKATION_DEFERER_H \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc/ringFinder.cc --- a/src/misc/ringFinder.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +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 . - */ - -#include "ringFinder.h" -#include "../misc.h" - -RingFinder g_RingFinder; - -// ============================================================================= -// This is the main algorithm of the ring finder. It tries to use math to find -// the one ring between r0 and r1. If it fails (the ring number is non-integral), -// it finds an intermediate radius (ceil of the ring number times scale) and -// splits the radius at this point, calling this function again to try find the -// rings between r0 - r and r - r1. -// -// This does not always yield into usable results. If at some point r == r0 or -// r == r1, there is no hope of finding the rings, at least with this algorithm, -// as it would fall into an infinite recursion. -// ----------------------------------------------------------------------------- -bool RingFinder::findRingsRecursor (double r0, double r1, Solution& currentSolution) -{ - // Don't recurse too deep. - if (m_stack >= 5) - return false; - - // Find the scale and number of a ring between r1 and r0. - assert (r1 >= r0); - double scale = r1 - r0; - double num = r0 / scale; - - // If the ring number is integral, we have found a fitting ring to r0 -> r1! - if (isInteger (num)) - { - Component cmp; - cmp.scale = scale; - cmp.num = (int) round (num); - currentSolution.addComponent (cmp); - - // If we're still at the first recursion, this is the only - // ring and there's nothing left to do. Guess we found the winner. - if (m_stack == 0) - { - m_solutions.push_back (currentSolution); - return true; - } - } - else - { - // Try find solutions by splitting the ring in various positions. - if (isZero (r1 - r0)) - return false; - - double interval; - - // Determine interval. The smaller delta between radii, the more precise - // interval should be used. We can't really use a 0.5 increment when - // calculating rings to 10 -> 105... that would take ages to process! - if (r1 - r0 < 0.5) - interval = 0.1; - else if (r1 - r0 < 10) - interval = 0.5; - else if (r1 - r0 < 50) - interval = 1; - else - interval = 5; - - // Now go through possible splits and try find rings for both segments. - for (double r = r0 + interval; r < r1; r += interval) - { - Solution sol = currentSolution; - - m_stack++; - bool res = findRingsRecursor (r0, r, sol) && findRingsRecursor (r, r1, sol); - m_stack--; - - if (res) - { - // We succeeded in finding radii for this segment. If the stack is 0, this - // is the first recursion to this function. Thus there are no more ring segments - // to process and we can add the solution. - // - // If not, when this function ends, it will be called again with more arguments. - // Accept the solution to this segment by setting currentSolution to sol, and - // return true to continue processing. - if (m_stack == 0) - m_solutions.push_back (sol); - else - { - currentSolution = sol; - return true; - } - } - } - - return false; - } - - return true; -} - -// ============================================================================= -// Main function. Call this with r0 and r1. If this returns true, use bestSolution -// for the solution that was presented. -// ----------------------------------------------------------------------------- -bool RingFinder::findRings (double r0, double r1) -{ - m_solutions.clear(); - Solution sol; - - // Recurse in and try find solutions. - findRingsRecursor (r0, r1, sol); - - // Compare the solutions and find the best one. The solution class has an operator> - // overload to compare two solutions. - m_bestSolution = null; - - for (QVector::iterator solp = m_solutions.begin(); solp != m_solutions.end(); ++solp) - { - const Solution& sol = *solp; - - if (m_bestSolution == null || sol.isBetterThan (m_bestSolution)) - m_bestSolution = / - } - - return (m_bestSolution != null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool RingFinder::Solution::isBetterThan (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 diff -r 6b13e4c2e97b -r b75c6cce02e2 src/misc/ringFinder.h --- a/src/misc/ringFinder.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +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 . - */ - -#ifndef LDFORGE_MISC_RINGFINDER_H -#define LDFORGE_MISC_RINGFINDER_H - -#include "../main.h" - -// ============================================================================= -// RingFinder -// -// Provides an algorithm for finding a solution of rings between radii r0 and r1. -// ============================================================================= -class RingFinder -{ - public: - struct Component - { - int num; - double scale; - }; - - class Solution - { - public: - // Components of this solution - inline const QVector& getComponents() const - { - return m_components; - } - - // Add a component to this solution - inline void addComponent (const Component& a) - { - m_components.push_back (a); - } - - // Compare solutions - bool isBetterThan (const Solution* other) const; - - private: - QVector m_components; - }; - - RingFinder() {} - bool findRings (double r0, double r1); - - inline const Solution* bestSolution() - { - return m_bestSolution; - } - - inline const QVector& allSolutions() const - { - return m_solutions; - } - - inline bool operator() (double r0, double r1) - { - return findRings (r0, r1); - } - - private: - QVector m_solutions; - const Solution* m_bestSolution; - int m_stack; - - bool findRingsRecursor (double r0, double r1, Solution& currentSolution); -}; - -extern RingFinder g_RingFinder; - -#endif // LDFORGE_MISC_RINGFINDER_H \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/primitives.cc --- a/src/primitives.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,704 +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 . - */ - -#include -#include -#include -#include "document.h" -#include "gui.h" -#include "primitives.h" -#include "ui_makeprim.h" -#include "misc.h" -#include "colors.h" -#include "moc_primitives.cpp" - -QList g_PrimitiveCategories; -QList 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)) - { - // 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(); - log ("%1 primitives loaded.\n", g_primitives.size()); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void recursiveGetFilenames (QDir dir, QList& 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()); - log ("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.cat = 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 (fmt ("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(); - log ("%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.cat = null; - - // Go over the categories and their regexes, if and when there's a match, - // the primitive's category is set to the category the regex beloings to. - for (PrimitiveCategory* cat : g_PrimitiveCategories) - { - for (RegexEntry& entry : cat->regexes) - { - switch (entry.type) - { - case EFilenameRegex: - { - // f-regex, check against filename - matched = entry.regex.exactMatch (prim.name); - } break; - - case ETitleRegex: - { - // t-regex, check against title - matched = entry.regex.exactMatch (prim.title); - } break; - } - - if (matched) - { - prim.cat = cat; - break; - } - } - - // Drop out if a category was decided on. - if (prim.cat != null) - break; - } - - // If there was a match, add the primitive to the category. - // Otherwise, add it to the list of unmatched primitives. - if (prim.cat != null) - prim.cat->prims << prim; - else - g_unmatched->prims << prim; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PrimitiveCategory::loadCategories() -{ - for (PrimitiveCategory* cat : g_PrimitiveCategories) - delete cat; - - g_PrimitiveCategories.clear(); - QString path = Config::dirpath() + "primregexps.cfg"; - - if (!QFile::exists (path)) - path = ":/data/primitive-categories.cfg"; - - QFile f (path); - - if (!f.open (QIODevice::ReadOnly)) - { - critical (fmt (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); - ERegexType type = EFilenameRegex; - - if (cmd == "f") - type = EFilenameRegex; - elif (cmd == "t") - type = ETitleRegex; - else - { - log (tr ("Warning: unknown command \"%1\" on line \"%2\""), cmd, line); - continue; - } - - QRegExp regex (line.mid (colon + 1)); - RegexEntry entry = { regex, type }; - cat->regexes << entry; - } - else - log ("Warning: Rules given before the first category name"); - } - - if (cat->isValidToInclude()) - g_PrimitiveCategories << cat; - - // Add a category for unmatched primitives. - // Note: if this function is called the second time, g_unmatched has been - // deleted at the beginning of the function and is dangling at this point. - g_unmatched = new PrimitiveCategory (tr ("Other")); - g_PrimitiveCategories << g_unmatched; - f.close(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool PrimitiveCategory::isValidToInclude() -{ - if (regexes.size() == 0) - { - log (tr ("Warning: category \"%1\" left without patterns"), getName()); - deleteLater(); - return false; - } - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool isPrimitiveLoaderBusy() -{ - return g_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& 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 condLineSegs; - QList 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 == lores) ? "" : fmt ("%1/", divs); - QString frac = fmt ("%1-%2", numer, denom); - QString root = g_radialNameRoots[type]; - QString numstr = (type == Ring || type == Cone) ? fmt ("%1", num) : ""; - - // Truncate the root if necessary (7-16rin4.dat for instance). - // However, always keep the root at least 2 characters. - int extra = (frac.length() + numstr.length() + root.length()) - 8; - root.chop (clamp (extra, 0, 2)); - - // Stick them all together and return the result. - return prefix + frac + root + numstr + ".dat"; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument* generatePrimitive (PrimitiveType type, int segs, int divs, int num) -{ - // Make the description - 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 = fmt ("%1 %2%3 x %4", primitiveTypeName (type), spacing, num, frac); - } - else - descr = fmt ("%1 %2", primitiveTypeName (type), frac); - - // Prepend "Hi-Res" if 48/ primitive. - if (divs == hires) - descr.insert (0, "Hi-Res "); - - LDDocument* f = new LDDocument; - f->setDefaultName (name); - - QString author = APPNAME; - QString license = ""; - - if (ld_defaultname.isEmpty() == false) - { - license = getLicenseText (ld_defaultlicense); - author = fmt ("%1 [%2]", ld_defaultname, ld_defaultuser); - } - - f->addObjects ( - { - new LDComment (descr), - new LDComment (fmt ("Name: %1", name)), - new LDComment (fmt ("Author: %1", author)), - new LDComment (fmt ("!LDRAW_ORG Unofficial_%1Primitive", divs == hires ? "48_" : "")), - new LDComment (license), - new LDEmpty, - new LDBFC (LDBFC::CertifyCCW), - new LDEmpty, - }); - - f->addObjects (makePrimitive (type, segs, divs, num)); - return f; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num) -{ - 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 ? hires : lores); - - // If the current value is 16 and we switch to hi-res, default the - // spinbox to 48. - if (on && ui->sb_segs->value() == lores) - ui->sb_segs->setValue (hires); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (MakePrimitive, 0) -{ - PrimitivePrompt* dlg = new PrimitivePrompt (g_win); - - if (!dlg->exec()) - return; - - int segs = dlg->ui->sb_segs->value(); - int divs = dlg->ui->cb_hires->isChecked() ? hires : lores; - int num = dlg->ui->sb_ringnum->value(); - PrimitiveType type = - dlg->ui->rb_circle->isChecked() ? Circle : - dlg->ui->rb_cylinder->isChecked() ? Cylinder : - dlg->ui->rb_disc->isChecked() ? Disc : - dlg->ui->rb_ndisc->isChecked() ? DiscNeg : - dlg->ui->rb_ring->isChecked() ? Ring : Cone; - - LDDocument* f = generatePrimitive (type, segs, divs, num); - - g_win->save (f, false); - delete f; -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/primitives.h --- a/src/primitives.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +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 . - */ - -#ifndef LDFORGE_PRIMITIVES_H -#define LDFORGE_PRIMITIVES_H - -#include "main.h" -#include "types.h" -#include -#include - -class LDDocument; -class Ui_MakePrimUI; -class PrimitiveCategory; -struct Primitive -{ - QString name, title; - PrimitiveCategory* cat; -}; - -class PrimitiveCategory : public QObject -{ - Q_OBJECT - PROPERTY (public, QString, Name, STR_OPS, STOCK_WRITE) - - public: - enum ERegexType - { - EFilenameRegex, - ETitleRegex - }; - - struct RegexEntry - { - QRegExp regex; - ERegexType type; - }; - - QList regexes; - QList 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 m_prims; - QStringList m_files; - int m_i; - int m_baselen; -}; - -extern QList 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& 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); - -#endif // LDFORGE_PRIMITIVES_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/property.h --- a/src/property.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,124 +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 . - */ - -#ifndef LDFORGE_PROPERTY_H -#define LDFORGE_PROPERTY_H - -#define PROPERTY( ACCESS, TYPE, NAME, OPS, WRITETYPE ) \ - private: \ - TYPE m_##NAME; \ - \ - public: \ - inline TYPE const& GET_READ_METHOD( NAME, OPS ) const \ - { \ - return m_##NAME; \ - } \ - \ - ACCESS: \ - DEFINE_WRITE_METHOD_##WRITETYPE( TYPE, NAME ) \ - DEFINE_PROPERTY_##OPS( TYPE, NAME ) - -#define GET_READ_METHOD( NAME, OPS ) \ - GET_READ_METHOD_##OPS( NAME ) - -#define GET_READ_METHOD_BOOL_OPS( NAME ) is##NAME() -#define GET_READ_METHOD_NO_OPS( NAME ) get##NAME() -#define GET_READ_METHOD_STR_OPS( NAME ) get##NAME() -#define GET_READ_METHOD_NUM_OPS( NAME ) get##NAME() -#define GET_READ_METHOD_LIST_OPS( NAME ) get##NAME() - -#define DEFINE_WRITE_METHOD_STOCK_WRITE( TYPE, NAME ) \ - inline void set##NAME( TYPE const& NAME##_ ) \ - { \ - m_##NAME = NAME##_; \ - } - -#define DEFINE_WRITE_METHOD_CUSTOM_WRITE( TYPE, NAME ) \ - void set##NAME( TYPE const& NAME##_ ); \ - -#define DEFINE_WITH_CB( NAME ) void NAME##Changed(); -#define DEFINE_NO_CB( NAME ) - -#define DEFINE_PROPERTY_NO_OPS( TYPE, NAME ) - -#define DEFINE_PROPERTY_STR_OPS( TYPE, NAME ) \ - void appendTo##NAME( TYPE a ) \ - { \ - TYPE tmp( m_##NAME ); \ - tmp.append( a ); \ - set##NAME( tmp ); \ - } \ - \ - void prependTo##NAME( TYPE a ) \ - { \ - TYPE tmp( m_##NAME ); \ - tmp.prepend( a ); \ - set##NAME( tmp ); \ - } \ - \ - void replaceIn##NAME( TYPE a, TYPE b ) \ - { \ - TYPE tmp( m_##NAME ); \ - tmp.replace( a, b ); \ - set##NAME( tmp ); \ - } - -#define DEFINE_PROPERTY_NUM_OPS( TYPE, NAME ) \ - inline void increase##NAME( TYPE a = 1 ) \ - { \ - set##NAME( m_##NAME + a ); \ - } \ - \ - inline void decrease##NAME( TYPE a = 1 ) \ - { \ - set##NAME( m_##NAME - a ); \ - } - -#define DEFINE_PROPERTY_BOOL_OPS( TYPE, NAME ) \ - inline void toggle##NAME() \ - { \ - set##NAME( !m_##NAME ); \ - } - -#define DEFINE_PROPERTY_LIST_OPS( TYPE, NAME ) \ - void pushTo##NAME( const TYPE::value_type& a ) \ - { \ - TYPE tmp( m_##NAME ); \ - tmp.push_back( a ); \ - set##NAME( tmp ); \ - } \ - \ - void removeFrom##NAME( const TYPE::value_type& a ) \ - { \ - TYPE tmp( m_##NAME ); \ - tmp.removeOne( a ); \ - set##NAME( tmp ); \ - } \ - \ - inline void clear##NAME() \ - { \ - set##NAME( TYPE() ); \ - } \ - \ - public: \ - inline int count##NAME() \ - { \ - return get##NAME().size(); \ - } - -#endif // LDFORGE_PROPERTY_H \ No newline at end of file diff -r 6b13e4c2e97b -r b75c6cce02e2 src/types.cc --- a/src/types.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,416 +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 . - */ - -#include -#include -#include -#include -#include -#include "main.h" -#include "types.h" -#include "misc.h" -#include "ldtypes.h" -#include "document.h" - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString DoFormat (QList args) -{ - assert (args.size() >= 1); - QString text = args[0].value(); - - for (uchar i = 1; i < args.size(); ++i) - text = text.arg (args[i].value()); - - return text; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -Vertex::Vertex (double x, double y, double z) -{ - m_coords[X] = x; - m_coords[Y] = y; - m_coords[Z] = z; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void Vertex::move (const Vertex& other) -{ - for_axes (ax) - m_coords[ax] += other[ax]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -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 fmtstr = "%1 %2 %3"; - - if (mangled) - fmtstr = "(%1, %2, %3)"; - - return fmt (fmtstr, 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 (initlist vals) -{ - assert (vals.size() == 9); - memcpy (&m_vals[0], & (*vals.begin()), sizeof m_vals); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void Matrix::puts() const -{ - for (int i = 0; i < 3; ++i) - { - for (int j = 0; j < 3; ++j) - log ("%1\t", m_vals[ (i * 3) + j]); - - log ("\n"); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -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 (val (0) * val (4) * val (8)) + - (val (1) * val (5) * val (6)) + - (val (2) * val (3) * val (7)) - - (val (2) * val (4) * val (6)) - - (val (1) * val (3) * val (8)) - - (val (0) * val (5) * val (7)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool Matrix::operator== (const Matrix& other) const -{ - for (int i = 0; i < 9; ++i) - if (val (i) != other[i]) - return false; - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDBoundingBox::LDBoundingBox() -{ - reset(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDBoundingBox::calculate() -{ - reset(); - - if (!getCurrentDocument()) - return; - - for (LDObject* obj : getCurrentDocument()->getObjects()) - calcObject (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDBoundingBox::calcObject (LDObject* obj) -{ - switch (obj->getType()) - { - case LDObject::ELine: - case LDObject::ETriangle: - case LDObject::EQuad: - case LDObject::ECondLine: - { - for (int i = 0; i < obj->vertices(); ++i) - calcVertex (obj->getVertex (i)); - } break; - - case LDObject::ESubfile: - { - LDSubfile* ref = static_cast (obj); - LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline); - - for (LDObject * obj : objs) - { - calcObject (obj); - obj->deleteSelf(); - } - } - break; - - default: - break; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDBoundingBox& LDBoundingBox::operator<< (const Vertex& v) -{ - calcVertex (v); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDBoundingBox& LDBoundingBox::operator<< (LDObject* obj) -{ - calcObject (obj); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDBoundingBox::calcVertex (const Vertex& v) -{ - for_axes (ax) - { - m_Vertex0[ax] = min (v[ax], m_Vertex0[ax]); - m_Vertex1[ax] = max (v[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::size() const -{ - double xscale = (m_Vertex0[X] - m_Vertex1[X]); - double yscale = (m_Vertex0[Y] - m_Vertex1[Y]); - double zscale = (m_Vertex0[Z] - m_Vertex1[Z]); - double size = zscale; - - if (xscale > yscale) - { - if (xscale > zscale) - size = xscale; - } - elif (yscale > zscale) - size = yscale; - - if (abs (size) >= 2.0f) - return abs (size / 2); - - return 1.0f; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -Vertex LDBoundingBox::center() const -{ - return Vertex ( - (m_Vertex0[X] + m_Vertex1[X]) / 2, - (m_Vertex0[Y] + m_Vertex1[Y]) / 2, - (m_Vertex0[Z] + m_Vertex1[Z]) / 2); -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/types.h --- a/src/types.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,315 +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 . - */ - -#ifndef LDFORGE_TYPES_H -#define LDFORGE_TYPES_H - -#include -#include -#include -#include -#include "property.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 -using initlist = std::initializer_list; - -template -using pair = std::pair; - -enum Axis -{ - X, - Y, - Z -}; - -// ============================================================================= -// -class LDObject; -using LDObjectList = QList; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -// matrix -// -// A mathematical 3 x 3 matrix -// ============================================================================= -class Matrix -{ - public: - Matrix() {} - Matrix (initlist vals); - Matrix (double fillval); - Matrix (double vals[]); - - double getDeterminant() const; - Matrix mult (const Matrix& other) const; - void puts() const; - QString toString() const; - void zero(); - Matrix& operator= (const Matrix& other); - - inline double& val (int idx) - { - return m_vals[idx]; - } - - inline const double& val (int idx) const - { - return m_vals[idx]; - } - - inline Matrix operator* (const Matrix& other) const - { - return mult (other); - } - - inline double& operator[] (int idx) - { - return val (idx); - } - - inline const double& operator[] (int idx) const - { - return val (idx); - } - - bool operator== (const Matrix& other) const; - - private: - double m_vals[9]; -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -// Vertex -// -// Vertex class, contains a single point in 3D space. Not to be confused with -// LDVertex, which is a vertex used in an LDraw part file. -// ============================================================================= -class Vertex -{ - public: - Vertex() {} - Vertex (double x, double y, double z); - - double distanceTo (const Vertex& other) const; - Vertex midpoint (const Vertex& other); - void move (const Vertex& other); - QString toString (bool mangled) const; - void transform (const Matrix& matr, const Vertex& pos); - - Vertex& operator+= (const Vertex& other); - Vertex operator+ (const Vertex& other) const; - Vertex operator/ (const double d) const; - Vertex& operator/= (const double d); - bool operator== (const Vertex& other) const; - bool operator!= (const Vertex& other) const; - Vertex operator-() const; - int operator< (const Vertex& other) const; - - inline double& operator[] (const Axis ax) - { - return getCoordinate ((int) ax); - } - - inline const double& operator[] (const Axis ax) const - { - return getCoordinate ((int) ax); - } - - inline double& operator[] (const int ax) - { - return getCoordinate (ax); - } - - inline const double& operator[] (const int ax) const - { - return getCoordinate (ax); - } - - inline double& getCoordinate (int n) - { - return m_coords[n]; - } - - inline const double& getCoordinate (int n) const - { - return m_coords[n]; - } - - inline double& x() - { - return m_coords[X]; - } - - inline const double& x() const - { - return m_coords[X]; - } - - inline double& y() - { - return m_coords[Y]; - } - - inline const double& y() const - { - return m_coords[Y]; - } - - inline double& z() - { - return m_coords[Z]; - } - - inline const double& z() const - { - return m_coords[Z]; - } - - private: - double m_coords[3]; -}; - -Q_DECLARE_METATYPE (Vertex) - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -// StringFormatArg -// -// Converts a given value into a string that can be retrieved with ::value(). -// Used as the argument type to the formatting functions, hence its name. -// ============================================================================= -class StringFormatArg -{ - public: - StringFormatArg (const QString& a) : m_val (a) {} - StringFormatArg (const char& a) : m_val (a) {} - StringFormatArg (const uchar& a) : m_val (a) {} - StringFormatArg (const QChar& a) : m_val (a) {} - StringFormatArg (int a) : m_val (QString::number (a)) {} - StringFormatArg (const float& a) : m_val (QString::number (a)) {} - StringFormatArg (const double& a) : m_val (QString::number (a)) {} - StringFormatArg (const Vertex& a) : m_val (a.toString (false)) {} - StringFormatArg (const Matrix& a) : m_val (a.toString()) {} - StringFormatArg (const char* a) : m_val (a) {} - - StringFormatArg (const void* a) - { - m_val.sprintf ("%p", a); - } - - template StringFormatArg (const QList& a) - { - m_val = "{ "; - - for (const T& it : a) - { - if (&it != &a.first()) - m_val += ", "; - - StringFormatArg arg (it); - m_val += arg.value(); - } - - if (!a.isEmpty()) - m_val += " "; - - m_val += "}"; - } - - inline QString value() const - { - return m_val; - } - - private: - QString m_val; -}; - -// ============================================================================= -// LDBoundingBox -// -// The bounding box is the box that encompasses a given set of objects. -// v0 is the minimum vertex, v1 is the maximum vertex. -// ============================================================================= -class LDBoundingBox -{ - PROPERTY (private, bool, Empty, BOOL_OPS, STOCK_WRITE) - PROPERTY (private, Vertex, Vertex0, NO_OPS, STOCK_WRITE) - PROPERTY (private, Vertex, Vertex1, NO_OPS, STOCK_WRITE) - - public: - LDBoundingBox(); - void reset(); - void calculate(); - double size() const; - void calcObject (LDObject* obj); - void calcVertex (const Vertex& v); - Vertex center() const; - - LDBoundingBox& operator<< (LDObject* obj); - LDBoundingBox& operator<< (const Vertex& v); -}; - -// Formatter function -QString DoFormat (QList args); - -// printf replacement -void doPrint (QFile& f, QList args); -void doPrint (FILE* fp, QList args); - -// log() - universal access to the message log. Defined here so that I don't have -// to include messagelog.h here and recompile everything every time that file changes. -void DoLog (std::initializer_list args); - -// Macros to access these functions -# ifndef IN_IDE_PARSER -#define fmt(...) DoFormat ({__VA_ARGS__}) -# define fprint(F, ...) doPrint (F, {__VA_ARGS__}) -# define log(...) DoLog({ __VA_ARGS__ }) -#else -QString fmt (const char* fmtstr, ...); -void fprint (QFile& f, const char* fmtstr, ...); -void fprint (FILE* fp, const char* fmtstr, ...); -void log (const char* fmtstr, ...); -#endif - -extern const Vertex g_origin; // Vertex at (0, 0, 0) -extern const Matrix g_identity; // Identity matrix - -static const double pi = 3.14159265358979323846; - -#endif // LDFORGE_TYPES_H diff -r 6b13e4c2e97b -r b75c6cce02e2 src/widgets.cc --- a/src/widgets.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,195 +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 . - */ - -// 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 -#include -#include -#include -#include - -#include "widgets.h" -#include "moc_widgets.cpp" - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup::RadioGroup (const QString& title, QWidget* parent) : QGroupBox (title, parent) -{ - init (Qt::Vertical); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false) -{ - return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool RadioGroup::isChecked (int n) const -{ - return m_buttonGroup->checkedId() == n; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::init (Qt::Orientation orient) -{ - m_vert = orient == Qt::Vertical; - - m_buttonGroup = new QButtonGroup; - m_oldId = m_curId = 0; - m_coreLayout = null; - - m_coreLayout = new QBoxLayout ( (orient == Qt::Vertical) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom); - setLayout (m_coreLayout); - - // Init the first row with a break - rowBreak(); - - connect (m_buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int))); - connect (m_buttonGroup, SIGNAL (buttonReleased (int)), this, SLOT (slot_buttonReleased (int))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup::RadioGroup (const QString& title, initlist 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(); -} diff -r 6b13e4c2e97b -r b75c6cce02e2 src/widgets.h --- a/src/widgets.h Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +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 . - */ - -#ifndef WIDGETS_H -#define WIDGETS_H - -#include -#include -#include -#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::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, initlist 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 m_objects; - QList 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); -}; - -#endif // WIDGETS_H