Mon, 20 Jan 2014 15:04:26 +0200
- Merge commit '5c835eb' into HEAD
Conflicts:
src/file.cpp
src/gldraw.cc
src/gldraw.h
--- a/.gitignore Wed Oct 23 13:14:17 2013 +0300 +++ b/.gitignore Mon Jan 20 15:04:26 2014 +0200 @@ -1,4 +1,5 @@ build +build_shared build_debug build_release ldforge.kdev4 @@ -8,4 +9,6 @@ ldforge_debug *.dat debug_lastOutput -.kdev_include_paths \ No newline at end of file +debug_lastInput +.kdev_include_paths +*.cfg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/INSTALL Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,16 @@ +You need Qt4 or Qt5 with OpenGL and networking support. On Debian and +derivatives, e.g. Ubuntu, you'll need: + sudo apt-get install build-essential libqt4-dev libqt4-opengl-dev + libqt4-network + +You may also replace qt4 with qt5 in the above for Qt5. + +To compile: +- enter the root directory +- enter 'qmake' +- enter 'make release' or 'make debug'. A release build is made by default. + +Troubleshooting: +- src/gldraw.h:22:21: fatal error: QGLWidget: No such file or directory +You are missing the OpenGL development package, on Debian and derivatives, +this is libqt4-opengl-dev or libqt5-opengl-dev. \ No newline at end of file
--- a/README.md Wed Oct 23 13:14:17 2013 +0300 +++ b/README.md Mon Jan 20 15:04:26 2014 +0200 @@ -13,6 +13,7 @@ * Parse error recovery, if a line/object cannot be parsed properly it will be displayed as an errorneous object. This object can be selected and its contents edited and have it reparsed, so you can fix these errors within LDForge. * 6 camera modes plus a free-angle one. * Drawing mode that allows you to literally draw polygons and lines into the screen. +* Circle drawing mode as an extension to drawing mode to allow easy circle and ring placement. (development version only) * A simple primitive generator * Object hiding * Select by color or type
--- a/changelog.txt Wed Oct 23 13:14:17 2013 +0300 +++ b/changelog.txt Mon Jan 20 15:04:26 2014 +0200 @@ -1,25 +1,33 @@ ================================================= -== Changes in version 0.3-alpha +== Changes in version 0.3 ================================================= - Multiple files can now be kept open at the same time. A new list widget is on the left to contain the list of currently open files. - Added close, close all and save all actions. - Added ability to download parts from the Parts Tracker or from a custom-specified URL. If the resulting file contains references to unknown files, LDForge attempts to recursively download all of them. -- Converted the configuration code to use QSettings, in practice this means the configuration file moved to - the registry under Windows and into ~/.config/LDForge under Linux. Unfortunately this means settings get - lost during transition from version 0.2 and 0.3. - Added a new editing mode for drawing circles. -- Corrections to the primitive generator: +- Major corrections to the primitive generator: - Fixed: "Hi-Res" was not prepended to the names of 48/ primitives. - Fixed: Checking the Hi-Res option would not allow segment values over 16. - Added support for multiple spaces before the ring number. +- Added an action for quickly subfiling the group of selected objects. +- Coordinate rounding now works properly, figures scientific notation and rounds subfile position and matrix. + Values are also now properly rounded instead of just floored, 1.2348 now rounds to 1.235 and not 1.234. + Subfile matrix values are rounded to 4 decimals, everything else to 3 decimals. +- Implicitely-opened documents are now closed automatically when rendered unused, hopefully reducing memory + usage somewhat. +- Configuration is now always situated and read from the application working directory +- LDraw code parser no longer complains about scientific notation. - Changing to draw mode while in free camera now causes the camera to be changed to top. +- When drawing polygons, line lengths are now displayed. Added a configuration option to toggle this behavior. +- Added an option for drawing line angles similarly. - Added config fields for default name/username/license. This data will be automatically filled into forms that require such information. - Upon first start the configuration prompt pops up on its own, defaulting on the profile tab. This way the user can fill in the profile data on the first start and get that out of the way (and gives the opportunity to see the other config fields) +- Now recognizes "8\" as a special subdirectory alongside "48\" and "s\" - Added new action "Add History Line" for quickly inserting 0 !HISTORY lines to headers - Added new action "Go to line", default shortcut Ctrl-G. It should be obvious what it does. - Added new actions "Hide" and "Reveal" which allow direct setting of objects' visibility setting instead of @@ -27,6 +35,9 @@ - Added support for logoed studs, this should satisfy Steffen. :p - Added support for '0 BFC CLIP' and '0 BFC NOCLIP' and added auto-correction from errorneous MLCAD syntax ('0 BFC CERTIFY CLIP'). +- Increased the amount of recent file slots from 5 to 10. +- Added Ball Joint-8 primitives to the primitive categories. +- Fixed: Configuration window did not allow for grid angle values higher than 99.99 - The viewport now uses 7 sets of rotation/pan/zoom values, one for each camera. Changing these values in one camera no longer affects other cameras. - When an external program is attempted to be used without a binary path defined, one will be asked @@ -34,17 +45,29 @@ - When adding edges with Intersector (which is done with Isecalc), the user is prompted for Isecalc's path now as well if necessary instead of just ignoring it and not adding the edgelines. - Added a configuration option for line anti-aliasing. +- Added a configurable selected color to blend to objects when they are selected, instead of a simple + light-up. Defaults to a blue color. - BFC red/green view and black edges no longer default to true. +- When the background is dark, the "black edges" option actually means "white edges" now. - If the vertex snapper finds a vertex closer than 4 pixels, it likely is the vertex being looked for and the algorithm can terminate early, hopefully this will save a few cycles on large parts. - The camera icons now draw real tooltips instead of emulated ones. - Color icon border now reflects the color's edge color. -- Fixed: File loading would skip every 300th line. +- Changed the default color toolbar to have a wider range of colors. No need for transparent ones. +- Fixed: When rotating subfiles, the scale was treated incorrectly due to bad operand order in matrix + multiplication, causing unwanted morphing. +- [Linux] Fixed: If an external program is attempted to be launched requiring Wine but with Wine missing, + there was no error message and instead LDForge took this as no output from the program. - Fixed: LDForge would sometimes crash during startup over uninitialized data in the GL renderer. - Fixed: The message log was still written with black text with dark backgrounds. ================================================= -== Changes in version 0.2-alpha +== Changes in version 0.2.1 +================================================= +- Fixed: File loading would skip every 300th line (don't ask me how this managed to happen). + +================================================= +== Changes in version 0.2 ================================================= - Completely rewrote history (undo/redo) code, making it a LOT stabler in the process.
--- a/data/primitive-categories.cfg Wed Oct 23 13:14:17 2013 +0300 +++ b/data/primitive-categories.cfg Mon Jan 20 15:04:26 2014 +0200 @@ -72,7 +72,7 @@ Cones (48) f:48\\[0-9]+\-[0-9]+cone[0-9]\.dat f:48\\[0-9]+\-[0-9]+con[0-9]+\.dat -f:48\\[0-9]+\-[0-9]+ri[0-9]+\.dat +f:48\\[0-9]+\-[0-9]+co[0-9]+\.dat Other (48) f:48\\.*\.dat @@ -109,4 +109,7 @@ Technic Other f:tooth.*\.dat f:tootb.*\.dat -t:Technic .* \ No newline at end of file +t:Technic .* + +Ball Joint-8 +f:joint-8-.*\.dat \ No newline at end of file
--- a/ldforge.pro Wed Oct 23 13:14:17 2013 +0300 +++ b/ldforge.pro Mon Jan 20 15:04:26 2014 +0200 @@ -4,34 +4,29 @@ TEMPLATE = app TARGET = ldforge -SUBDIRS += ./src - -TARGET = ldforge -DEPENDPATH += . -INCLUDEPATH += . ./build/ RC_FILE = ldforge.rc RESOURCES = ldforge.qrc -RCC_DIR = ./build/ -MOC_DIR = ./build/ -RCC_DIR = ./build/ -UI_DIR = ./build/ -SOURCES = src/*.cpp -HEADERS = src/*.h +RCC_DIR = ./build_shared/ +MOC_DIR = ./build_shared/ +UI_DIR = ./build_shared/ +SOURCES = src/*.cc +HEADERS = src/*.h src/misc/*.h FORMS = ui/*.ui QT += opengl network QMAKE_CXXFLAGS += -std=c++0x CONFIG += debug_and_release CONFIG (debug, debug|release) { - TARGET = ldforge_debug DEFINES += DEBUG OBJECTS_DIR = ./build_debug/ } else { - TARGET = ldforge DEFINES += RELEASE OBJECTS_DIR = ./build_release/ } +exists(.git): DEFINES += GIT_DESCRIBE="\"\\\"$$system(git describe --tags --long)\\\"\"" + unix { LIBS += -lGLU + DEFINES += COMPILE_DATE="\"\\\"$$system(LC_ALL=C date \"+%d %h %Y %H:%M:%S\")\\\"\"" }
--- a/ldforge.qrc Wed Oct 23 13:14:17 2013 +0300 +++ b/ldforge.qrc Mon Jan 20 15:04:26 2014 +0200 @@ -66,6 +66,7 @@ <file>./icons/line.png</file> <file>./icons/mail.png</file> <file>./icons/make-borders.png</file> + <file>./icons/mode-angle.png</file> <file>./icons/mode-circle.png</file> <file>./icons/mode-draw.png</file> <file>./icons/mode-select.png</file>
--- a/src/actions.h Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,112 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -act (New) -act (NewFile) -act (Open) -act (DownloadFrom) -act (Save) -act (SaveAs) -act (SaveAll) -act (Close) -act (CloseAll) -act (InsertFrom) -act (ExportTo) -act (Settings) -act (SetLDrawPath) -act (ScanPrimitives) -act (Exit) -act (ResetView) -act (Axes) -act (Wireframe) -act (BFCView) -act (SetOverlay) -act (ClearOverlay) -act (Screenshot) -act (InsertRaw) -act (NewSubfile) -act (NewLine) -act (NewTriangle) -act (NewQuad) -act (NewCLine) -act (NewComment) -act (NewBFC) -act (NewVertex) -act (Undo) -act (Redo) -act (Cut) -act (Copy) -act (Paste) -act (Delete) -act (SelectAll) -act (SelectByColor) -act (SelectByType) -act (ModeDraw) -act (ModeSelect) -act (ModeCircle) -act (SetDrawDepth) -act (SetColor) -act (Autocolor) -act (Uncolorize) -act (Inline) -act (InlineDeep) -act (Invert) -act (MakePrimitive) -act (SplitQuads) -act (EditRaw) -act (Borders) -act (CornerVerts) -act (RoundCoordinates) -act (VisibilityHide) -act (VisibilityReveal) -act (VisibilityToggle) -act (ReplaceCoords) -act (Flip) -act (Demote) -act (Ytruder) -act (Rectifier) -act (Intersector) -act (Isecalc) -act (Coverer) -act (Edger2) -act (Help) -act (About) -act (AboutQt) -act (GridCoarse) -act (GridMedium) -act (GridFine) -act (Edit) -act (MoveUp) -act (MoveDown) -act (MoveXNeg) -act (MoveXPos) -act (MoveYNeg) -act (MoveYPos) -act (MoveZNeg) -act (MoveZPos) -act (RotateXNeg) -act (RotateXPos) -act (RotateYNeg) -act (RotateYPos) -act (RotateZNeg) -act (RotateZPos) -act (RotationPoint) -act (AddHistoryLine) -act (JumpTo) - -#undef act \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/addObjectDialog.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#include <QGridLayout> +#include <QCheckBox> +#include <QDialogButtonBox> +#include <QSpinBox> +#include <QLabel> +#include <QListWidget> +#include <QTreeWidget> +#include <QLineEdit> +#include <QPushButton> +#include "gui.h" +#include "addObjectDialog.h" +#include "document.h" +#include "colors.h" +#include "colorSelectDialog.h" +#include "history.h" +#include "widgets.h" +#include "misc.h" +#include "primitives.h" +#include "moc_addObjectDialog.cpp" + +// ============================================================================= +// ----------------------------------------------------------------------------- +class SubfileListItem : public QTreeWidgetItem +{ + PROPERTY (public, Primitive*, PrimitiveInfo, NO_OPS, STOCK_WRITE) + + public: + SubfileListItem (QTreeWidgetItem* parent, Primitive* info) : + QTreeWidgetItem (parent), + m_PrimitiveInfo (info) {} + + SubfileListItem (QTreeWidget* parent, Primitive* info) : + QTreeWidgetItem (parent), + m_PrimitiveInfo (info) {} +}; + +// ============================================================================= +// ----------------------------------------------------------------------------- +AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) : + QDialog (parent) +{ + setlocale (LC_ALL, "C"); + + int coordCount = 0; + QString typeName = LDObject::typeName (type); + + switch (type) + { + case LDObject::EComment: + { + le_comment = new QLineEdit; + + if (obj) + le_comment->setText (static_cast<LDComment*> (obj)->text); + + le_comment->setMinimumWidth (384); + } break; + + case LDObject::ELine: + { + coordCount = 6; + } break; + + case LDObject::ETriangle: + { + coordCount = 9; + } break; + + case LDObject::EQuad: + case LDObject::ECondLine: + { + coordCount = 12; + } break; + + case LDObject::EVertex: + { + coordCount = 3; + } break; + + case LDObject::EBFC: + { + rb_bfcType = new RadioGroup ("Statement", {}, 0, Qt::Vertical); + + for (int i = 0; i < LDBFC::NumStatements; ++i) + { + // Separate these in two columns + if (i == LDBFC::NumStatements / 2) + rb_bfcType->rowBreak(); + + rb_bfcType->addButton (LDBFC::statements[i]); + } + + if (obj) + rb_bfcType->setValue ( (int) static_cast<LDBFC*> (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<QTreeWidgetItem*> subfileItems; + + for (Primitive& prim : cat->prims) + { + SubfileListItem* item = new SubfileListItem (parentItem, &prim); + item->setText (0, fmt ("%1 - %2", prim.name, prim.title)); + subfileItems << item; + + // If this primitive is the one the current object points to, + // select it by default + if (obj && static_cast<LDSubfile*> (obj)->getFileInfo()->getName() == prim.name) + tw_subfileList->setCurrentItem (item); + } + + tw_subfileList->addTopLevelItem (parentItem); + } + + connect (tw_subfileList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_subfileTypeChanged())); + lb_subfileName = new QLabel ("File:"); + le_subfileName = new QLineEdit; + le_subfileName->setFocus(); + + if (obj) + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + le_subfileName->setText (ref->getFileInfo()->getName()); + } + } break; + + default: + { + critical (fmt ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName)); + } return; + } + + QPixmap icon = getIcon (fmt ("add-%1", typeName)); + LDObject* defaults = LDObject::getDefault (type); + + lb_typeIcon = new QLabel; + lb_typeIcon->setPixmap (icon); + + // Show a color edit dialog for the types that actually use the color + if (defaults->isColored()) + { + if (obj != null) + colnum = obj->getColor(); + else + colnum = (type == LDObject::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<LDMatrixObject*> (obj); + + QLabel* lb_matrix = new QLabel ("Matrix:"); + le_matrix = new QLineEdit; + // le_matrix->setValidator (new QDoubleValidator); + Matrix defaultMatrix = g_identity; + + if (mo) + { + for_axes (ax) + dsb_coords[ax]->setValue (mo->getPosition()[ax]); + + defaultMatrix = mo->getTransform(); + } + + le_matrix->setText (defaultMatrix.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<SubfileListItem*> (tw_subfileList->currentItem()); + + if (item->getPrimitiveInfo() == null) + return ""; // selected a heading + + return item->getPrimitiveInfo()->name; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddObjectDialog::slot_colorButtonClicked() +{ + ColorSelector::selectColor (colnum, colnum, this); + setButtonBackground (pb_color, colnum); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddObjectDialog::slot_subfileTypeChanged() +{ + QString name = currentSubfileName(); + + if (name.length() > 0) + le_subfileName->setText (name); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +template<class T> static T* initObj (LDObject*& obj) +{ + if (obj == null) + obj = new T; + + return static_cast<T*> (obj); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AddObjectDialog::staticDialog (const LDObject::Type type, LDObject* obj) +{ + setlocale (LC_ALL, "C"); + + // FIXME: Redirect to Edit Raw + if (obj && obj->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<LDComment> (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<LDBFC> (obj); + bfc->type = (LDBFC::Type) dlg.rb_bfcType->value(); + } break; + + case LDObject::EVertex: + { + LDVertex* vert = initObj<LDVertex> (obj); + + for_axes (ax) + vert->pos[ax] = dlg.dsb_coords[ax]->value(); + } + break; + + case LDObject::ESubfile: + { + QString name = dlg.le_subfileName->text(); + + if (name.length() == 0) + return; // no subfile filename + + LDDocument* file = getDocument (name); + + if (!file) + { + critical (fmt ("Couldn't open `%1': %2", name, strerror (errno))); + return; + } + + LDSubfile* ref = initObj<LDSubfile> (obj); + assert (ref); + + for_axes (ax) + ref->setCoordinate (ax, dlg.dsb_coords[ax]->value()); + + ref->setTransform (transform); + ref->setFileInfo (file); + } break; + + default: + break; + } + + if (obj->isColored()) + obj->setColor (dlg.colnum); + + if (newObject) + { + int idx = g_win->getInsertionPoint(); + getCurrentDocument()->insertObj (idx, obj); + } + + g_win->refresh(); +}
--- a/src/addObjectDialog.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,412 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QGridLayout> -#include <QCheckBox> -#include <QDialogButtonBox> -#include <QSpinBox> -#include <QLabel> -#include <QListWidget> -#include <QTreeWidget> -#include <QLineEdit> -#include <QPushButton> -#include "gui.h" -#include "addObjectDialog.h" -#include "file.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 (Primitive*, primInfo, setPrimInfo) - - public: - SubfileListItem (QTreeWidgetItem* parent, Primitive* info) : - QTreeWidgetItem (parent), m_primInfo (info) {} - SubfileListItem (QTreeWidget* parent, Primitive* info) : - QTreeWidgetItem (parent), m_primInfo (info) {} -}; - -// ============================================================================= -// ----------------------------------------------------------------------------- -AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) : QDialog (parent) -{ setlocale (LC_ALL, "C"); - - short coordCount = 0; - str typeName = LDObject::typeName (type); - - switch (type) - { case LDObject::Comment: - le_comment = new QLineEdit; - - if (obj) - le_comment->setText (static_cast<LDComment*> (obj)->text); - - le_comment->setMinimumWidth (384); - break; - - case LDObject::Line: - coordCount = 6; - break; - - case LDObject::Triangle: - coordCount = 9; - break; - - case LDObject::Quad: - case LDObject::CndLine: - coordCount = 12; - break; - - case LDObject::Vertex: - coordCount = 3; - break; - - case LDObject::BFC: - rb_bfcType = new RadioGroup ("Statement", {}, 0, Qt::Vertical); - - for (int i = 0; i < LDBFC::NumStatements; ++i) - { // Separate these in two columns - if (i == LDBFC::NumStatements / 2) - rb_bfcType->rowBreak(); - - rb_bfcType->addButton (LDBFC::statements[i]); - } - - if (obj) - rb_bfcType->setValue ( (int) static_cast<LDBFC*> (obj)->type); - - break; - - case LDObject::Subfile: - coordCount = 3; - - // If the primitive lister is busy writing data, we have to wait - // for that to happen first. This should be quite considerably rare. - while (primitiveLoaderBusy()) - ; - - tw_subfileList = new QTreeWidget(); - tw_subfileList->setHeaderLabel (tr ("Primitives")); - - for (PrimitiveCategory & cat : g_PrimitiveCategories) - { SubfileListItem* parentItem = new SubfileListItem (tw_subfileList, null); - parentItem->setText (0, cat.name()); - QList<QTreeWidgetItem*> subfileItems; - - for (Primitive & prim : cat.prims) - { SubfileListItem* item = new SubfileListItem (parentItem, &prim); - item->setText (0, fmt ("%1 - %2", prim.name, prim.title)); - subfileItems << item; - - // If this primitive is the one the current object points to, - // select it by default - if (obj && static_cast<LDSubfile*> (obj)->fileInfo()->name() == prim.name) - tw_subfileList->setCurrentItem (item); - } - - tw_subfileList->addTopLevelItem (parentItem); - } - - connect (tw_subfileList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_subfileTypeChanged())); - lb_subfileName = new QLabel ("File:"); - le_subfileName = new QLineEdit; - le_subfileName->setFocus(); - - if (obj) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - le_subfileName->setText (ref->fileInfo()->name()); - } - - break; - - default: - critical (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->color(); - else - colnum = (type == LDObject::CndLine || type == LDObject::Line) ? edgecolor : maincolor; - - pb_color = new QPushButton; - setButtonBackground (pb_color, colnum); - connect (pb_color, SIGNAL (clicked()), this, SLOT (slot_colorButtonClicked())); - } - - for (short i = 0; i < coordCount; ++i) - { dsb_coords[i] = new QDoubleSpinBox; - dsb_coords[i]->setDecimals (5); - dsb_coords[i]->setMinimum (-10000.0); - dsb_coords[i]->setMaximum (10000.0); - } - - QGridLayout* const layout = new QGridLayout; - layout->addWidget (lb_typeIcon, 0, 0); - - switch (type) - { case LDObject::Line: - case LDObject::CndLine: - case LDObject::Triangle: - case LDObject::Quad: - - // Apply coordinates - if (obj) - { for (short i = 0; i < coordCount / 3; ++i) - for (short j = 0; j < 3; ++j) - dsb_coords[ (i * 3) + j]->setValue (obj->getVertex (i).coord (j)); - } - - break; - - case LDObject::Comment: - layout->addWidget (le_comment, 0, 1); - break; - - case LDObject::BFC: - layout->addWidget (rb_bfcType, 0, 1); - break; - - case LDObject::Subfile: - layout->addWidget (tw_subfileList, 1, 1, 1, 2); - layout->addWidget (lb_subfileName, 2, 1); - layout->addWidget (le_subfileName, 2, 2); - break; - - default: - break; - } - - if (defaults->hasMatrix()) - { LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj); - - QLabel* lb_matrix = new QLabel ("Matrix:"); - le_matrix = new QLineEdit; - // le_matrix->setValidator (new QDoubleValidator); - matrix defaultMatrix = g_identity; - - if (mo) - { for (const Axis ax : g_Axes) - dsb_coords[ax]->setValue (mo->position() [ax]); - - defaultMatrix = mo->transform(); - } - - le_matrix->setText (defaultMatrix.stringRep()); - layout->addWidget (lb_matrix, 4, 1); - layout->addWidget (le_matrix, 4, 2, 1, 3); - } - - if (defaults->isColored()) - layout->addWidget (pb_color, 1, 0); - - if (coordCount > 0) - { QGridLayout* const qCoordLayout = new QGridLayout; - - for (short i = 0; i < coordCount; ++i) - qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3)); - - layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3); - } - - QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - QWidget::connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept())); - QWidget::connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); - layout->addWidget (bbx_buttons, 5, 0, 1, 4); - setLayout (layout); - setWindowTitle (fmt (tr ("Edit %1"), typeName)); - - setWindowIcon (icon); - delete defaults; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddObjectDialog::setButtonBackground (QPushButton* button, short colnum) -{ LDColor* col = getColor (colnum); - - button->setIcon (getIcon ("palette")); - button->setAutoFillBackground (true); - - if (col) - button->setStyleSheet (fmt ("background-color: %1", col->hexcode)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str AddObjectDialog::currentSubfileName() -{ SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem()); - - if (item->primInfo() == null) - return ""; // selected a heading - - return item->primInfo()->name; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddObjectDialog::slot_colorButtonClicked() -{ ColorSelector::getColor (colnum, colnum, this); - setButtonBackground (pb_color, colnum); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddObjectDialog::slot_subfileTypeChanged() -{ str name = currentSubfileName(); - - if (name.length() > 0) - le_subfileName->setText (name); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -template<class T> static T* initObj (LDObject*& obj) -{ if (obj == null) - obj = new T; - - return static_cast<T*> (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddObjectDialog::staticDialog (const LDObject::Type type, LDObject* obj) -{ setlocale (LC_ALL, "C"); - - // FIXME: Redirect to Edit Raw - if (obj && obj->getType() == LDObject::Error) - return; - - if (type == LDObject::Empty) - return; // Nothing to edit with empties - - const bool newObject = (obj == null); - matrix transform = g_identity; - AddObjectDialog dlg (type, obj); - - assert (!obj || obj->getType() == type); - - if (dlg.exec() == false) - return; - - if (type == LDObject::Subfile) - { QStringList matrixstrvals = dlg.le_matrix->text().split (" ", QString::SkipEmptyParts); - - if (matrixstrvals.size() == 9) - { double matrixvals[9]; - int i = 0; - - for (str val : matrixstrvals) - matrixvals[i++] = val.toFloat(); - - transform = matrix (matrixvals); - } - } - - switch (type) - { case LDObject::Comment: - { LDComment* comm = initObj<LDComment> (obj); - comm->text = dlg.le_comment->text(); - } - break; - - case LDObject::Line: - case LDObject::Triangle: - case LDObject::Quad: - case LDObject::CndLine: - - if (!obj) - obj = LDObject::getDefault (type); - - for (short i = 0; i < obj->vertices(); ++i) - { vertex v; - - for (const Axis ax : g_Axes) - v[ax] = dlg.dsb_coords[ (i * 3) + ax]->value(); - - obj->setVertex (i, v); - } - - break; - - case LDObject::BFC: - { LDBFC* bfc = initObj<LDBFC> (obj); - bfc->type = (LDBFC::Type) dlg.rb_bfcType->value(); - } - break; - - case LDObject::Vertex: - { LDVertex* vert = initObj<LDVertex> (obj); - - for (const Axis ax : g_Axes) - vert->pos[ax] = dlg.dsb_coords[ax]->value(); - } - break; - - case LDObject::Subfile: - { str name = dlg.le_subfileName->text(); - - if (name.length() == 0) - return; // no subfile filename - - LDFile* file = getFile (name); - - if (!file) - { critical (fmt ("Couldn't open `%1': %2", name, strerror (errno))); - return; - } - - LDSubfile* ref = initObj<LDSubfile> (obj); - - for (const Axis ax : g_Axes) - ref->setCoordinate (ax, dlg.dsb_coords[ax]->value()); - - ref->setTransform (transform); - ref->setFileInfo (file); - } - break; - - default: - break; - } - - if (obj->isColored()) - obj->setColor (dlg.colnum); - - if (newObject) - { int idx = g_win->getInsertionPoint(); - LDFile::current()->insertObj (idx, obj); - } - - g_win->fullRefresh(); -}
--- a/src/addObjectDialog.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/addObjectDialog.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -32,7 +32,8 @@ class QDoubleSpinBox; class AddObjectDialog : public QDialog -{ Q_OBJECT +{ + Q_OBJECT public: AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent = null); @@ -59,10 +60,10 @@ QLineEdit* le_matrix; private: - void setButtonBackground (QPushButton* button, short color); - str currentSubfileName(); + void setButtonBackground (QPushButton* button, int color); + QString currentSubfileName(); - short colnum; + int colnum; private slots: void slot_colorButtonClicked();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colorSelectDialog.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + * ===================================================================== + * + * colorSelectDialog.cpp: Color selector box. + */ + +#include <QGraphicsScene> +#include <QGraphicsItem> +#include <QMouseEvent> +#include <QScrollBar> + +#include "main.h" +#include "gui.h" +#include "colorSelectDialog.h" +#include "colors.h" +#include "config.h" +#include "misc.h" +#include "ui_colorsel.h" +#include "moc_colorSelectDialog.cpp" + +static const int g_numColumns = 16; +static const int g_squareSize = 32; + +extern_cfg (String, gl_maincolor); +extern_cfg (Float, gl_maincolor_alpha); + +// ============================================================================= +// ----------------------------------------------------------------------------- +ColorSelector::ColorSelector (int defval, QWidget* parent) : QDialog (parent) +{ + // Remove the default color if it's invalid + if (!getColor (defval)) + defval = -1; + + m_firstResize = true; + ui = new Ui_ColorSelUI; + ui->setupUi (this); + + m_scene = new QGraphicsScene; + ui->viewport->setScene (m_scene); + setSelection (getColor (defval)); + + // not really an icon but eh + m_scene->setBackgroundBrush (getIcon ("checkerboard")); + drawScene(); + + int width = viewportWidth(); + ui->viewport->setMinimumWidth (width); + ui->viewport->setMaximumWidth (width); + + drawColorInfo(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +ColorSelector::~ColorSelector() +{ + delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ColorSelector::drawScene() +{ + const int numCols = g_numColumns; + const int square = g_squareSize; + const int g_maxHeight = (numRows() * square); + QRect sceneRect (0, 0, viewportWidth(), g_maxHeight); + + m_scene->setSceneRect (sceneRect); + ui->viewport->setSceneRect (sceneRect); + + const double penWidth = 1.0f; + + // Draw the color rectangles. + m_scene->clear(); + + for (int i = 0; i < MAX_COLORS; ++i) + { + LDColor* info = ::getColor (i); + + if (!info) + continue; + + const double x = (i % numCols) * square; + const double y = (i / numCols) * square; + const double w = square - (penWidth / 2); + + QColor col = info->faceColor; + + if (i == maincolor) + { + // Use the user preferences for main color here + col = QColor (gl_maincolor); + col.setAlpha (gl_maincolor_alpha * 255.0f); + } + + QPen pen (info->edgeColor, penWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + m_scene->addRect (x, y, w, w, pen, col); + QGraphicsTextItem* numtext = m_scene->addText (fmt ("%1", i)); + numtext->setDefaultTextColor ( (luma (col) < 80) ? Qt::white : Qt::black); + numtext->setPos (x, y); + + if (getSelection() && i == getSelection()->index) + { + auto curspic = m_scene->addPixmap (getIcon ("colorcursor")); + curspic->setPos (x, y); + } + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int ColorSelector::numRows() const +{ + return (MAX_COLORS / g_numColumns); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int ColorSelector::viewportWidth() const +{ + return g_numColumns * g_squareSize + 21; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ColorSelector::drawColorInfo() +{ + if (!getSelection()) + { + ui->colorLabel->setText ("---"); + return; + } + + ui->colorLabel->setText (fmt ("%1 - %2", getSelection()->index, getSelection()->name)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ColorSelector::resizeEvent (QResizeEvent* ev) +{ + // If this is the first resize, check if we need to scroll down to see the + // currently selected color. We cannot do this in the constructor because the + // height is not set properly there. + if (m_firstResize) + { + int visibleColors = (ui->viewport->height() / g_squareSize) * g_numColumns; + + if (getSelection() && getSelection()->index >= visibleColors) + { + int y = (getSelection()->index / g_numColumns) * g_squareSize; + ui->viewport->verticalScrollBar()->setValue (y); + } + + m_firstResize = false; + } + + (void) ev; + drawScene(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ColorSelector::mousePressEvent (QMouseEvent* event) +{ + QPointF scenepos = ui->viewport->mapToScene (event->pos()); + + int x = (scenepos.x() - (g_squareSize / 2)) / g_squareSize; + int y = (scenepos.y() - (g_squareSize / 2)) / g_squareSize; + int idx = (y * g_numColumns) + x; + + LDColor* col = ::getColor (idx); + + if (!col) + return; + + setSelection (col); + drawScene(); + drawColorInfo(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool ColorSelector::selectColor (int& val, int defval, QWidget* parent) +{ + ColorSelector dlg (defval, parent); + + if (dlg.exec() && dlg.getSelection() != null) + { + val = dlg.getSelection()->index; + return true; + } + + return false; +} \ No newline at end of file
--- a/src/colorSelectDialog.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,195 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * ===================================================================== - * - * colorSelectDialog.cpp: Color selector box. - */ - -#include <QGraphicsScene> -#include <QGraphicsItem> -#include <QMouseEvent> -#include <QScrollBar> - -#include "common.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 short g_squareSize = 32; - -extern_cfg (String, gl_maincolor); -extern_cfg (Float, gl_maincolor_alpha); - -// ============================================================================= -// ----------------------------------------------------------------------------- -ColorSelector::ColorSelector (short 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 (short 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 (sel() && i == sel()->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 (!sel()) - { ui->colorLabel->setText ("---"); - return; - } - - ui->colorLabel->setText (fmt ("%1 - %2", sel()->index, sel()->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 (sel() && sel()->index >= visibleColors) - { int y = (sel()->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::getColor (short& val, short int defval, QWidget* parent) -{ ColorSelector dlg (defval, parent); - - if (dlg.exec() && dlg.sel() != null) - { val = dlg.sel()->index; - return true; - } - - return false; -} \ No newline at end of file
--- a/src/colorSelectDialog.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/colorSelectDialog.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -20,20 +20,21 @@ #define LDFORGE_COLORSELECTOR_H #include <QDialog> -#include "common.h" +#include "main.h" class LDColor; class Ui_ColorSelUI; class QGraphicsScene; class ColorSelector : public QDialog -{ Q_OBJECT - READ_PROPERTY (LDColor*, sel, setSelection) +{ + Q_OBJECT + PROPERTY (private, LDColor*, Selection, NO_OPS, STOCK_WRITE) public: - explicit ColorSelector (short defval = -1, QWidget* parent = null); + explicit ColorSelector (int defval = -1, QWidget* parent = null); virtual ~ColorSelector(); - static bool getColor (short& val, short defval = -1, QWidget* parent = null); + static bool selectColor (int& val, int defval = -1, QWidget* parent = null); protected: void mousePressEvent (QMouseEvent* event);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colors.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,82 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * ===================================================================== + * + * colors.cpp: LDraw color management. LDConfig.ldr parsing is not here! + * TODO: Make LDColor more full-fledged, add support for direct colors. + * TODO: g_LDColors should probably be a map. + */ + +#include "main.h" +#include "colors.h" +#include "document.h" +#include "misc.h" +#include "gui.h" +#include "ldconfig.h" +#include <QColor> + +static LDColor* g_LDColors[MAX_COLORS]; + +// ============================================================================= +// ----------------------------------------------------------------------------- +void initColors() +{ + LDColor* col; + log ("%1: initializing color information.\n", __func__); + + // Always make sure there's 16 and 24 available. They're special like that. + col = new LDColor; + col->faceColor = col->hexcode = "#AAAAAA"; + col->edgeColor = Qt::black; + g_LDColors[maincolor] = col; + + col = new LDColor; + col->faceColor = col->edgeColor = col->hexcode = "#000000"; + g_LDColors[edgecolor] = col; + + parseLDConfig(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDColor* getColor (int colnum) +{ + // Check bounds + if (colnum < 0 || colnum >= MAX_COLORS) + return null; + + return g_LDColors[colnum]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void setColor (int colnum, LDColor* col) +{ + if (colnum < 0 || colnum >= MAX_COLORS) + return; + + g_LDColors[colnum] = col; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int luma (QColor& col) +{ + return (0.2126f * col.red()) + + (0.7152f * col.green()) + + (0.0722f * col.blue()); +}
--- a/src/colors.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * ===================================================================== - * - * colors.cpp: LDraw color management. LDConfig.ldr parsing is not here! - * TODO: Make LDColor more full-fledged, add support for direct colors. - * TODO: g_LDColors should probably be a map. - */ - -#include "common.h" -#include "colors.h" -#include "file.h" -#include "misc.h" -#include "gui.h" -#include "ldconfig.h" -#include <QColor> - -static LDColor* g_LDColors[MAX_COLORS]; - -// ============================================================================= -// ----------------------------------------------------------------------------- -void initColors() -{ LDColor* col; - log ("%1: initializing color information.\n", __func__); - - // Always make sure there's 16 and 24 available. They're special like that. - col = new LDColor; - col->faceColor = col->hexcode = "#AAAAAA"; - col->edgeColor = Qt::black; - g_LDColors[maincolor] = col; - - col = new LDColor; - col->faceColor = col->edgeColor = col->hexcode = "#000000"; - g_LDColors[edgecolor] = col; - - parseLDConfig(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDColor* getColor (short colnum) -{ // Check bounds - if (colnum < 0 || colnum >= MAX_COLORS) - return null; - - return g_LDColors[colnum]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void setColor (short colnum, LDColor* col) -{ if (colnum < 0 || colnum >= MAX_COLORS) - return; - - g_LDColors[colnum] = col; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int luma (QColor& col) -{ return (0.2126f * col.red()) + - (0.7152f * col.green()) + - (0.0722f * col.blue()); -}
--- a/src/colors.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/colors.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -20,26 +20,27 @@ #define LDFORGE_COLORS_H #include <QColor> -#include "common.h" +#include "main.h" #define MAX_COLORS 512 class LDColor -{ public: - str name, hexcode; +{ + public: + QString name, hexcode; QColor faceColor, edgeColor; - short index; + int index; }; void initColors(); int luma (QColor& col); // Safely gets a color with the given number or null if no such color. -LDColor* getColor (short colnum); -void setColor (short colnum, LDColor* col); +LDColor* getColor (int colnum); +void setColor (int colnum, LDColor* col); // Main and edge color identifiers -static const short maincolor = 16; -static const short edgecolor = 24; +static const int maincolor = 16; +static const int edgecolor = 24; #endif // LDFORGE_COLORS_H
--- a/src/common.h Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,172 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -// ============================================================================= -// This file is included one way or another in every source file of LDForge. -// Stuff defined and included here is universally included. - -#ifndef LDFORGE_COMMON_H -#define LDFORGE_COMMON_H - -#include <stdio.h> -#include <stdlib.h> -#include <stdint.h> -#include <stdarg.h> -#include <QString> -#include <QMutex> - -#include "config.h" - -#define APPNAME "LDForge" -#define VERSION_MAJOR 0 -#define VERSION_MINOR 2 -#define VERSION_PATCH 999 -#define BUILD_ID BUILD_INTERNAL - -#define BUILD_INTERNAL 0 -#define BUILD_ALPHA 1 -#define BUILD_BETA 2 -#define BUILD_RC 3 -#define BUILD_RELEASE 4 - -// RC Number for BUILD_RC -#define RC_NUMBER 0 - -// ============================================= -#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 - -#define PROP_NAME(GET) m_##GET - -#define READ_ACCESSOR(T, GET) \ - T const& GET() const - -#define SET_ACCESSOR(T, SET) \ - void SET (T val) - -// Read-only, private property with a get accessor -#define DECLARE_READ_PROPERTY(T, GET, SET) \ -private: \ - T PROP_NAME (GET); \ - SET_ACCESSOR (T, SET); \ -public: \ - READ_ACCESSOR (T, GET); - -// Read/write private property with get and set accessors -#define DECLARE_PROPERTY(T, GET, SET) \ -private: \ - T PROP_NAME (GET); \ -public: \ - READ_ACCESSOR (T, GET); \ - SET_ACCESSOR (T, SET); - -#define DEFINE_PROPERTY(T, CLASS, GET, SET) \ - READ_ACCESSOR (T, CLASS::GET) { return PROP_NAME (GET); } \ - SET_ACCESSOR (T, CLASS::SET) { PROP_NAME (GET) = val; } - -// Shortcuts -#define PROPERTY(T, GET, SET) \ -private: \ - T PROP_NAME (GET); \ -public: \ - READ_ACCESSOR (T, GET) { return PROP_NAME (GET); } \ - SET_ACCESSOR (T, SET) { PROP_NAME (GET) = val; } - -#define READ_PROPERTY(T, GET, SET) \ -private: \ - T PROP_NAME (GET); \ - SET_ACCESSOR (T, SET) { PROP_NAME (GET) = val; } \ -public: \ - READ_ACCESSOR (T, GET) { return PROP_NAME (GET); } - -// Property whose set accessor is a public slot -// TODO: make this replace PROPERTY -#define SLOT_PROPERTY (T, GET, SET) \ -private: \ - T PROP_NAME (GET); \ -public: \ - READ_ACCESSOR (T, GET) { return PROP_NAME (GET); } \ -public slots: \ - SET_ACCESSOR (T, SET) { PROP_NAME (GET) = val; } - -#ifdef null -#undef null -#endif // null - -#ifdef __GNUC__ -#define FUNCNAME __PRETTY_FUNCTION__ -#else -#define FUNCNAME __func__ -#endif // __GNUC__ - -// 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); - -#ifdef assert -# undef assert -#endif // 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 versionMoniker(); -QString fullVersionString(); - -// ----------------------------------------------------------------------------- -#ifdef IN_IDE_PARSER // KDevelop workarounds: -# error IN_IDE_PARSER is defined (this code is only for KDevelop workarounds) - -# 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_COMMON_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/config.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + * ===================================================================== + * + * config.cpp: Configuration management. I don't like how unsafe QSettings + * is so this implements a type-safer and identifer-safer wrapping system of + * configuration variables. QSettings is used underlyingly, this is a matter + * of interface. + */ + +#include <errno.h> +#include <QDir> +#include <QTextStream> +#include <QSettings> +#include "main.h" +#include "config.h" +#include "misc.h" +#include "gui.h" +#include "document.h" + +#ifdef _WIN32 +# define EXTENSION ".ini" +#else +# define EXTENSION ".cfg" +#endif // _WIN32 + +Config* g_configPointers[MAX_CONFIG]; +static int g_cfgPointerCursor = 0; +static QMap<QString, Config*> g_configsByName; +static QList<Config*> g_configs; + +// ============================================================================= +// Get the QSettings object. +// ----------------------------------------------------------------------------- +static QSettings* getSettingsObject() +{ + QString path = qApp->applicationDirPath() + "/" UNIXNAME EXTENSION; + return new QSettings (path, QSettings::IniFormat); +} + +Config::Config (QString name) : + m_Name (name) {} + +// ============================================================================= +// Load the configuration from file +// ----------------------------------------------------------------------------- +bool Config::load() +{ + QSettings* settings = getSettingsObject(); + 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<class T> T* getConfigByName (QString name, Config::Type type) +{ + auto it = g_configsByName.find (name); + + if (it == g_configsByName.end()) + return null; + + Config* cfg = it.value(); + + if (cfg->getType() != type) + { + fprint (stderr, "type of %1 is %2, not %3\n", name, cfg->getType(), type); + abort(); + } + + return reinterpret_cast<T*> (cfg); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +#undef IMPLEMENT_CONFIG + +#define IMPLEMENT_CONFIG(NAME) \ + NAME##Config* NAME##Config::getByName (QString name) \ + { \ + return getConfigByName<NAME##Config> (name, E##NAME##Type); \ + } + +IMPLEMENT_CONFIG (Int) +IMPLEMENT_CONFIG (String) +IMPLEMENT_CONFIG (Bool) +IMPLEMENT_CONFIG (Float) +IMPLEMENT_CONFIG (List) +IMPLEMENT_CONFIG (KeySequence) +IMPLEMENT_CONFIG (Vertex)
--- a/src/config.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,139 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * ===================================================================== - * - * config.cpp: Configuration management. I don't like how unsafe QSettings - * is so this implements a type-safer and idenitifer-safer wrapping system of - * configuration variables. QSettings is used underlyingly, this is a matter - * of interface. - */ - -#include <errno.h> -#include <QDir> -#include <QTextStream> -#include <QSettings> -#include "common.h" -#include "config.h" -#include "misc.h" -#include "gui.h" -#include "file.h" - -Config* g_configPointers[MAX_CONFIG]; -static int g_cfgPointerCursor = 0; - -// ============================================================================= -// Get the QSettings object. A portable build refers to a file in the current -// directory, a non-portable build to ~/.config/LDForge or to registry. -// ----------------------------------------------------------------------------- -static QSettings* getSettingsObject() -{ -#ifdef PORTABLE -# ifdef _WIN32 -# define EXTENSION ".ini" -# else -# define EXTENSION ".cfg" -# endif // _WIN32 - return new QSettings (str (APPNAME).toLower() + EXTENSION, QSettings::IniFormat); -#else - return new QSettings; -#endif // PORTABLE -} - -Config::Config (const char* name, const char* defstring) : - name (name), m_defstring (defstring) {} - -// ============================================================================= -// Load the configuration from file -// ----------------------------------------------------------------------------- -bool Config::load() -{ QSettings* settings = getSettingsObject(); - log ("config::load: Loading configuration file from %1\n", settings->fileName()); - - for (Config* cfg : g_configPointers) - { if (!cfg) - break; - - QVariant val = settings->value (cfg->name, cfg->defaultVariant()); - cfg->loadFromVariant (val); - } - - settings->deleteLater(); - return true; -} - -// ============================================================================= -// Save the configuration to disk -// ----------------------------------------------------------------------------- -bool Config::save() -{ QSettings* settings = getSettingsObject(); - log ("Saving configuration to %1...\n", settings->fileName()); - - for (Config* cfg : g_configPointers) - { if (!cfg) - break; - - if (cfg->isDefault()) - continue; - - settings->setValue (cfg->name, cfg->toVariant()); - } - - settings->sync(); - settings->deleteLater(); - return true; -} - -// ============================================================================= -// Reset configuration defaults. -// ----------------------------------------------------------------------------- -void Config::reset() -{ for (Config * cfg : g_configPointers) - { if (!cfg) - break; - - cfg->resetValue(); - } -} - -// ============================================================================= -// Where is the configuration file located at? Note that the Windows build uses -// the registry so only use this with PORTABLE code. -// ----------------------------------------------------------------------------- -str Config::filepath (str file) -{ return Config::dirpath() + DIRSLASH + file; -} - -// ============================================================================= -// Directory of the configuration file. PORTABLE code here as well. -// ----------------------------------------------------------------------------- -str Config::dirpath() -{ QSettings* cfg = getSettingsObject(); - return dirname (cfg->fileName()); -} - -// ============================================================================= -// We cannot just add config objects to a list or vector because that would rely -// on the vector's c-tor being called before the configs' c-tors. With global -// variables we cannot assume that!! Therefore we need to use a C-style array here. -// ----------------------------------------------------------------------------- -void Config::addToArray (Config* ptr) -{ if (g_cfgPointerCursor == 0) - memset (g_configPointers, 0, sizeof g_configPointers); - - assert (g_cfgPointerCursor < MAX_CONFIG); - g_configPointers[g_cfgPointerCursor++] = ptr; -}
--- a/src/config.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/config.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -19,7 +19,8 @@ #ifndef LDFORGE_CONFIG_H #define LDFORGE_CONFIG_H -#include "common.h" +#include "property.h" +#include "types.h" // ============================================================================= #include <QString> @@ -27,220 +28,165 @@ #include <QKeySequence> class QSettings; -typedef QChar qchar; -typedef QString str; - #define MAX_INI_LINE 512 #define MAX_CONFIG 512 -#define cfg(T, NAME, DEFAULT) T##Config NAME (DEFAULT, #NAME, #DEFAULT) -#define extern_cfg(T, NAME) extern T##Config NAME +#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 -{ public: +{ + PROPERTY (private, QString, Name, STR_OPS, STOCK_WRITE) + + public: enum Type - { Int, - String, - Float, - Bool, - KeySequence, - List, + { + EIntType, + EStringType, + EFloatType, + EBoolType, + EKeySequenceType, + EListType, + EVertexType, }; - Config (const char* name, const char* defstring); - const char* name; - - virtual Type getType() const - { return (Type) 0; - } - - virtual void resetValue() {} - virtual void loadFromVariant (const QVariant& val) - { (void) val; - } + using IntType = int; + using StringType = QString; + using FloatType = float; + using BoolType = bool; + using KeySequenceType = QKeySequence; + using ListType = QList<QVariant>; + using VertexType = Vertex; - virtual bool isDefault() const - { return false; - } + Config (QString name); - virtual QVariant toVariant() const - { return QVariant(); - } - - virtual QVariant defaultVariant() const - { return QVariant(); - } + 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 str dirpath(); - static str filepath (str file); + static QString dirpath(); + static QString filepath (QString file); protected: static void addToArray (Config* ptr); - - private: - const char* m_defstring; }; // ============================================================================= -#define IMPLEMENT_CONFIG(NAME, T) \ - T value, defval; \ - NAME##Config (T defval, const char* name, const char* defstring) : \ - Config (name, defstring), value (defval), defval (defval) \ - { Config::addToArray (this); } \ - \ - operator const T&() const { return value; } \ - Config::Type getType() const override { return Config::NAME; } \ - virtual void resetValue() override { value = defval; } \ - virtual bool isDefault() const override { return value == defval; } \ - virtual QVariant toVariant() const override { return QVariant::fromValue<T> (value); } \ - virtual QVariant defaultVariant() const override { return QVariant::fromValue<T> (defval); } \ - virtual void loadFromVariant (const QVariant& val) override { value = val.value<T>(); } \ - -#define DEFINE_UNARY_OPERATOR(T, OP) \ - T operator OP() { \ - return OP value; \ - } - -#define DEFINE_BINARY_OPERATOR(T, OP) \ - T operator OP (const T other) { \ - return value OP other; \ - } - -#define DEFINE_ASSIGN_OPERATOR(T, OP) \ - T& operator OP (const T other) { \ - return value OP other; \ - } - -#define DEFINE_COMPARE_OPERATOR(T, OP) \ - bool operator OP (const T other) { \ - return value OP other; \ - } - -#define DEFINE_CAST_OPERATOR(T) \ - operator T() { \ - return (T) value; \ - } - -#define DEFINE_ALL_COMPARE_OPERATORS(T) \ - DEFINE_COMPARE_OPERATOR (T, ==) \ - DEFINE_COMPARE_OPERATOR (T, !=) \ - DEFINE_COMPARE_OPERATOR (T, >) \ - DEFINE_COMPARE_OPERATOR (T, <) \ - DEFINE_COMPARE_OPERATOR (T, >=) \ - DEFINE_COMPARE_OPERATOR (T, <=) \ - -#define DEFINE_INCREMENT_OPERATORS(T) \ - T operator++() { return ++value; } \ - T operator++(int) { return value++; } \ - T operator--() { return --value; } \ - T operator--(int) { return value--; } +#define IMPLEMENT_CONFIG(NAME) \ +public: \ + using ValueType = Config::NAME##Type; \ + \ + NAME##Config (ValueType* valueptr, QString name, ValueType def) : \ + Config (name), \ + m_valueptr (valueptr), \ + m_default (def) \ + { \ + Config::addToArray (this); \ + *m_valueptr = def; \ + } \ + \ + inline ValueType getValue() const \ + { \ + return *m_valueptr; \ + } \ + \ + inline void setValue (ValueType val) \ + { \ + *m_valueptr = val; \ + } \ + \ + virtual Config::Type getType() const \ + { \ + return Config::E##NAME##Type; \ + } \ + \ + virtual void resetValue() \ + { \ + *m_valueptr = m_default; \ + } \ + \ + virtual const ValueType& getDefault() const \ + { \ + return m_default; \ + } \ + \ + virtual bool isDefault() const \ + { \ + return *m_valueptr == m_default; \ + } \ + \ + virtual void loadFromVariant (const QVariant& val) \ + { \ + *m_valueptr = val.value<ValueType>(); \ + } \ + \ + virtual QVariant toVariant() const \ + { \ + return QVariant::fromValue<ValueType> (*m_valueptr); \ + } \ + \ + virtual QVariant getDefaultAsVariant() const \ + { \ + return QVariant::fromValue<ValueType> (m_default); \ + } \ + \ + static NAME##Config* getByName (QString name); \ + \ +private: \ + ValueType* m_valueptr; \ + ValueType m_default; // ============================================================================= class IntConfig : public Config -{ public: - IMPLEMENT_CONFIG (Int, int) - DEFINE_ALL_COMPARE_OPERATORS (int) - DEFINE_INCREMENT_OPERATORS (int) - DEFINE_BINARY_OPERATOR (int, +) - DEFINE_BINARY_OPERATOR (int, -) - DEFINE_BINARY_OPERATOR (int, *) - DEFINE_BINARY_OPERATOR (int, /) - DEFINE_BINARY_OPERATOR (int, %) - DEFINE_BINARY_OPERATOR (int, ^) - DEFINE_BINARY_OPERATOR (int, |) - DEFINE_BINARY_OPERATOR (int, &) - DEFINE_BINARY_OPERATOR (int, >>) - DEFINE_BINARY_OPERATOR (int, <<) - DEFINE_UNARY_OPERATOR (int, !) - DEFINE_UNARY_OPERATOR (int, ~) - DEFINE_UNARY_OPERATOR (int, -) - DEFINE_UNARY_OPERATOR (int, +) - DEFINE_ASSIGN_OPERATOR (int, =) - DEFINE_ASSIGN_OPERATOR (int, +=) - DEFINE_ASSIGN_OPERATOR (int, -=) - DEFINE_ASSIGN_OPERATOR (int, *=) - DEFINE_ASSIGN_OPERATOR (int, /=) - DEFINE_ASSIGN_OPERATOR (int, %=) - DEFINE_ASSIGN_OPERATOR (int, >>=) - DEFINE_ASSIGN_OPERATOR (int, <<=) +{ + IMPLEMENT_CONFIG (Int) }; // ============================================================================= class StringConfig : public Config -{ public: - IMPLEMENT_CONFIG (String, str) - - DEFINE_COMPARE_OPERATOR (str, ==) - DEFINE_COMPARE_OPERATOR (str, !=) - DEFINE_ASSIGN_OPERATOR (str, =) - DEFINE_ASSIGN_OPERATOR (str, +=) - - QChar operator[] (int n) - { return value[n]; - } +{ + IMPLEMENT_CONFIG (String) }; // ============================================================================= class FloatConfig : public Config -{ public: - IMPLEMENT_CONFIG (Float, float) - DEFINE_ALL_COMPARE_OPERATORS (float) - DEFINE_INCREMENT_OPERATORS (float) - DEFINE_BINARY_OPERATOR (float, +) - DEFINE_BINARY_OPERATOR (float, -) - DEFINE_BINARY_OPERATOR (float, *) - DEFINE_UNARY_OPERATOR (float, !) - DEFINE_ASSIGN_OPERATOR (float, =) - DEFINE_ASSIGN_OPERATOR (float, +=) - DEFINE_ASSIGN_OPERATOR (float, -=) - DEFINE_ASSIGN_OPERATOR (float, *=) +{ + IMPLEMENT_CONFIG (Float) }; // ============================================================================= class BoolConfig : public Config -{ public: - IMPLEMENT_CONFIG (Bool, bool) - DEFINE_ALL_COMPARE_OPERATORS (bool) - DEFINE_ASSIGN_OPERATOR (bool, =) +{ + IMPLEMENT_CONFIG (Bool) }; // ============================================================================= class KeySequenceConfig : public Config -{ public: - IMPLEMENT_CONFIG (KeySequence, QKeySequence) - DEFINE_ALL_COMPARE_OPERATORS (QKeySequence) - DEFINE_ASSIGN_OPERATOR (QKeySequence, =) +{ + IMPLEMENT_CONFIG (KeySequence) }; // ============================================================================= class ListConfig : public Config -{ public: - IMPLEMENT_CONFIG (List, QList<QVariant>) - DEFINE_ASSIGN_OPERATOR (QList<QVariant>, =) - - typedef QList<QVariant>::iterator it; - typedef QList<QVariant>::const_iterator c_it; - - it begin() - { return value.begin(); - } +{ + IMPLEMENT_CONFIG (List) +}; - c_it begin() const - { return value.constBegin(); - } - - it end() - { return value.end(); - } - - c_it end() const - { return value.constEnd(); - } +// ============================================================================= +class VertexConfig : public Config +{ + IMPLEMENT_CONFIG (Vertex) }; #endif // LDFORGE_CONFIG_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/configDialog.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + * ===================================================================== + * + * configDialog.cpp: Settings dialog and everything related to it. + * Actual configuration core is in config.cpp. + */ + +#include <QGridLayout> +#include <QFileDialog> +#include <QColorDialog> +#include <QBoxLayout> +#include <QKeyEvent> +#include <QGroupBox> +#include <QDoubleSpinBox> +#include <QLineEdit> +#include <QCheckBox> +#include "main.h" +#include "configDialog.h" +#include "document.h" +#include "config.h" +#include "misc.h" +#include "colors.h" +#include "colorSelectDialog.h" +#include "gldraw.h" +#include "ui_config.h" +#include "moc_configDialog.cpp" + +extern_cfg (String, gl_bgcolor); +extern_cfg (String, gl_maincolor); +extern_cfg (Bool, lv_colorize); +extern_cfg (Bool, gl_colorbfc); +extern_cfg (Float, gl_maincolor_alpha); +extern_cfg (Int, gl_linethickness); +extern_cfg (String, gui_colortoolbar); +extern_cfg (Bool, edit_schemanticinline); +extern_cfg (Bool, gl_blackedges); +extern_cfg (Bool, gl_aa); +extern_cfg (Bool, gui_implicitfiles); +extern_cfg (String, net_downloadpath); +extern_cfg (Bool, net_guesspaths); +extern_cfg (Bool, net_autoclose); +extern_cfg (Bool, gl_logostuds); +extern_cfg (Bool, gl_linelengths); +extern_cfg (String, ld_defaultname); +extern_cfg (String, ld_defaultuser); +extern_cfg (Int, ld_defaultlicense); +extern_cfg (String, gl_selectcolor); +extern_cfg (String, prog_ytruder); +extern_cfg (String, prog_rectifier); +extern_cfg (String, prog_intersector); +extern_cfg (String, prog_coverer); +extern_cfg (String, prog_isecalc); +extern_cfg (String, prog_edger2); +extern_cfg (Bool, prog_ytruder_wine); +extern_cfg (Bool, prog_rectifier_wine); +extern_cfg (Bool, prog_intersector_wine); +extern_cfg (Bool, prog_coverer_wine); +extern_cfg (Bool, prog_isecalc_wine); +extern_cfg (Bool, prog_edger2_wine); + +const char* g_extProgPathFilter = +#ifdef _WIN32 + "Applications (*.exe)(*.exe);;All files (*.*)(*.*)"; +#else + ""; +#endif + +// ============================================================================= +// ----------------------------------------------------------------------------- +ConfigDialog::ConfigDialog (ConfigDialog::Tab deftab, QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f) +{ + assert (g_win != null); + ui = new Ui_ConfigUI; + ui->setupUi (this); + + // Interface tab + setButtonBackground (ui->backgroundColorButton, gl_bgcolor); + connect (ui->backgroundColorButton, SIGNAL (clicked()), + this, SLOT (slot_setGLBackground())); + + setButtonBackground (ui->mainColorButton, gl_maincolor); + connect (ui->mainColorButton, SIGNAL (clicked()), + this, SLOT (slot_setGLForeground())); + + setButtonBackground (ui->selColorButton, gl_selectcolor); + connect (ui->selColorButton, SIGNAL (clicked()), + this, SLOT (slot_setGLSelectColor())); + + ui->mainColorAlpha->setValue (gl_maincolor_alpha * 10.0f); + ui->lineThickness->setValue (gl_linethickness); + ui->colorizeObjects->setChecked (lv_colorize); + ui->colorBFC->setChecked (gl_colorbfc); + ui->blackEdges->setChecked (gl_blackedges); + ui->m_aa->setChecked (gl_aa); + ui->implicitFiles->setChecked (gui_implicitfiles); + ui->m_logostuds->setChecked (gl_logostuds); + ui->linelengths->setChecked (gl_linelengths); + + int i = 0; + + for (QAction* act : g_win->findChildren<QAction*>()) + { + KeySequenceConfig* cfg = g_win->shortcutForAction (act); + + if (cfg) + addShortcut (*cfg, act, i); + } + + ui->shortcutsList->setSortingEnabled (true); + ui->shortcutsList->sortItems(); + + connect (ui->shortcut_set, SIGNAL (clicked()), this, SLOT (slot_setShortcut())); + connect (ui->shortcut_reset, SIGNAL (clicked()), this, SLOT (slot_resetShortcut())); + connect (ui->shortcut_clear, SIGNAL (clicked()), this, SLOT (slot_clearShortcut())); + + quickColors = quickColorsFromConfig(); + updateQuickColorList(); + + connect (ui->quickColor_add, SIGNAL (clicked()), this, SLOT (slot_setColor())); + connect (ui->quickColor_remove, SIGNAL (clicked()), this, SLOT (slot_delColor())); + connect (ui->quickColor_edit, SIGNAL (clicked()), this, SLOT (slot_setColor())); + connect (ui->quickColor_addSep, SIGNAL (clicked()), this, SLOT (slot_addColorSeparator())); + connect (ui->quickColor_moveUp, SIGNAL (clicked()), this, SLOT (slot_moveColor())); + connect (ui->quickColor_moveDown, SIGNAL (clicked()), this, SLOT (slot_moveColor())); + connect (ui->quickColor_clear, SIGNAL (clicked()), this, SLOT (slot_clearColors())); + + ui->downloadPath->setText (net_downloadpath); + ui->guessNetPaths->setChecked (net_guesspaths); + ui->autoCloseNetPrompt->setChecked (net_autoclose); + connect (ui->findDownloadPath, SIGNAL (clicked (bool)), this, SLOT (slot_findDownloadFolder())); + + ui->m_profileName->setText (ld_defaultname); + ui->m_profileUsername->setText (ld_defaultuser); + ui->m_profileLicense->setCurrentIndex (ld_defaultlicense); + + initGrids(); + initExtProgs(); + selectPage (deftab); + + connect (ui->buttonBox, SIGNAL (clicked (QAbstractButton*)), + this, SLOT (buttonClicked (QAbstractButton*))); + + connect (ui->m_pages, SIGNAL (currentChanged (int)), + this, SLOT (selectPage (int))); + + connect (ui->m_pagelist, SIGNAL (currentRowChanged (int)), + this, SLOT (selectPage (int))); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +ConfigDialog::~ConfigDialog() +{ + delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ConfigDialog::selectPage (int row) +{ + ui->m_pagelist->setCurrentRow (row); + ui->m_pages->setCurrentIndex (row); +} + +// ============================================================================= +// Adds a shortcut entry to the list of shortcuts. +// ----------------------------------------------------------------------------- +void ConfigDialog::addShortcut (KeySequenceConfig& cfg, QAction* act, int& i) +{ + ShortcutListItem* item = new ShortcutListItem; + item->setIcon (act->icon()); + item->setKeyConfig (&cfg); + item->setAction (act); + setShortcutText (item); + + // If the action doesn't have a valid icon, use an empty one + // so that the list is kept aligned. + if (act->icon().isNull()) + item->setIcon (getIcon ("empty")); + + ui->shortcutsList->insertItem (i++, item); +} + +// ============================================================================= +// Initializes the table of grid stuff +// ----------------------------------------------------------------------------- +void ConfigDialog::initGrids() +{ + QGridLayout* gridlayout = new QGridLayout; + QLabel* xlabel = new QLabel ("X"), + *ylabel = new QLabel ("Y"), + *zlabel = new QLabel ("Z"), + *anglabel = new QLabel ("Angle"); + int i = 1; + + for (QLabel* label : initlist<QLabel*> ({xlabel, ylabel, zlabel, anglabel})) + { + label->setAlignment (Qt::AlignCenter); + gridlayout->addWidget (label, 0, i++); + } + + for (int i = 0; i < g_NumGrids; ++i) + { + // Icon + lb_gridIcons[i] = new QLabel; + lb_gridIcons[i]->setPixmap (getIcon (fmt ("grid-%1", 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<QPushButton*> (sender()) == ui->quickColor_add; + + if (isNew == false) + { + item = getSelectedQuickColor(); + + if (!item) + return; + + int i = getItemRow (item, quickColorItems); + entry = &quickColors[i]; + + if (entry->isSeparator() == true) + return; // don't color separators + } + + int defval = entry ? entry->getColor()->index : -1; + int val; + + if (ColorSelector::selectColor (val, defval, this) == false) + return; + + if (entry) + entry->setColor (getColor (val)); + else + { + LDQuickColor entry (getColor (val), null); + + item = getSelectedQuickColor(); + int idx = (item) ? getItemRow (item, quickColorItems) + 1 : quickColorItems.size(); + + quickColors.insert (idx, entry); + entry = quickColors[idx]; + } + + updateQuickColorList (entry); +} + +// ============================================================================= +// Remove a quick color +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_delColor() +{ + if (ui->quickColorList->selectedItems().isEmpty()) + return; + + QListWidgetItem* item = ui->quickColorList->selectedItems() [0]; + quickColors.removeAt (getItemRow (item, quickColorItems)); + updateQuickColorList(); +} + +// ============================================================================= +// Move a quick color up/down +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_moveColor() +{ + const bool up = (static_cast<QPushButton*> (sender()) == ui->quickColor_moveUp); + + if (ui->quickColorList->selectedItems().isEmpty()) + return; + + QListWidgetItem* item = ui->quickColorList->selectedItems() [0]; + int idx = getItemRow (item, quickColorItems); + int dest = up ? (idx - 1) : (idx + 1); + + if (dest < 0 || dest >= quickColorItems.size()) + return; // destination out of bounds + + LDQuickColor tmp = quickColors[dest]; + quickColors[dest] = quickColors[idx]; + quickColors[idx] = tmp; + + updateQuickColorList (&quickColors[dest]); +} + +// ============================================================================= +// Add a separator to quick colors +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_addColorSeparator() +{ + quickColors << LDQuickColor::getSeparator(); + updateQuickColorList (&quickColors[quickColors.size() - 1]); +} + +// ============================================================================= +// Clear all quick colors +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_clearColors() +{ + quickColors.clear(); + updateQuickColorList(); +} + +// ============================================================================= +// Pick a color and set the appropriate configuration option. +// ----------------------------------------------------------------------------- +void ConfigDialog::pickColor (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<QListWidgetItem*>& haystack) +{ + int i = 0; + + for (QListWidgetItem* it : haystack) + { + if (it == item) + return i; + + ++i; + } + + return -1; +} + +// ============================================================================= +// Which quick color is currently selected? +// ----------------------------------------------------------------------------- +QListWidgetItem* ConfigDialog::getSelectedQuickColor() +{ + if (ui->quickColorList->selectedItems().isEmpty()) + return null; + + return ui->quickColorList->selectedItems() [0]; +} + +// ============================================================================= +// Get the list of shortcuts selected +// ----------------------------------------------------------------------------- +QList<ShortcutListItem*> ConfigDialog::getShortcutSelection() +{ + QList<ShortcutListItem*> out; + + for (QListWidgetItem* entry : ui->shortcutsList->selectedItems()) + out << static_cast<ShortcutListItem*> (entry); + + return out; +} + +// ============================================================================= +// Edit the shortcut of a given action. +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_setShortcut() +{ + QList<ShortcutListItem*> sel = getShortcutSelection(); + + if (sel.size() < 1) + return; + + ShortcutListItem* item = sel[0]; + + if (KeySequenceDialog::staticDialog (item->getKeyConfig(), this)) + setShortcutText (item); +} + +// ============================================================================= +// Reset a shortcut to defaults +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_resetShortcut() +{ + QList<ShortcutListItem*> sel = getShortcutSelection(); + + for (ShortcutListItem* item : sel) + { + item->getKeyConfig()->reset(); + setShortcutText (item); + } +} + +// ============================================================================= +// Remove the shortcut of an action. +// ----------------------------------------------------------------------------- +void ConfigDialog::slot_clearShortcut() +{ + QList<ShortcutListItem*> sel = getShortcutSelection(); + + for (ShortcutListItem* item : sel) + { + item->getKeyConfig()->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 ("<center><b>%1</b></center>", shortcut); + lb_output->setText (text); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) +{ + seq = ev->key() + ev->modifiers(); + updateOutput(); +}
--- a/src/configDialog.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,719 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * ===================================================================== - * - * configDialog.cpp: Settings dialog and everything related to it. - * Actual configuration core is in config.cpp. - */ - -#include <QGridLayout> -#include <QFileDialog> -#include <QColorDialog> -#include <QBoxLayout> -#include <QKeyEvent> -#include <QGroupBox> -#include <QDoubleSpinBox> -#include <QLineEdit> -#include <QCheckBox> -#include "common.h" -#include "configDialog.h" -#include "file.h" -#include "config.h" -#include "misc.h" -#include "colors.h" -#include "colorSelectDialog.h" -#include "gldraw.h" -#include "ui_config.h" -#include "moc_configDialog.cpp" - -extern_cfg (String, gl_bgcolor); -extern_cfg (String, gl_maincolor); -extern_cfg (Bool, lv_colorize); -extern_cfg (Bool, gl_colorbfc); -extern_cfg (Float, gl_maincolor_alpha); -extern_cfg (Int, gl_linethickness); -extern_cfg (String, gui_colortoolbar); -extern_cfg (Bool, edit_schemanticinline); -extern_cfg (Bool, gl_blackedges); -extern_cfg (Bool, gl_aa); -extern_cfg (Bool, gui_implicitfiles); -extern_cfg (String, net_downloadpath); -extern_cfg (Bool, net_guesspaths); -extern_cfg (Bool, net_autoclose); -extern_cfg (Bool, gl_logostuds); -extern_cfg (String, ld_defaultname); -extern_cfg (String, ld_defaultuser); -extern_cfg (Int, ld_defaultlicense); -extern_cfg (String, prog_ytruder); -extern_cfg (String, prog_rectifier); -extern_cfg (String, prog_intersector); -extern_cfg (String, prog_coverer); -extern_cfg (String, prog_isecalc); -extern_cfg (String, prog_edger2); -extern_cfg (Bool, prog_ytruder_wine); -extern_cfg (Bool, prog_rectifier_wine); -extern_cfg (Bool, prog_intersector_wine); -extern_cfg (Bool, prog_coverer_wine); -extern_cfg (Bool, prog_isecalc_wine); -extern_cfg (Bool, prog_edger2_wine); - -#define act(N) extern_cfg (KeySequence, key_##N); -#include "actions.h" - -const char* g_extProgPathFilter = -#ifdef _WIN32 - "Applications (*.exe)(*.exe);;All files (*.*)(*.*)"; -#else - ""; -#endif - -// ============================================================================= -// ----------------------------------------------------------------------------- -ConfigDialog::ConfigDialog (ConfigDialog::Tab deftab, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f) -{ - assert (g_win != null); - ui = new Ui_ConfigUI; - ui->setupUi (this); - - // Interface tab - setButtonBackground (ui->backgroundColorButton, gl_bgcolor); - connect (ui->backgroundColorButton, SIGNAL (clicked()), - this, SLOT (slot_setGLBackground())); - - setButtonBackground (ui->mainColorButton, gl_maincolor); - connect (ui->mainColorButton, SIGNAL (clicked()), - this, SLOT (slot_setGLForeground())); - - ui->mainColorAlpha->setValue (gl_maincolor_alpha * 10.0f); - ui->lineThickness->setValue (gl_linethickness); - ui->colorizeObjects->setChecked (lv_colorize); - ui->colorBFC->setChecked (gl_colorbfc); - ui->blackEdges->setChecked (gl_blackedges); - ui->m_aa->setChecked (gl_aa); - ui->implicitFiles->setChecked (gui_implicitfiles); - ui->m_logostuds->setChecked (gl_logostuds); - - int i = 0; -#define act(N) addShortcut (key_##N, ACTION(N), i); -#include "actions.h" - - ui->shortcutsList->setSortingEnabled (true); - ui->shortcutsList->sortItems(); - - connect (ui->shortcut_set, SIGNAL (clicked()), this, SLOT (slot_setShortcut())); - connect (ui->shortcut_reset, SIGNAL (clicked()), this, SLOT (slot_resetShortcut())); - connect (ui->shortcut_clear, SIGNAL (clicked()), this, SLOT (slot_clearShortcut())); - - quickColors = quickColorsFromConfig(); - updateQuickColorList(); - - connect (ui->quickColor_add, SIGNAL (clicked()), this, SLOT (slot_setColor())); - connect (ui->quickColor_remove, SIGNAL (clicked()), this, SLOT (slot_delColor())); - connect (ui->quickColor_edit, SIGNAL (clicked()), this, SLOT (slot_setColor())); - connect (ui->quickColor_addSep, SIGNAL (clicked()), this, SLOT (slot_addColorSeparator())); - connect (ui->quickColor_moveUp, SIGNAL (clicked()), this, SLOT (slot_moveColor())); - connect (ui->quickColor_moveDown, SIGNAL (clicked()), this, SLOT (slot_moveColor())); - connect (ui->quickColor_clear, SIGNAL (clicked()), this, SLOT (slot_clearColors())); - - ui->downloadPath->setText (net_downloadpath); - ui->guessNetPaths->setChecked (net_guesspaths); - ui->autoCloseNetPrompt->setChecked (net_autoclose); - connect (ui->findDownloadPath, SIGNAL (clicked (bool)), this, SLOT (slot_findDownloadFolder())); - - ui->m_profileName->setText (ld_defaultname); - ui->m_profileUsername->setText (ld_defaultuser); - ui->m_profileLicense->setCurrentIndex (ld_defaultlicense); - - initGrids(); - initExtProgs(); - selectPage (deftab); - - connect (ui->buttonBox, SIGNAL (clicked (QAbstractButton*)), - this, SLOT (buttonClicked (QAbstractButton*))); - - connect (ui->m_pages, SIGNAL (currentChanged (int)), - this, SLOT (selectPage (int))); - - connect (ui->m_pagelist, SIGNAL (currentRowChanged (int)), - this, SLOT (selectPage (int))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -ConfigDialog::~ConfigDialog() -{ delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ConfigDialog::selectPage (int row) -{ ui->m_pagelist->setCurrentRow (row); - ui->m_pages->setCurrentIndex (row); -} - -// ============================================================================= -// Adds a shortcut entry to the list of shortcuts. -// ----------------------------------------------------------------------------- -void ConfigDialog::addShortcut (KeySequenceConfig& cfg, QAction* act, int& i) -{ ShortcutListItem* item = new ShortcutListItem; - item->setIcon (act->icon()); - item->setKeyConfig (&cfg); - item->setAction (act); - setShortcutText (item); - - // If the action doesn't have a valid icon, use an empty one - // so that the list is kept aligned. - if (act->icon().isNull()) - item->setIcon (getIcon ("empty")); - - ui->shortcutsList->insertItem (i++, item); -} - -// ============================================================================= -// Initializes the table of grid stuff -// ----------------------------------------------------------------------------- -void ConfigDialog::initGrids() -{ QGridLayout* gridlayout = new QGridLayout; - QLabel* xlabel = new QLabel ("X"), - *ylabel = new QLabel ("Y"), - *zlabel = new QLabel ("Z"), - *anglabel = new QLabel ("Angle"); - short i = 1; - - for (QLabel* label : initlist<QLabel*> ({xlabel, ylabel, zlabel, anglabel})) - { label->setAlignment (Qt::AlignCenter); - gridlayout->addWidget (label, 0, i++); - } - - for (int i = 0; i < g_NumGrids; ++i) - { // Icon - lb_gridIcons[i] = new QLabel; - lb_gridIcons[i]->setPixmap (getIcon (fmt ("grid-%1", str (g_GridInfo[i].name).toLower()))); - - // Text label - lb_gridLabels[i] = new QLabel (fmt ("%1:", g_GridInfo[i].name)); - - QHBoxLayout* labellayout = new QHBoxLayout; - labellayout->addWidget (lb_gridIcons[i]); - labellayout->addWidget (lb_gridLabels[i]); - gridlayout->addLayout (labellayout, i + 1, 0); - - // Add the widgets - for (int j = 0; j < 4; ++j) - { dsb_gridData[i][j] = new QDoubleSpinBox; - dsb_gridData[i][j]->setValue (g_GridInfo[i].confs[j]->value); - gridlayout->addWidget (dsb_gridData[i][j], i + 1, j + 1); - } - } - - ui->grids->setLayout (gridlayout); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static const struct LDExtProgInfo -{ const str name, iconname; - StringConfig* const path; - mutable QLineEdit* input; - mutable QPushButton* setPathButton; -#ifndef _WIN32 - BoolConfig* const wine; - mutable QCheckBox* wineBox; -#endif // _WIN32 -} g_LDExtProgInfo[] = -{ -#ifndef _WIN32 -# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &prog_##LOWNAME, null, null, &prog_##LOWNAME##_wine, null }, -#else -# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &prog_##LOWNAME, null, null }, -#endif - EXTPROG (Ytruder, ytruder) - EXTPROG (Rectifier, rectifier) - EXTPROG (Intersector, intersector) - EXTPROG (Isecalc, isecalc) - EXTPROG (Coverer, coverer) - EXTPROG (Edger2, edger2) -#undef EXTPROG -}; - -// ============================================================================= -// Initializes the stuff in the ext programs tab -// ----------------------------------------------------------------------------- -void ConfigDialog::initExtProgs() -{ QGridLayout* pathsLayout = new QGridLayout; - int row = 0; - - for (const LDExtProgInfo& info : g_LDExtProgInfo) - { QLabel* icon = new QLabel, - *progLabel = new QLabel (info.name); - QLineEdit* input = new QLineEdit; - QPushButton* setPathButton = new QPushButton; - - icon->setPixmap (getIcon (info.iconname)); - input->setText (info.path->value); - setPathButton->setIcon (getIcon ("folder")); - info.input = input; - info.setPathButton = setPathButton; - - connect (setPathButton, SIGNAL (clicked()), this, SLOT (slot_setExtProgPath())); - - pathsLayout->addWidget (icon, row, 0); - pathsLayout->addWidget (progLabel, row, 1); - pathsLayout->addWidget (input, row, 2); - pathsLayout->addWidget (setPathButton, row, 3); - -#ifndef _WIN32 - QCheckBox* wineBox = new QCheckBox ("Wine"); - wineBox->setChecked (*info.wine); - info.wineBox = wineBox; - pathsLayout->addWidget (wineBox, row, 4); -#endif - - ++row; - } - - ui->extProgs->setLayout (pathsLayout); -} - -// ============================================================================= -// Set the settings based on widget data. -// ----------------------------------------------------------------------------- -void ConfigDialog::applySettings() -{ // Apply configuration - lv_colorize = ui->colorizeObjects->isChecked(); - gl_colorbfc = ui->colorBFC->isChecked(); - gl_blackedges = ui->blackEdges->isChecked(); - gl_maincolor_alpha = ( (double) ui->mainColorAlpha->value()) / 10.0f; - gl_linethickness = ui->lineThickness->value(); - gui_implicitfiles = ui->implicitFiles->isChecked(); - net_downloadpath = ui->downloadPath->text(); - net_guesspaths = ui->guessNetPaths->isChecked(); - net_autoclose = ui->autoCloseNetPrompt->isChecked(); - gl_logostuds = ui->m_logostuds->isChecked(); - ld_defaultuser = ui->m_profileUsername->text(); - ld_defaultname = ui->m_profileName->text(); - ld_defaultlicense = ui->m_profileLicense->currentIndex(); - gl_aa = ui->m_aa->isChecked(); - - // Rebuild the quick color toolbar - g_win->setQuickColors (quickColors); - gui_colortoolbar = quickColorString(); - - // Set the grid settings - for (int i = 0; i < g_NumGrids; ++i) - for (int j = 0; j < 4; ++j) - g_GridInfo[i].confs[j]->value = dsb_gridData[i][j]->value(); - - // Apply key shortcuts -#define act(N) ACTION(N)->setShortcut (key_##N); -#include "actions.h" - - // Ext program settings - for (const LDExtProgInfo& info : g_LDExtProgInfo) - { *info.path = info.input->text(); - -#ifndef _WIN32 - *info.wine = info.wineBox->isChecked(); -#endif // _WIN32 - } - - Config::save(); - reloadAllSubfiles(); - loadLogoedStuds(); - g_win->R()->setBackground(); - g_win->fullRefresh(); - g_win->updateToolBars(); - g_win->updateFileList(); -} - -// ============================================================================= -// A dialog button was clicked -// ----------------------------------------------------------------------------- -void ConfigDialog::buttonClicked (QAbstractButton* button) -{ typedef QDialogButtonBox QDDB; - QDialogButtonBox* dbb = ui->buttonBox; - - if (button == dbb->button (QDDB::Ok)) - { applySettings(); - accept(); - } elif (button == dbb->button (QDDB::Apply)) - { applySettings(); - } elif (button == dbb->button (QDDB::Cancel)) - { reject(); - } -} - -// ============================================================================= -// Update the list of color toolbar items in the quick color tab. -// ----------------------------------------------------------------------------- -void ConfigDialog::updateQuickColorList (LDQuickColor* sel) -{ for (QListWidgetItem * item : quickColorItems) - delete item; - - quickColorItems.clear(); - - // Init table items - for (LDQuickColor& entry : quickColors) - { QListWidgetItem* item = new QListWidgetItem; - - if (entry.isSeparator()) - { item->setText ("--------"); - item->setIcon (getIcon ("empty")); - } - else - { LDColor* col = entry.color(); - - if (col == null) - { item->setText ("[[unknown color]]"); - item->setIcon (getIcon ("error")); - } - else - { item->setText (col->name); - item->setIcon (makeColorIcon (col, 16)); - } - } - - ui->quickColorList->addItem (item); - quickColorItems << item; - - if (sel && &entry == sel) - { ui->quickColorList->setCurrentItem (item); - ui->quickColorList->scrollToItem (item); - } - } -} - -// ============================================================================= -// Quick colors: add or edit button was clicked. -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_setColor() -{ LDQuickColor* entry = null; - QListWidgetItem* item = null; - const bool isNew = static_cast<QPushButton*> (sender()) == ui->quickColor_add; - - if (isNew == false) - { item = getSelectedQuickColor(); - - if (!item) - return; - - int i = getItemRow (item, quickColorItems); - entry = &quickColors[i]; - - if (entry->isSeparator() == true) - return; // don't color separators - } - - short defval = entry ? entry->color()->index : -1; - short val; - - if (ColorSelector::getColor (val, defval, this) == false) - return; - - if (entry) - entry->setColor (getColor (val)); - else - { LDQuickColor entry (getColor (val), null); - - item = getSelectedQuickColor(); - int idx = (item) ? getItemRow (item, quickColorItems) + 1 : quickColorItems.size(); - - quickColors.insert (idx, entry); - entry = quickColors[idx]; - } - - updateQuickColorList (entry); -} - -// ============================================================================= -// Remove a quick color -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_delColor() -{ if (ui->quickColorList->selectedItems().isEmpty()) - return; - - QListWidgetItem* item = ui->quickColorList->selectedItems() [0]; - quickColors.removeAt (getItemRow (item, quickColorItems)); - updateQuickColorList(); -} - -// ============================================================================= -// Move a quick color up/down -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_moveColor() -{ const bool up = (static_cast<QPushButton*> (sender()) == ui->quickColor_moveUp); - - if (ui->quickColorList->selectedItems().isEmpty()) - return; - - QListWidgetItem* item = ui->quickColorList->selectedItems() [0]; - int idx = getItemRow (item, quickColorItems); - int dest = up ? (idx - 1) : (idx + 1); - - if (dest < 0 || dest >= quickColorItems.size()) - return; // destination out of bounds - - LDQuickColor tmp = quickColors[dest]; - quickColors[dest] = quickColors[idx]; - quickColors[idx] = tmp; - - updateQuickColorList (&quickColors[dest]); -} - -// ============================================================================= -// Add a separator to quick colors -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_addColorSeparator() -{ quickColors << LDQuickColor::getSeparator(); - updateQuickColorList (&quickColors[quickColors.size() - 1]); -} - -// ============================================================================= -// Clear all quick colors -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_clearColors() -{ quickColors.clear(); - updateQuickColorList(); -} - -// ============================================================================= -// Pick a color and set the appropriate configuration option. -// ----------------------------------------------------------------------------- -void ConfigDialog::pickColor (StringConfig& conf, QPushButton* button) -{ QColor col = QColorDialog::getColor (QColor (conf)); - - if (col.isValid()) - { uchar r = col.red(), - g = col.green(), - b = col.blue(); - conf.value.sprintf ("#%.2X%.2X%.2X", r, g, b); - setButtonBackground (button, conf.value); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_setGLBackground() -{ pickColor (gl_bgcolor, ui->backgroundColorButton); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_setGLForeground() -{ pickColor (gl_maincolor, ui->mainColorButton); -} - -// ============================================================================= -// Sets background color of a given button. -// ----------------------------------------------------------------------------- -void ConfigDialog::setButtonBackground (QPushButton* button, str value) -{ button->setIcon (getIcon ("colorselect")); - button->setAutoFillBackground (true); - button->setStyleSheet (fmt ("background-color: %1", value)); -} - -// ============================================================================= -// Finds the given list widget item in the list of widget items given. -// ----------------------------------------------------------------------------- -int ConfigDialog::getItemRow (QListWidgetItem* item, QList<QListWidgetItem*>& haystack) -{ int i = 0; - - for (QListWidgetItem* it : haystack) - { if (it == item) - return i; - - ++i; - } - - return -1; -} - -// ============================================================================= -// Which quick color is currently selected? -// ----------------------------------------------------------------------------- -QListWidgetItem* ConfigDialog::getSelectedQuickColor() -{ if (ui->quickColorList->selectedItems().isEmpty()) - return null; - - return ui->quickColorList->selectedItems() [0]; -} - -// ============================================================================= -// Get the list of shortcuts selected -// ----------------------------------------------------------------------------- -QList<ShortcutListItem*> ConfigDialog::getShortcutSelection() -{ QList<ShortcutListItem*> out; - - for (QListWidgetItem* entry : ui->shortcutsList->selectedItems()) - out << static_cast<ShortcutListItem*> (entry); - - return out; -} - -// ============================================================================= -// Edit the shortcut of a given action. -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_setShortcut() -{ QList<ShortcutListItem*> sel = getShortcutSelection(); - - if (sel.size() < 1) - return; - - ShortcutListItem* item = sel[0]; - - if (KeySequenceDialog::staticDialog (item->keyConfig(), this)) - setShortcutText (item); -} - -// ============================================================================= -// Reset a shortcut to defaults -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_resetShortcut() -{ QList<ShortcutListItem*> sel = getShortcutSelection(); - - for (ShortcutListItem* item : sel) - { item->keyConfig()->reset(); - setShortcutText (item); - } -} - -// ============================================================================= -// Remove the shortcut of an action. -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_clearShortcut() -{ QList<ShortcutListItem*> sel = getShortcutSelection(); - - for (ShortcutListItem* item : sel) - { item->keyConfig()->value = QKeySequence(); - setShortcutText (item); - } -} - -// ============================================================================= -// Set the path of an external program -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_setExtProgPath() -{ const LDExtProgInfo* info = null; - - for (const LDExtProgInfo& it : g_LDExtProgInfo) - { if (it.setPathButton == sender()) - { info = ⁢ - break; - } - } - - assert (info != null); - str fpath = QFileDialog::getOpenFileName (this, fmt ("Path to %1", info->name), *info->path, g_extProgPathFilter); - - if (fpath.isEmpty()) - return; - - info->input->setText (fpath); -} - -// ============================================================================= -// '...' button pressed for the download path -// ----------------------------------------------------------------------------- -void ConfigDialog::slot_findDownloadFolder() -{ str dpath = QFileDialog::getExistingDirectory(); - ui->downloadPath->setText (dpath); -} - -// ============================================================================= -// Updates the text string for a given shortcut list item -// ----------------------------------------------------------------------------- -void ConfigDialog::setShortcutText (ShortcutListItem* item) -{ QAction* act = item->action(); - str label = act->iconText(); - str keybind = item->keyConfig()->value.toString(); - item->setText (fmt ("%1 (%2)", label, keybind)); -} - -// ============================================================================= -// Gets the configuration string of the quick color toolbar -// ----------------------------------------------------------------------------- -str ConfigDialog::quickColorString() -{ str val; - - for (const LDQuickColor& entry : quickColors) - { if (val.length() > 0) - val += ':'; - - if (entry.isSeparator()) - val += '|'; - else - val += fmt ("%1", entry.color()->index); - } - - return val; -} - -// ========================================================================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ========================================================================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ========================================================================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ========================================================================================================================= -KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f), seq (seq) -{ lb_output = new QLabel; - IMPLEMENT_DIALOG_BUTTONS - - setWhatsThis ("Into this dialog you can input a key sequence for use as a " - "shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to " - "dismiss."); - - QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (lb_output); - layout->addWidget (bbx_buttons); - setLayout (layout); - - updateOutput(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool KeySequenceDialog::staticDialog (KeySequenceConfig* cfg, QWidget* parent) -{ KeySequenceDialog dlg (cfg->value, parent); - - if (dlg.exec() == false) - return false; - - cfg->value = dlg.seq; - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void KeySequenceDialog::updateOutput() -{ str shortcut = seq.toString(); - - if (seq == QKeySequence()) - shortcut = "<empty>"; - - str text = fmt ("<center><b>%1</b></center>", shortcut); - lb_output->setText (text); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) -{ seq = ev->key() + ev->modifiers(); - updateOutput(); -}
--- a/src/configDialog.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/configDialog.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -28,8 +28,9 @@ // ============================================================================= class ShortcutListItem : public QListWidgetItem -{ PROPERTY (KeySequenceConfig*, keyConfig, setKeyConfig) - PROPERTY (QAction*, action, setAction) +{ + 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) : @@ -38,11 +39,13 @@ // ============================================================================= class ConfigDialog : public QDialog -{ Q_OBJECT +{ + Q_OBJECT public: enum Tab - { InterfaceTab, + { + InterfaceTab, ProfileTab, ShortcutsTab, QuickColorsTab, @@ -66,12 +69,12 @@ void applySettings(); void addShortcut (KeySequenceConfig& cfg, QAction* act, int& i); - void setButtonBackground (QPushButton* button, str value); - void pickColor (StringConfig& cfg, QPushButton* button); + void setButtonBackground (QPushButton* button, QString value); + void pickColor (QString& conf, QPushButton* button); void updateQuickColorList (LDQuickColor* sel = null); void setShortcutText (ShortcutListItem* item); int getItemRow (QListWidgetItem* item, QList<QListWidgetItem*>& haystack); - str quickColorString(); + QString quickColorString(); QListWidgetItem* getSelectedQuickColor(); QList<ShortcutListItem*> getShortcutSelection(); void initGrids(); @@ -80,6 +83,7 @@ private slots: void slot_setGLBackground(); void slot_setGLForeground(); + void slot_setGLSelectColor(); void slot_setShortcut(); void slot_resetShortcut(); void slot_clearShortcut(); @@ -98,7 +102,8 @@ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= class KeySequenceDialog : public QDialog -{ Q_OBJECT +{ + Q_OBJECT public: explicit KeySequenceDialog (QKeySequence seq, QWidget* parent = null, Qt::WindowFlags f = 0);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/crashcatcher.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#ifdef __unix__ + +#include <QString> +#include <QProcess> +#include <QTemporaryFile> +#include <QMessageBox> +#include <unistd.h> +#include <signal.h> +#include <sys/prctl.h> +#include "crashcatcher.h" +#include "types.h" +#include "dialogs.h" + +// Is the crash catcher active now? +static bool g_crashCatcherActive = false; + +// If an assertion failed, what was it? +static QString g_assertionFailure; + +// List of signals to catch and crash on +static QList<int> g_signalsToCatch ({ + SIGSEGV, // segmentation fault + SIGABRT, // abort() calls + SIGFPE, // floating point exceptions (e.g. division by zero) + SIGILL, // illegal instructions +}); + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void handleCrash (int sig) +{ + printf ("%s: crashed with signal %d, launching gdb\n", __func__, sig); + + if (g_crashCatcherActive) + { + printf ("caught signal while crash catcher is active!\n"); + exit (149); + } + + const pid_t pid = getpid(); + QProcess proc; + QTemporaryFile commandsFile; + + g_crashCatcherActive = true; + + if (commandsFile.open()) + { + commandsFile.write (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 ("<h3>Program crashed with signal %1</h3>\n\n" + "%2" + "<p><b>GDB <tt>stdout</tt>:</b></p><pre>%3</pre>\n" + "<p><b>GDB <tt>stderr</tt>:</b></p><pre>%4</pre>", + sig, (!g_assertionFailure.isEmpty()) ? g_assertionFailure : "", output, err)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void initCrashCatcher() +{ + struct sigaction sighandler; + sighandler.sa_handler = &handleCrash; + sighandler.sa_flags = 0; + sigemptyset (&sighandler.sa_mask); + + for (int sig : g_signalsToCatch) + sigaction (sig, &sighandler, null); + + log ("%1: crash catcher hooked to signals: %2\n", __func__, g_signalsToCatch); +} +#endif // #ifdef __unix__ + +// ============================================================================= +// This function must be readily available in both Windows and Linux. We display +// the bomb box straight in Windows while in Linux we let abort() trigger the +// signal handler, which will cause the usual bomb box with GDB diagnostics. +// Said prompt will embed the assertion failure information. +// ----------------------------------------------------------------------------- +void assertionFailure (const char* file, int line, const char* funcname, const char* expr) +{ + QString errmsg = fmt ( + "<p><b>File</b>: <tt>%1</tt><br />" + "<b>Line</b>: <tt>%2</tt><br />" + "<b>Function:</b> <tt>%3</tt></p>" + "<p>Assertion <b><tt>`%4'</tt></b> failed.</p>", + file, line, funcname, expr); + + g_assertionFailure = errmsg; + +#ifndef __unix__ + bombBox (errmsg); +#endif + + abort(); +} \ No newline at end of file
--- a/src/crashcatcher.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -#ifdef __unix__ - -#include <QString> -#include <QProcess> -#include <QTemporaryFile> -#include <QMessageBox> -#include <unistd.h> -#include <signal.h> -#include <sys/prctl.h> -#include "crashcatcher.h" -#include "types.h" -#include "dialogs.h" - -// Is the crash catcher active now? -static bool g_crashCatcherActive = false; - -// If an assertion failed, what was it? -static str g_assertionFailure; - -// List of signals to catch and crash on -static QList<int> g_signalsToCatch ({ - SIGSEGV, // segmentation fault - SIGABRT, // abort() calls - SIGFPE, // floating point exceptions (e.g. division by zero) - SIGILL, // illegal instructions -}); - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void handleCrash (int sig) -{ printf ("%s: crashed with signal %d, launching gdb\n", __func__, sig); - - if (g_crashCatcherActive) - { printf ("caught signal while crash catcher is active!\n"); - exit (149); - } - - const pid_t pid = getpid(); - QProcess proc; - QTemporaryFile commandsFile; - - g_crashCatcherActive = true; - - if (commandsFile.open()) - { commandsFile.write (fmt ("attach %1\n", pid).toLocal8Bit()); - commandsFile.write (str ("backtrace full\n").toLocal8Bit()); - commandsFile.write (str ("detach\n").toLocal8Bit()); - commandsFile.write (str ("quit").toLocal8Bit()); - commandsFile.flush(); - commandsFile.close(); - } - - QStringList args ({"-x", commandsFile.fileName()}); - - proc.start ("gdb", args); - - // Linux doesn't allow ptrace to be used on anything but direct child processes - // so we need to use prctl to register an exception to this to allow GDB attach to us. - // We need to do this now and no earlier because only now we actually know GDB's PID. - prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0); - - proc.waitForFinished (1000); - str output = QString (proc.readAllStandardOutput()); - str err = QString (proc.readAllStandardError()); - - bombBox (fmt ("<h3>Program crashed with signal %1</h3>\n\n" - "%2" - "<p><b>GDB <tt>stdout</tt>:</b></p><pre>%3</pre>\n" - "<p><b>GDB <tt>stderr</tt>:</b></p><pre>%4</pre>", - sig, (!g_assertionFailure.isEmpty()) ? g_assertionFailure : "", output, err)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void initCrashCatcher() -{ struct sigaction sighandler; - sighandler.sa_handler = &handleCrash; - sighandler.sa_flags = 0; - sigemptyset (&sighandler.sa_mask); - - for (int sig : g_signalsToCatch) - sigaction (sig, &sighandler, null); - - log ("%1: crash catcher hooked to signals: %2\n", __func__, g_signalsToCatch); -} -#endif // #ifdef __unix__ - -// ============================================================================= -// This function must be readily available in both Windows and Linux. We display -// the bomb box straight in Windows while in Linux we let abort() trigger the -// signal handler, which will cause the usual bomb box with GDB diagnostics. -// Said prompt will embed the assertion failure information. -// ----------------------------------------------------------------------------- -void assertionFailure (const char* file, int line, const char* funcname, const char* expr) -{ str errmsg = fmt ( - "<p><b>File</b>: <tt>%1</tt><br />" - "<b>Line</b>: <tt>%2</tt><br />" - "<b>Function:</b> <tt>%3</tt></p>" - "<p>Assertion <b><tt>`%4'</tt></b> failed.</p>", - file, line, funcname, expr); - - g_assertionFailure = errmsg; - -#ifndef __unix__ - bombBox (errmsg); -#endif - - abort(); -} \ No newline at end of file
--- a/src/crashcatcher.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/crashcatcher.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,3 +1,21 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + #ifndef LDFORGE_CRASHCATCHER_H #define LDFORGE_CRASHCATCHER_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dialogs.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#include <QDialog> +#include <QLineEdit> +#include <QSpinBox> +#include <QDialogButtonBox> +#include <QFileDialog> +#include <QLabel> +#include <QPushButton> +#include <QBoxLayout> +#include <QGridLayout> +#include <QProgressBar> +#include <QCheckBox> +#include <QDesktopServices> +#include <QMessageBox> +#include <QUrl> +#include "dialogs.h" +#include "widgets.h" +#include "gui.h" +#include "gldraw.h" +#include "docs.h" +#include "document.h" +#include "dialogs.h" +#include "ui_overlay.h" +#include "ui_ldrawpath.h" +#include "ui_openprogress.h" +#include "ui_extprogpath.h" +#include "ui_about.h" +#include "ui_bombbox.h" +#include "moc_dialogs.cpp" + +extern const char* g_extProgPathFilter; +extern_cfg (String, io_ldpath); + +// ============================================================================= +// ----------------------------------------------------------------------------- +OverlayDialog::OverlayDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) +{ + ui = new Ui_OverlayUI; + ui->setupUi (this); + + m_cameraArgs = + { + { ui->top, GL::ETopCamera }, + { ui->bottom, GL::EBottomCamera }, + { ui->front, GL::EFrontCamera }, + { ui->back, GL::EBackCamera }, + { ui->left, GL::ELeftCamera }, + { ui->right, GL::ERightCamera } + }; + + GL::EFixedCamera cam = g_win->R()->camera(); + + if (cam == GL::EFreeCamera) + cam = GL::ETopCamera; + + connect (ui->width, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged())); + connect (ui->height, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged())); + connect (ui->buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_help())); + connect (ui->fileSearchButton, SIGNAL (clicked (bool)), this, SLOT (slot_fpath())); + + slot_dimensionsChanged(); + fillDefaults (cam); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +OverlayDialog::~OverlayDialog() +{ + delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void OverlayDialog::fillDefaults (int newcam) +{ + LDGLOverlay& info = g_win->R()->getOverlay (newcam); + radioDefault<int> (newcam, m_cameraArgs); + + if (info.img != null) + { + ui->filename->setText (info.fname); + ui->originX->setValue (info.ox); + ui->originY->setValue (info.oy); + ui->width->setValue (info.lw); + ui->height->setValue (info.lh); + } + else + { + ui->filename->setText (""); + ui->originX->setValue (0); + ui->originY->setValue (0); + ui->width->setValue (0.0f); + ui->height->setValue (0.0f); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString OverlayDialog::fpath() const +{ + return ui->filename->text(); +} + +int OverlayDialog::ofsx() const +{ + return ui->originX->value(); +} + +int OverlayDialog::ofsy() const +{ + return ui->originY->value(); +} + +double OverlayDialog::lwidth() const +{ + return ui->width->value(); +} + +double OverlayDialog::lheight() const +{ + return ui->height->value(); +} + +int OverlayDialog::camera() const +{ + return radioSwitch<int> (GL::ETopCamera, m_cameraArgs); +} + +void OverlayDialog::slot_fpath() +{ + ui->filename->setText (QFileDialog::getOpenFileName (null, "Overlay image")); +} + +void OverlayDialog::slot_help() +{ + showDocumentation (g_docs_overlays); +} + +void OverlayDialog::slot_dimensionsChanged() +{ + bool enable = (ui->width->value() != 0) || (ui->height->value() != 0); + ui->buttonBox->button (QDialogButtonBox::Ok)->setEnabled (enable); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDrawPathDialog::LDrawPathDialog (const bool validDefault, QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f), + m_validDefault (validDefault) +{ + ui = new Ui_LDPathUI; + ui->setupUi (this); + ui->status->setText ("---"); + + if (validDefault) + ui->heading->hide(); + else + { + cancelButton()->setText ("Exit"); + cancelButton()->setIcon (getIcon ("exit")); + } + + okButton()->setEnabled (false); + + connect (ui->path, SIGNAL (textEdited (QString)), this, SLOT (slot_tryConfigure())); + connect (ui->searchButton, SIGNAL (clicked()), this, SLOT (slot_findPath())); + connect (ui->buttonBox, SIGNAL (rejected()), this, validDefault ? SLOT (reject()) : SLOT (slot_exit())); + connect (ui->buttonBox, SIGNAL (accepted()), this, SLOT (slot_accept())); + + setPath (io_ldpath); + + if (validDefault) + slot_tryConfigure(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDrawPathDialog::~LDrawPathDialog() +{ + delete ui; +} + +QPushButton* LDrawPathDialog::okButton() +{ + return ui->buttonBox->button (QDialogButtonBox::Ok); +} + +QPushButton* LDrawPathDialog::cancelButton() +{ + return ui->buttonBox->button (QDialogButtonBox::Cancel); +} + +void LDrawPathDialog::setPath (QString path) +{ + ui->path->setText (path); +} + +QString LDrawPathDialog::filename() const +{ + return ui->path->text(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDrawPathDialog::slot_findPath() +{ + QString newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path"); + + if (newpath.length() > 0 && newpath != filename()) + { + setPath (newpath); + slot_tryConfigure(); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDrawPathDialog::slot_exit() +{ + exit (0); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDrawPathDialog::slot_tryConfigure() +{ + if (LDPaths::tryConfigure (filename()) == false) + { + ui->status->setText (fmt ("<span style=\"color:#700; \">%1</span>", LDPaths::getError())); + okButton()->setEnabled (false); + return; + } + + ui->status->setText ("<span style=\"color: #270; \">OK!</span>"); + okButton()->setEnabled (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDrawPathDialog::slot_accept() +{ + Config::save(); + accept(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) +{ + ui = new Ui_OpenProgressUI; + ui->setupUi (this); + ui->progressText->setText ("Parsing..."); + setNumLines (0); + m_Progress = 0; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +OpenProgressDialog::~OpenProgressDialog() +{ + delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void OpenProgressDialog::setNumLines (int const& a) +{ + m_NumLines = a; + ui->progressBar->setRange (0, getNumLines()); + updateValues(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void OpenProgressDialog::updateValues() +{ + ui->progressText->setText (fmt ("Parsing... %1 / %2", getProgress(), getNumLines())); + ui->progressBar->setValue (getProgress()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void OpenProgressDialog::updateProgress (int progress) +{ + setProgress (progress); + updateValues(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +ExtProgPathPrompt::ExtProgPathPrompt (QString progName, QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f), + ui (new Ui_ExtProgPath) +{ + ui->setupUi (this); + QString labelText = ui->m_label->text(); + labelText.replace ("<PROGRAM>", progName); + ui->m_label->setText (labelText); + connect (ui->m_findPath, SIGNAL (clicked (bool)), this, SLOT (findPath())); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +ExtProgPathPrompt::~ExtProgPathPrompt() +{ + delete ui; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ExtProgPathPrompt::findPath() +{ + QString path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter); + + if (!path.isEmpty()) + ui->m_path->setText (path); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString ExtProgPathPrompt::getPath() const +{ + return ui->m_path->text(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f) +{ + Ui::AboutUI ui; + ui.setupUi (this); + ui.versionInfo->setText (APPNAME " " + fullVersionString()); + + QPushButton* mailButton = new QPushButton; + mailButton->setText (tr ("Contact")); + mailButton->setIcon (getIcon ("mail")); + ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole); + connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail())); + + setWindowTitle (fmt (tr ("About %1"), APPNAME)); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void AboutDialog::slot_mail() +{ + QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <arezey@gmail.com>?subject=LDForge")); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void bombBox (const QString& message) +{ + QDialog dlg (g_win); + Ui_BombBox ui; + + ui.setupUi (&dlg); + ui.m_text->setText (message); + ui.buttonBox->button (QDialogButtonBox::Close)->setText (QObject::tr ("Damn it")); + dlg.exec(); +}
--- a/src/dialogs.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,346 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QDialog> -#include <QLineEdit> -#include <QSpinBox> -#include <QDialogButtonBox> -#include <QFileDialog> -#include <QLabel> -#include <QPushButton> -#include <QBoxLayout> -#include <QGridLayout> -#include <QProgressBar> -#include <QCheckBox> -#include <QDesktopServices> -#include <QMessageBox> -#include <QUrl> -#include "dialogs.h" -#include "widgets.h" -#include "gui.h" -#include "gldraw.h" -#include "docs.h" -#include "file.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::Top }, - { ui->bottom, GL::Bottom }, - { ui->front, GL::Front }, - { ui->back, GL::Back }, - { ui->left, GL::Left }, - { ui->right, GL::Right } - }; - - GL::Camera cam = g_win->R()->camera(); - - if (cam == GL::Free) - cam = GL::Top; - - connect (ui->width, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged())); - connect (ui->height, SIGNAL (valueChanged (double)), this, SLOT (slot_dimensionsChanged())); - connect (ui->buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_help())); - connect (ui->fileSearchButton, SIGNAL (clicked (bool)), this, SLOT (slot_fpath())); - - slot_dimensionsChanged(); - fillDefaults (cam); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -OverlayDialog::~OverlayDialog() -{ delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void OverlayDialog::fillDefaults (int newcam) -{ LDGLOverlay& info = g_win->R()->getOverlay (newcam); - radioDefault<int> (newcam, m_cameraArgs); - - if (info.img != null) - { ui->filename->setText (info.fname); - ui->originX->setValue (info.ox); - ui->originY->setValue (info.oy); - ui->width->setValue (info.lw); - ui->height->setValue (info.lh); - } - else - { ui->filename->setText (""); - ui->originX->setValue (0); - ui->originY->setValue (0); - ui->width->setValue (0.0f); - ui->height->setValue (0.0f); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str OverlayDialog::fpath() const -{ return ui->filename->text(); -} - -int OverlayDialog::ofsx() const -{ return ui->originX->value(); -} - -int OverlayDialog::ofsy() const -{ return ui->originY->value(); -} - -double OverlayDialog::lwidth() const -{ return ui->width->value(); -} - -double OverlayDialog::lheight() const -{ return ui->height->value(); -} - -int OverlayDialog::camera() const -{ return radioSwitch<int> (GL::Top, m_cameraArgs); -} - -void OverlayDialog::slot_fpath() -{ ui->filename->setText (QFileDialog::getOpenFileName (null, "Overlay image")); -} - -void OverlayDialog::slot_help() -{ showDocumentation (g_docs_overlays); -} - -void OverlayDialog::slot_dimensionsChanged() -{ bool enable = (ui->width->value() != 0) || (ui->height->value() != 0); - ui->buttonBox->button (QDialogButtonBox::Ok)->setEnabled (enable); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDrawPathDialog::LDrawPathDialog (const bool validDefault, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f), - m_validDefault (validDefault) -{ ui = new Ui_LDPathUI; - ui->setupUi (this); - ui->status->setText ("---"); - - if (validDefault) - ui->heading->hide(); - else - { cancelButton()->setText ("Exit"); - cancelButton()->setIcon (getIcon ("exit")); - } - - okButton()->setEnabled (false); - - connect (ui->path, SIGNAL (textEdited (QString)), this, SLOT (slot_tryConfigure())); - connect (ui->searchButton, SIGNAL (clicked()), this, SLOT (slot_findPath())); - connect (ui->buttonBox, SIGNAL (rejected()), this, validDefault ? SLOT (reject()) : SLOT (slot_exit())); - connect (ui->buttonBox, SIGNAL (accepted()), this, SLOT (slot_accept())); - - setPath (io_ldpath); - - if (validDefault) - slot_tryConfigure(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDrawPathDialog::~LDrawPathDialog() -{ delete ui; -} - -QPushButton* LDrawPathDialog::okButton() -{ return ui->buttonBox->button (QDialogButtonBox::Ok); -} - -QPushButton* LDrawPathDialog::cancelButton() -{ return ui->buttonBox->button (QDialogButtonBox::Cancel); -} - -void LDrawPathDialog::setPath (str path) -{ ui->path->setText (path); -} - -str LDrawPathDialog::filename() const -{ return ui->path->text(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDrawPathDialog::slot_findPath() -{ str newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path"); - - if (newpath.length() > 0 && newpath != filename()) - { setPath (newpath); - slot_tryConfigure(); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDrawPathDialog::slot_exit() -{ exit (0); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDrawPathDialog::slot_tryConfigure() -{ if (LDPaths::tryConfigure (filename()) == false) - { ui->status->setText (fmt ("<span style=\"color:#700; \">%1</span>", LDPaths::getError())); - okButton()->setEnabled (false); - return; - } - - ui->status->setText ("<span style=\"color: #270; \">OK!</span>"); - okButton()->setEnabled (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDrawPathDialog::slot_accept() -{ Config::save(); - accept(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) -{ ui = new Ui_OpenProgressUI; - ui->setupUi (this); - ui->progressText->setText ("Parsing..."); - setNumLines (0); - m_progress = 0; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -OpenProgressDialog::~OpenProgressDialog() -{ delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -READ_ACCESSOR (int, OpenProgressDialog::numLines) -{ return m_numLines; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -SET_ACCESSOR (int, OpenProgressDialog::setNumLines) -{ m_numLines = val; - ui->progressBar->setRange (0, numLines()); - updateValues(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void OpenProgressDialog::updateValues() -{ ui->progressText->setText (fmt ("Parsing... %1 / %2", progress(), numLines())); - ui->progressBar->setValue (progress()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void OpenProgressDialog::updateProgress (int progress) -{ m_progress = progress; - updateValues(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -ExtProgPathPrompt::ExtProgPathPrompt (str progName, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f), - ui (new Ui_ExtProgPath) -{ - ui->setupUi (this); - str labelText = ui->m_label->text(); - labelText.replace ("<PROGRAM>", progName); - ui->m_label->setText (labelText); - connect (ui->m_findPath, SIGNAL (clicked (bool)), this, SLOT (findPath())); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -ExtProgPathPrompt::~ExtProgPathPrompt() -{ delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ExtProgPathPrompt::findPath() -{ str path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter); - - if (!path.isEmpty()) - ui->m_path->setText (path); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str ExtProgPathPrompt::getPath() const -{ return ui->m_path->text(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f) -{ Ui::AboutUI ui; - ui.setupUi (this); - ui.versionInfo->setText (fmt (tr ("LDForge %1"), fullVersionString())); - - QPushButton* mailButton = new QPushButton; - mailButton->setText ("Contact"); - mailButton->setIcon (getIcon ("mail")); - ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole); - connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail())); - - setWindowTitle ("About " APPNAME); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AboutDialog::slot_mail() -{ QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <slatenails64@gmail.com>?subject=LDForge")); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void bombBox (const str& message) -{ QDialog dlg (g_win); - Ui_BombBox ui; - - ui.setupUi (&dlg); - ui.m_text->setText (message); - ui.buttonBox->button (QDialogButtonBox::Close)->setText (QObject::tr ("Damn it")); - dlg.exec(); -}
--- a/src/dialogs.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/dialogs.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -20,7 +20,7 @@ #define LDFORGE_DIALOGS_H #include <QDialog> -#include "common.h" +#include "main.h" #include "types.h" class Ui_ExtProgPath; @@ -41,13 +41,14 @@ class Ui_OpenProgressUI; class OverlayDialog : public QDialog -{ Q_OBJECT +{ + Q_OBJECT public: explicit OverlayDialog (QWidget* parent = null, Qt::WindowFlags f = 0); virtual ~OverlayDialog(); - str fpath() const; + QString fpath() const; int ofsx() const; int ofsy() const; double lwidth() const; @@ -67,13 +68,14 @@ // ============================================================================= class LDrawPathDialog : public QDialog -{ Q_OBJECT +{ + Q_OBJECT public: explicit LDrawPathDialog (const bool validDefault, QWidget* parent = null, Qt::WindowFlags f = 0); virtual ~LDrawPathDialog(); - str filename() const; - void setPath (str path); + QString filename() const; + void setPath (QString path); private: Q_DISABLE_COPY (LDrawPathDialog) @@ -91,9 +93,10 @@ // ============================================================================= class OpenProgressDialog : public QDialog -{ Q_OBJECT - READ_PROPERTY (int, progress, setProgress) - DECLARE_PROPERTY (int, numLines, setNumLines) +{ + 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); @@ -110,12 +113,13 @@ // ============================================================================= class ExtProgPathPrompt : public QDialog -{ Q_OBJECT +{ + Q_OBJECT public: - explicit ExtProgPathPrompt (str progName, QWidget* parent = 0, Qt::WindowFlags f = 0); + explicit ExtProgPathPrompt (QString progName, QWidget* parent = 0, Qt::WindowFlags f = 0); virtual ~ExtProgPathPrompt(); - str getPath() const; + QString getPath() const; public slots: void findPath(); @@ -126,7 +130,8 @@ // ============================================================================= class AboutDialog : public QDialog -{ Q_OBJECT +{ + Q_OBJECT public: AboutDialog (QWidget* parent = null, Qt::WindowFlags f = 0); @@ -135,6 +140,6 @@ void slot_mail(); }; -void bombBox (const str& message); +void bombBox (const QString& message); #endif // LDFORGE_DIALOGS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/docs.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,76 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QDialog> +#include <QTextEdit> +#include <QDialogButtonBox> +#include <QBoxLayout> +#include "main.h" +#include "types.h" + +// ============================================================================= +// ----------------------------------------------------------------------------- +class DocumentViewer : public QDialog +{ + public: + explicit DocumentViewer (QWidget* parent = null, Qt::WindowFlags f = 0) : QDialog (parent, f) + { + te_text = new QTextEdit (this); + te_text->setMinimumSize (QSize (400, 300)); + te_text->setReadOnly (true); + + QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Close); + QVBoxLayout* layout = new QVBoxLayout (this); + layout->addWidget (te_text); + layout->addWidget (bbx_buttons); + + connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); + } + + void setText (const char* text) + { + te_text->setText (text); + } + + private: + QTextEdit* te_text; +}; + +const char* g_docs_overlays = + "<h1>Overlay images</h1><br />" + "<p>" APPNAME " supports drawing transparent images over the part model. This " + "can be used to have, for instance, a photo of the part overlaid on top of the " + "model and use it for drawing curves somewhat accurately.</p>" + "<p>For this purpose, a specific photo has to be taken of the part; it should " + "represent the part as true as possible to the actual camera used for editing. " + "The image should be taken from straight above the part, at as an orthogonal " + "angle as possible. It is recommended to take a lot of pictures this way and " + "select the best candidate.</p>" + "<p>The image should then be cropped with the knowledge of the image's LDU " + "dimensions in mind. The offset should then be identified in the image in pixels.</p>" + "<p>Finally, use the \"Set Overlay Image\" dialog and fill in the details. The " + "overlay image should then be ready for use."; + +// ============================================================================= +// ----------------------------------------------------------------------------- +void showDocumentation (const char* text) +{ + DocumentViewer dlg; + dlg.setText (text); + dlg.exec(); +}
--- a/src/docs.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QDialog> -#include <QTextEdit> -#include <QDialogButtonBox> -#include <QBoxLayout> -#include "common.h" -#include "types.h" - -// ============================================================================= -// ----------------------------------------------------------------------------- -class DocumentViewer : public QDialog -{ public: - explicit DocumentViewer (QWidget* parent = null, Qt::WindowFlags f = 0) : QDialog (parent, f) - { te_text = new QTextEdit (this); - te_text->setMinimumSize (QSize (400, 300)); - te_text->setReadOnly (true); - - QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Close); - QVBoxLayout* layout = new QVBoxLayout (this); - layout->addWidget (te_text); - layout->addWidget (bbx_buttons); - - connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); - } - - void setText (const char* text) - { te_text->setText (text); - } - - private: - QTextEdit* te_text; -}; - -const char* g_docs_overlays = - "<h1>Overlay images</h1><br />" - "<p>" APPNAME " supports drawing transparent images over the part model. This " - "can be used to have, for instance, a photo of the part overlaid on top of the " - "model and use it for drawing curves somewhat accurately.</p>" - "<p>For this purpose, a specific photo has to be taken of the part; it should " - "represent the part as true as possible to the actual camera used for editing. " - "The image should be taken from straight above the part, at as an orthogonal " - "angle as possible. It is recommended to take a lot of pictures this way and " - "select the best candidate.</p>" - "<p>The image should then be cropped with the knowledge of the image's LDU " - "dimensions in mind. The offset should then be identified in the image in pixels.</p>" - "<p>Finally, use the \"Set Overlay Image\" dialog and fill in the details. The " - "overlay image should then be ready for use."; - -// ============================================================================= -// ----------------------------------------------------------------------------- -void showDocumentation (const char* text) -{ DocumentViewer dlg; - dlg.setText (text); - dlg.exec(); -}
--- a/src/docs.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/docs.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -16,11 +16,11 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef DOCS_H -#define DOCS_H +#ifndef LDFORGE_DOCS_H +#define LDFORGE_DOCS_H extern const char* g_docs_overlays; void showDocumentation (const char* text); -#endif // DOCS_H \ No newline at end of file +#endif // LDFORGE_DOCS_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/document.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#include <QMessageBox> +#include <QFileDialog> +#include <QDir> +#include <QApplication> +#include "main.h" +#include "config.h" +#include "document.h" +#include "misc.h" +#include "gui.h" +#include "history.h" +#include "dialogs.h" +#include "gldraw.h" +#include "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<br />have LDConfig.ldr, parts/ and p/."; + return false; + } + + pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path); + pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path); + pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path); + + return true; + } + + // Accessors + 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<QString> ({ io_ldpath, net_downloadpath })) + { + for (const QString& subdir : initlist<QString> ({ "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<LDError*> (obj)->reason); + + if (getWarnings() != null) + (*getWarnings())++; + } + + m_Objects << obj; + setProgress (i); + + // If we have a dialog pointer, update the progress now + if (isOnForeground()) + dlg->updateProgress (i); + } + + // If we're done now, tell the environment we're done and stop. + if (i >= ((int) getLines().size()) - 1) + { + emit workDone(); + setDone (true); + return; + } + + // Otherwise, continue, by recursing back. + if (!isDone()) + { + // If we have a dialog to show progress output to, we cannot just call + // work() again immediately as the dialog needs some processor cycles as + // well. Thus, take a detour through the event loop by using the + // meta-object system. + // + // This terminates the loop here and control goes back to the function + // which called the file loader. It will keep processing the event loop + // until we're ready (see loadFileContents), thus the event loop will + // eventually catch the invokation we throw here and send us back. Though + // it's not technically recursion anymore, more like a for loop. :P + if (isOnForeground()) + QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i)); + else + work (i); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +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 ("<anonymous>")); + + int button = msgbox::question (g_win, tr ("Unsaved Changes"), message, + (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); + + switch (button) + { + case msgbox::Yes: + { + // If we don't have a file path yet, we have to ask the user for one. + if (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<LDDocument*> files = g_loadedFiles; + + for (LDDocument* file : files) + delete file; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void newFile() +{ + // Create a new anonymous file and set it to our current + LDDocument* f = new LDDocument; + f->setName (""); + f->setImplicit (false); + g_loadedFiles << f; + LDDocument::setCurrent (f); + LDDocument::closeInitialFile(); + g_win->R()->setFile (f); + g_win->doFullRefresh(); + g_win->updateTitle(); + g_win->updateActions(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void addRecentFile (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() : "<null>"); + + 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<LDComment*> (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<LDSubfile*> (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<LDError*> (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 ("<anonymous>"); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObjectList LDDocument::inlineContents (LDSubfile::InlineFlags flags) +{ + // Possibly substitute with logoed studs: + // stud.dat -> stud-logo.dat + // stud2.dat -> stud-logo2.dat + if (gl_logostuds && (flags & LDSubfile::RendererInline)) + { + // Ensure logoed studs are loaded first + loadLogoedStuds(); + + if (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<LDSubfile*> (obj); + + // We only want to cache immediate subfiles, so shed the caching + // flag when recursing deeper in hierarchy. + LDObjectList otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline)); + + for (LDObject* otherobj : otherobjs) + { + // Cache this object, if desired + if (doCache) + objcache << otherobj->createCopy(); + + objs << otherobj; + } + } + else + { + if (doCache) + objcache << obj->createCopy(); + + objs << obj->createCopy(); + } + } + + if (doCache) + setCache (objcache); + } + + return objs; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDDocument* LDDocument::current() +{ + return m_curdoc; +} + +// ============================================================================= +// Sets the given file as the current one on display. At some point in time this +// was an operation completely unheard of. ;) +// +// TODO: f can be temporarily null. This probably should not be the case. +// ----------------------------------------------------------------------------- +void LDDocument::setCurrent (LDDocument* f) +{ + // Implicit files were loaded for caching purposes and must never be set + // current. + if (f && f->isImplicit()) + return; + + m_curdoc = f; + + if (g_win && f) + { + // A ton of stuff needs to be updated + g_win->updateDocumentListItem (f); + g_win->buildObjList(); + g_win->updateTitle(); + g_win->R()->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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/document.h Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef LDFORGE_DOCUMENT_H +#define LDFORGE_DOCUMENT_H + +#include <QObject> +#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<LDDocumentPointer*>, 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<LDDocument*> g_loadedFiles; + +inline const LDObjectList& selection() +{ + return getCurrentDocument()->getSelection(); +} + +void addRecentFile (QString path); +void loadLogoedStuds(); +QString basename (QString path); +QString dirname (QString path); + +extern QList<LDDocument*> g_loadedFiles; // Vector of all currently opened files. + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +// 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/download.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#include <QNetworkAccessManager> +#include <QNetworkRequest> +#include <QNetworkReply> +#include <QDir> +#include <QProgressBar> +#include <QPushButton> +#include "download.h" +#include "ui_downloadfrom.h" +#include "types.h" +#include "gui.h" +#include "document.h" +#include "gldraw.h" +#include "configDialog.h" +#include "moc_download.cpp" + +cfg (String, net_downloadpath, ""); +cfg (Bool, net_guesspaths, true); +cfg (Bool, net_autoclose, true); + +const 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<QPushButton*> (getInterface()->buttonBox->button (QDialogButtonBox::Abort)); + + case Close: + return qobject_cast<QPushButton*> (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<QProgressBar*> (table->cellWidget (getTableRow(), progcol)); + + if (!prog) + { + prog = new QProgressBar; + table->setCellWidget (getTableRow(), progcol, prog); + } + + prog->setRange (0, getBytesTotal()); + prog->setValue (getBytesRead()); + } break; + + case EFinished: + case EFailed: + { + const QString text = (getState() == EFinished) + ? "<b><span style=\"color: #080\">FINISHED</span></b>" + : "<b><span style=\"color: #800\">FAILED</span></b>"; + + QLabel* lb = new QLabel (text); + lb->setAlignment (Qt::AlignCenter); + table->setCellWidget (getTableRow(), progcol, lb); + } break; + } + + QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (getTableRow(), labelcol)); + + if (isFirstUpdate()) + { + lb = new QLabel (fmt ("<b>%1</b>", getDestinaton()), table); + table->setCellWidget (getTableRow(), labelcol, lb); + } + + // Make sure that the cell is big enough to contain the label + if (table->columnWidth (labelcol) < lb->width()) + table->setColumnWidth (labelcol, lb->width()); + + setFirstUpdate (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void PartDownloadRequest::downloadFinished() +{ + if (getReply()->error() != QNetworkReply::NoError) + { + if (isPrimary() && !getPrompt()->isAborted()) + critical (getReply()->errorString()); + + setState (EFailed); + } + elif (getState() != EFailed) + setState (EFinished); + + setBytesRead (getBytesTotal()); + updateToTable(); + + if (getFilePointer()) + { + getFilePointer()->close(); + delete getFilePointer(); + setFilePointer (null); + + if (getState() == EFailed) + QFile::remove (getFilePath()); + } + + if (getState() != EFinished) + { + getPrompt()->checkIfFinished(); + return; + } + + // Try to load this file now. + LDDocument* f = openDocument (getFilePath(), false); + + if (!f) + return; + + f->setImplicit (!isPrimary()); + + // Iterate through this file and check for errors. If there's any that stems + // from unknown file references, try resolve that by downloading the reference. + // This is why downloading a part may end up downloading multiple files, as + // it resolves dependencies. + for (LDObject* obj : f->getObjects()) + { + LDError* err = dynamic_cast<LDError*> (obj); + + if (!err || err->getFileReferenced().isEmpty()) + continue; + + 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(); +}
--- a/src/download.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,502 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QNetworkAccessManager> -#include <QNetworkRequest> -#include <QNetworkReply> -#include <QDir> -#include <QProgressBar> -#include <QPushButton> -#include "download.h" -#include "ui_downloadfrom.h" -#include "types.h" -#include "gui.h" -#include "file.h" -#include "gldraw.h" -#include "configDialog.h" -#include "moc_download.cpp" - -cfg (String, net_downloadpath, ""); -cfg (Bool, net_guesspaths, true); -cfg (Bool, net_autoclose, false); - -constexpr const char *PartDownloader::k_UnofficialURL; - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::k_download() -{ str path = getDownloadPath(); - - if (path == "" || QDir (path).exists() == false) - { critical (PartDownloader::tr ("You need to specify a valid path for " - "downloaded files in the configuration to download paths.")); - - (new ConfigDialog (ConfigDialog::DownloadTab, null))->exec(); - return; - } - - PartDownloader* dlg = new PartDownloader; - dlg->exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str PartDownloader::getDownloadPath() -{ str path = net_downloadpath; - -#if DIRSLASH_CHAR != '/' - path.replace (DIRSLASH, "/"); -#endif - - return path; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloader::PartDownloader (QWidget* parent) : QDialog (parent) -{ ui = new Ui_DownloadFrom; - ui->setupUi (this); - ui->fname->setFocus(); - ui->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); - - m_downloadButton = new QPushButton (tr ("Download")); - ui->buttonBox->addButton (m_downloadButton, QDialogButtonBox::ActionRole); - getButton (Abort)->setEnabled (false); - - connect (ui->source, SIGNAL (currentIndexChanged (int)), this, SLOT (sourceChanged (int))); - connect (ui->buttonBox, SIGNAL (clicked (QAbstractButton*)), this, SLOT (buttonClicked (QAbstractButton*))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloader::~PartDownloader() -{ delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str PartDownloader::getURL() const -{ const Source src = getSource(); - str dest; - - switch (src) - { case PartsTracker: - dest = ui->fname->text(); - modifyDest (dest); - return str (k_UnofficialURL) + dest; - - case CustomURL: - return ui->fname->text(); - } - - // Shouldn't happen - return ""; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::modifyDest (str& dest) const -{ dest = dest.simplified(); - - // If the user doesn't want us to guess, stop right here. - if (net_guesspaths == false) - return; - - // Ensure .dat extension - if (dest.right (4) != ".dat") - { // Remove the existing extension, if any. It may be we're here over a - // typo in the .dat extension. - const int dotpos = dest.lastIndexOf ("."); - - if (dotpos != -1 && dotpos >= dest.length() - 4) - dest.chop (dest.length() - dotpos); - - dest += ".dat"; - } - - // If the part starts with s\ or s/, then use parts/s/. Same goes with - // 48\ and p/48/. - if (dest.left (2) == "s\\" || dest.left (2) == "s/") - { dest.remove (0, 2); - dest.prepend ("parts/s/"); - } elif (dest.left (3) == "48\\" || dest.left (3) == "48/") - - { dest.remove (0, 3); - dest.prepend ("p/48/"); - } - - /* Try determine where to put this part. We have four directories: - parts/, parts/s/, p/, and p/48/. If we haven't already specified - either parts/ or p/, we need to add it automatically. Part files - are numbers wit a possible u prefix for parts with unknown number - which can be followed by any of: - - c** (composites) - - d** (formed stickers) - - p** (patterns) - - a lowercase alphabetic letter for variants - - Subfiles (usually) have an s** prefix, in which case we use parts/s/. - Note that the regex starts with a '^' so it won't catch already fully - given part file names. */ - str partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*"; - str subpartRegex = partRegex + "s[0-9][0-9]+"; - - partRegex += "\\.dat$"; - subpartRegex += "\\.dat$"; - - if (QRegExp (subpartRegex).exactMatch (dest)) - dest.prepend ("parts/s/"); - elif (QRegExp (partRegex).exactMatch (dest)) - dest.prepend ("parts/"); - elif (dest.left (6) != "parts/" && dest.left (2) != "p/") - dest.prepend ("p/"); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloader::Source PartDownloader::getSource() const -{ return (Source) ui->source->currentIndex(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::sourceChanged (int i) -{ if (i == CustomURL) - ui->fileNameLabel->setText (tr ("URL:")); - else - ui->fileNameLabel->setText (tr ("File name:")); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::buttonClicked (QAbstractButton* btn) -{ if (btn == getButton (Close)) - { reject(); - } - elif (btn == getButton (Abort)) - { setAborted (true); - - for (PartDownloadRequest* req : m_requests) - req->abort(); - } - elif (btn == getButton (Download)) - { str dest = ui->fname->text(); - setPrimaryFile (null); - setAborted (false); - - if (getSource() == CustomURL) - dest = basename (getURL()); - - modifyDest (dest); - - if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest)) - { const str overwritemsg = fmt (tr ("%1 already exists in download directory. Overwrite?"), dest); - if (!confirm (tr ("Overwrite?"), overwritemsg)) - return; - } - - m_downloadButton->setEnabled (false); - ui->progress->setEnabled (true); - ui->fname->setEnabled (false); - ui->source->setEnabled (false); - downloadFile (dest, getURL(), true); - getButton (Close)->setEnabled (false); - getButton (Abort)->setEnabled (true); - getButton (Download)->setEnabled (false); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::downloadFile (str dest, str url, bool primary) -{ const int row = ui->progress->rowCount(); - - // Don't download files repeadetly. - if (m_filesToDownload.indexOf (dest) != -1) - return; - - modifyDest (dest); - log ("DOWNLOAD: %1 -> %2\n", url, PartDownloader::getDownloadPath() + DIRSLASH + dest); - PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); - - m_filesToDownload << dest; - m_requests << req; - ui->progress->insertRow (row); - req->setTableRow (row); - req->updateToTable(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::checkIfFinished() -{ bool failed = aborted(); - - // If there is some download still working, we're not finished. - for (PartDownloadRequest* req : m_requests) - { if (!req->isFinished()) - return; - - if (req->state() == PartDownloadRequest::Failed) - failed = true; - } - - for (PartDownloadRequest* req : m_requests) - delete req; - - m_requests.clear(); - - // Update everything now - if (primaryFile()) - { LDFile::setCurrent (primaryFile()); - reloadAllSubfiles(); - g_win->fullRefresh(); - 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) -{ typedef QDialogButtonBox QDBB; - alias btnbox = ui->buttonBox; - - switch (i) - { case Download: - { return m_downloadButton; - } - - case Abort: - { return qobject_cast<QPushButton*> (btnbox->button (QDBB::Abort)); - } - - case Close: - { return qobject_cast<QPushButton*> (btnbox->button (QDBB::Close)); - } - } - - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloadRequest::PartDownloadRequest (str url, str dest, bool primary, PartDownloader* parent) : - QObject (parent), - m_prompt (parent), - m_url (url), - m_dest (dest), - m_fpath (PartDownloader::getDownloadPath() + DIRSLASH + dest), - m_nam (new QNetworkAccessManager), - m_firstUpdate (true), - m_state (Requesting), - m_primary (primary), - m_fp (null) -{ - // Make sure that we have a valid destination. - str dirpath = dirname (m_fpath); - - 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)); - } - - m_reply = m_nam->get (QNetworkRequest (QUrl (url))); - connect (m_reply, SIGNAL (finished()), this, SLOT (downloadFinished())); - connect (m_reply, SIGNAL (readyRead()), this, SLOT (readyRead())); - connect (m_reply, SIGNAL (downloadProgress (qint64, qint64)), this, SLOT (downloadProgress (qint64, qint64))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloadRequest::~PartDownloadRequest() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::updateToTable() -{ const int labelcol = PartDownloader::PartLabelColumn, - progcol = PartDownloader::ProgressColumn; - QTableWidget* table = m_prompt->ui->progress; - QProgressBar* prog; - - switch (m_state) - { case Requesting: - case Downloading: - { prog = qobject_cast<QProgressBar*> (table->cellWidget (tableRow(), progcol)); - - if (!prog) - { prog = new QProgressBar; - table->setCellWidget (tableRow(), progcol, prog); - } - - prog->setRange (0, m_bytesTotal); - prog->setValue (m_bytesRead); - } break; - - case Finished: - case Failed: - { QLabel* lb = new QLabel ((m_state == Finished) ? "<b><span style=\"color: #080\">FINISHED</span></b>" : - "<b><span style=\"color: #800\">FAILED</span></b>"); - lb->setAlignment (Qt::AlignCenter); - table->setCellWidget (tableRow(), progcol, lb); - } break; - } - - QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (tableRow(), labelcol)); - - if (m_firstUpdate) - { lb = new QLabel (fmt ("<b>%1</b>", m_dest), table); - table->setCellWidget (tableRow(), labelcol, lb); - } - - // Make sure that the cell is big enough to contain the label - if (table->columnWidth (labelcol) < lb->width()) - table->setColumnWidth (labelcol, lb->width()); - - m_firstUpdate = false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::downloadFinished() -{ if (m_reply->error() != QNetworkReply::NoError) - { if (m_primary && !m_prompt->aborted()) - critical (m_reply->errorString()); - - m_state = Failed; - } elif (state() != Failed) - m_state = Finished; - - m_bytesRead = m_bytesTotal; - updateToTable(); - - if (m_fp) - { m_fp->close(); - delete m_fp; - m_fp = null; - - if (m_state == Failed) - QFile::remove (m_fpath); - } - - if (m_state != Finished) - { m_prompt->checkIfFinished(); - return; - } - - // Try to load this file now. - LDFile* f = openDATFile (m_fpath, false); - - if (!f) - return; - - f->setImplicit (!m_primary); - - // Iterate through this file and check for errors. If there's any that stems - // from unknown file references, try resolve that by downloading the reference. - // This is why downloading a part may end up downloading multiple files, as - // it resolves dependencies. - for (LDObject* obj : f->objects()) - { LDError* err = dynamic_cast<LDError*> (obj); - - if (!err || err->fileRef().isEmpty()) - continue; - - str dest = err->fileRef(); - m_prompt->modifyDest (dest); - m_prompt->downloadFile (dest, str (PartDownloader::k_UnofficialURL) + dest, false); - } - - if (m_primary) - { addRecentFile (m_fpath); - m_prompt->setPrimaryFile (f); - } - - m_prompt->checkIfFinished(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::downloadProgress (int64 recv, int64 total) -{ m_bytesRead = recv; - m_bytesTotal = total; - m_state = Downloading; - updateToTable(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::readyRead() -{ if (state() == Failed) - return; - - if (m_fp == null) - { m_fpath.replace ("\\", "/"); - - // We have already asked the user whether we can overwrite so we're good - // to go here. - m_fp = new QFile (m_fpath.toLocal8Bit()); - - if (!m_fp->open (QIODevice::WriteOnly)) - { critical (fmt (tr ("Couldn't open %1 for writing: %2"), m_fpath, strerror (errno))); - m_state = Failed; - m_reply->abort(); - updateToTable(); - m_prompt->checkIfFinished(); - return; - } - } - - m_fp->write (m_reply->readAll()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool PartDownloadRequest::isFinished() const -{ return m_state == Finished || m_state == Failed; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const PartDownloadRequest::State& PartDownloadRequest::state() const -{ return m_state; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::abort() -{ m_reply->abort(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (DownloadFrom, 0) -{ PartDownloader::k_download(); -}
--- a/src/download.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/download.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -20,10 +20,10 @@ #define LDFORGE_DOWNLOAD_H #include <QDialog> -#include "common.h" +#include "main.h" #include "types.h" -class LDFile; +class LDDocument; class QFile; class PartDownloadRequest; class Ui_DownloadFrom; @@ -35,74 +35,92 @@ // ============================================================================= // ----------------------------------------------------------------------------- class PartDownloader : public QDialog -{ Q_OBJECT - PROPERTY (LDFile*, primaryFile, setPrimaryFile) - PROPERTY (bool, aborted, setAborted) - - public: - constexpr static const char* k_UnofficialURL = "http://ldraw.org/library/unofficial/"; - +{ + typedefs: enum Source - { PartsTracker, + { + PartsTracker, CustomURL, }; enum Button - { Download, + { + Download, Abort, Close }; enum TableColumn - { PartLabelColumn, + { + PartLabelColumn, ProgressColumn, }; - explicit PartDownloader (QWidget* parent = null); - virtual ~PartDownloader(); - str getURL() const; - static str getDownloadPath(); - Source getSource() const; - void downloadFile (str dest, str url, bool primary); - void modifyDest (str& dest) const; - QPushButton* getButton (Button i); - static void k_download(); + using RequestList = QList<PartDownloadRequest*>; + + 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 sourceChanged (int i); - void checkIfFinished(); - void buttonClicked (QAbstractButton* btn); - - protected: - Ui_DownloadFrom* ui; - friend class PartDownloadRequest; - - private: - QList<str> m_filesToDownload; - QList<PartDownloadRequest*> m_requests; - QPushButton* m_downloadButton; + void buttonClicked (QAbstractButton* btn); + void checkIfFinished(); + void sourceChanged (int i); }; // ============================================================================= // ----------------------------------------------------------------------------- class PartDownloadRequest : public QObject -{ Q_OBJECT - PROPERTY (int, tableRow, setTableRow) +{ + 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: - enum State - { Requesting, - Downloading, - Finished, - Failed, - }; - - explicit PartDownloadRequest (str url, str dest, bool primary, PartDownloader* parent); + explicit PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent); PartDownloadRequest (const PartDownloadRequest&) = delete; virtual ~PartDownloadRequest(); void updateToTable(); bool isFinished() const; - const State& state() const; void operator= (const PartDownloadRequest&) = delete; @@ -111,17 +129,6 @@ void readyRead(); void downloadProgress (qint64 recv, qint64 total); void abort(); - - private: - PartDownloader* m_prompt; - str m_url, m_dest, m_fpath; - QNetworkAccessManager* m_nam; - QNetworkReply* m_reply; - bool m_firstUpdate; - State m_state; - int64 m_bytesRead, m_bytesTotal; - bool m_primary; - QFile* m_fp; }; #endif // LDFORGE_DOWNLOAD_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/extprogs.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,696 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QProcess> +#include <QTemporaryFile> +#include <QDialog> +#include <QDialogButtonBox> +#include <QSpinBox> +#include <QCheckBox> +#include <QComboBox> +#include <QGridLayout> +#include "main.h" +#include "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<LDSubfile*> (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<int> 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, {}); +}
--- a/src/extprogs.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,638 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QProcess> -#include <QTemporaryFile> -#include <QDialog> -#include <QDialogButtonBox> -#include <QSpinBox> -#include <QCheckBox> -#include <QComboBox> -#include <QGridLayout> -#include "common.h" -#include "config.h" -#include "misc.h" -#include "gui.h" -#include "file.h" -#include "widgets.h" -#include "history.h" -#include "ui_ytruder.h" -#include "ui_intersector.h" -#include "ui_rectifier.h" -#include "ui_coverer.h" -#include "ui_isecalc.h" -#include "ui_edger2.h" -#include "dialogs.h" - -enum extprog -{ Isecalc, - Intersector, - Coverer, - Ytruder, - Rectifier, - Edger2, -}; - -// ============================================================================= -// ----------------------------------------------------------------------------- -cfg (String, prog_isecalc, ""); -cfg (String, prog_intersector, ""); -cfg (String, prog_coverer, ""); -cfg (String, prog_ytruder, ""); -cfg (String, prog_rectifier, ""); -cfg (String, prog_edger2, ""); - -StringConfig* const g_extProgPaths[] = -{ &prog_isecalc, - &prog_intersector, - &prog_coverer, - &prog_ytruder, - &prog_rectifier, - &prog_edger2, -}; - -#ifndef _WIN32 -cfg (Bool, prog_isecalc_wine, false); -cfg (Bool, prog_intersector_wine, false); -cfg (Bool, prog_coverer_wine, false); -cfg (Bool, prog_ytruder_wine, false); -cfg (Bool, prog_rectifier_wine, false); -cfg (Bool, prog_edger2_wine, false); - -BoolConfig* const g_extProgWine[] = -{ &prog_isecalc_wine, - &prog_intersector_wine, - &prog_coverer_wine, - &prog_ytruder_wine, - &prog_rectifier_wine, - &prog_edger2_wine, -}; -#endif // _WIN32 - -const char* g_extProgNames[] = -{ "Isecalc", - "Intersector", - "Coverer", - "Ytruder", - "Rectifier", - "Edger2" -}; - -// ============================================================================= -// ----------------------------------------------------------------------------- -static bool checkProgPath (const extprog prog) -{ alias path = g_extProgPaths[prog]->value; - - if (path.length() > 0) - return true; - - ExtProgPathPrompt* dlg = new ExtProgPathPrompt (g_extProgNames[prog]); - - if (dlg->exec() && !dlg->getPath().isEmpty()) - { path = dlg->getPath(); - return true; - } - - return false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static str processErrorString (QProcess& proc) -{ switch (proc.error()) - { case QProcess::FailedToStart: - return "Failed to start (check your permissions)"; - - case QProcess::Crashed: - return "Crashed."; - - case QProcess::WriteError: - case QProcess::ReadError: - return "I/O error."; - - case QProcess::UnknownError: - return "Unknown error"; - - case QProcess::Timedout: - return fmt ("Timed out (30 seconds)"); - } - - return ""; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static bool mkTempFile (QTemporaryFile& tmp, str& fname) -{ if (!tmp.open()) - return false; - - fname = tmp.fileName(); - tmp.close(); - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void writeObjects (const QList<LDObject*>& objects, File& f) -{ for (LDObject* obj : objects) - { if (obj->getType() == LDObject::Subfile) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepInline); - - writeObjects (objs, f); - - for (LDObject* obj : objs) - delete obj; - } - else - f.write (obj->raw() + "\r\n"); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void writeObjects (const QList<LDObject*>& objects, str fname) -{ // Write the input file - File f (fname, File::Write); - - if (!f) - { critical (fmt ("Couldn't open temporary file %1 for writing.\n", fname)); - return; - } - - writeObjects (objects, f); - f.close(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void writeSelection (str fname) -{ writeObjects (selection(), fname); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void writeColorGroup (const short colnum, str fname) -{ QList<LDObject*> objects; - - for (LDObject* obj : LDFile::current()->objects()) - { if (obj->isColored() == false || obj->color() != colnum) - continue; - - objects << obj; - } - - writeObjects (objects, fname); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool runUtilityProcess (extprog prog, str path, str argvstr) -{ QTemporaryFile input, output; - str inputname, outputname; - QStringList argv = argvstr.split (" ", QString::SkipEmptyParts); - -#ifndef _WIN32 - if (*g_extProgWine[prog]) - { argv.insert (0, path); - path = "wine"; - } -#endif // _WIN32 - - log ("cmdline: %1 %2\n", path, argv.join (" ")); - - // Temporary files for stdin and stdout - if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname)) - return false; - - QProcess proc; - - // Init stdin - File stdinfp (inputname, File::Write); - - // Begin! - proc.setStandardInputFile (inputname); - proc.start (path, argv); - - // Write an enter, the utility tools all expect one - stdinfp.write ("\n"); - - // Wait while it runs - proc.waitForFinished(); - - str err = ""; - - if (proc.exitStatus() != QProcess::NormalExit) - err = processErrorString (proc); - - // Check the return code - if (proc.exitCode() != 0) - err = fmt ("Program exited abnormally (return code %1).", proc.exitCode()); - - if (err.length() > 0) - { critical (fmt ("%1 failed: %2\n", g_extProgNames[prog], err)); - return false; - } - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void insertOutput (str fname, bool replace, QList<short> colorsToReplace) -{ -#ifndef RELEASE - QFile::copy (fname, "./debug_lastOutput"); -#endif // RELEASE - - // Read the output file - File f (fname, File::Read); - - if (!f) - { critical (fmt ("Couldn't open temporary file %1 for reading.\n", fname)); - return; - } - - QList<LDObject*> objs = loadFileContents (&f, null); - - // If we replace the objects, delete the selection now. - if (replace) - g_win->deleteSelection(); - - for (const short colnum : colorsToReplace) - g_win->deleteByColor (colnum); - - // Insert the new objects - LDFile::current()->clearSelection(); - - for (LDObject * obj : objs) - { if (!obj->isScemantic()) - { delete obj; - continue; - } - - LDFile::current()->addObject (obj); - obj->select(); - } - - g_win->fullRefresh(); -} - -// ============================================================================= -// Interface for Ytruder -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Ytruder, 0) -{ setlocale (LC_ALL, "C"); - - if (!checkProgPath (Ytruder)) - return; - - QDialog* dlg = new QDialog; - Ui::YtruderUI ui; - ui.setupUi (dlg); - - if (!dlg->exec()) - return; - - // Read the user's choices - const enum { Distance, Symmetry, Projection, Radial } mode = - ui.mode_distance->isChecked() ? Distance : - ui.mode_symmetry->isChecked() ? Symmetry : - ui.mode_projection->isChecked() ? Projection : Radial; - - const Axis axis = - ui.axis_x->isChecked() ? X : - ui.axis_y->isChecked() ? Y : Z; - - const double depth = ui.planeDepth->value(), - condAngle = ui.condAngle->value(); - - QTemporaryFile indat, outdat; - str inDATName, outDATName; - - // Make temp files for the input and output files - if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) - return; - - // Compose the command-line arguments - str argv = join ( - { (axis == X) ? "-x" : (axis == Y) ? "-y" : "-z", - (mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r", - depth, - "-a", - condAngle, - inDATName, - outDATName - }); - - writeSelection (inDATName); - - if (!runUtilityProcess (Ytruder, prog_ytruder, argv)) - return; - - insertOutput (outDATName, false, {}); -} - -// ============================================================================= -// Rectifier interface -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Rectifier, 0) -{ setlocale (LC_ALL, "C"); - - if (!checkProgPath (Rectifier)) - return; - - QDialog* dlg = new QDialog; - Ui::RectifierUI ui; - ui.setupUi (dlg); - - if (!dlg->exec()) - return; - - QTemporaryFile indat, outdat; - str inDATName, outDATName; - - // Make temp files for the input and output files - if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) - return; - - // Compose arguments - str argv = join ( - { (!ui.cb_condense->isChecked()) ? "-q" : "", - (!ui.cb_subst->isChecked()) ? "-r" : "", - (ui.cb_condlineCheck->isChecked()) ? "-a" : "", - (ui.cb_colorize->isChecked()) ? "-c" : "", - "-t", - ui.dsb_coplthres->value(), - inDATName, - outDATName - }); - - writeSelection (inDATName); - - if (!runUtilityProcess (Rectifier, prog_rectifier, argv)) - return; - - insertOutput (outDATName, true, {}); -} - -// ============================================================================= -// Intersector interface -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Intersector, 0) -{ setlocale (LC_ALL, "C"); - - if (!checkProgPath (Intersector)) - return; - - QDialog* dlg = new QDialog; - Ui::IntersectorUI ui; - ui.setupUi (dlg); - - makeColorSelector (ui.cmb_incol); - makeColorSelector (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."); - - short inCol, cutCol; - const bool repeatInverse = ui.cb_repeat->isChecked(); - - forever - { if (!dlg->exec()) - return; - - inCol = ui.cmb_incol->itemData (ui.cmb_incol->currentIndex()).toInt(); - cutCol = ui.cmb_cutcol->itemData (ui.cmb_cutcol->currentIndex()).toInt(); - - if (inCol == cutCol) - { critical ("Cannot use the same color group for both input and cutter!"); - continue; - } - - break; - } - - // Five temporary files! - // indat = input group file - // cutdat = cutter group file - // outdat = primary output - // outdat2 = inverse output - // edgesdat = edges output (isecalc) - QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat; - str inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName; - - if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) || - !mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) || - !mkTempFile (edgesdat, edgesDATName)) - { return; - } - - str parms = join ( - { (ui.cb_colorize->isChecked()) ? "-c" : "", - (ui.cb_nocondense->isChecked()) ? "-t" : "", - "-s", - ui.dsb_prescale->value() - }); - - str argv_normal = join ( - { parms, - inDATName, - cutDATName, - outDATName - }); - - str argv_inverse = join ( - { parms, - cutDATName, - inDATName, - outDAT2Name - }); - - writeColorGroup (inCol, inDATName); - writeColorGroup (cutCol, cutDATName); - - if (!runUtilityProcess (Intersector, prog_intersector, argv_normal)) - return; - - insertOutput (outDATName, false, {inCol}); - - if (repeatInverse && runUtilityProcess (Intersector, prog_intersector, argv_inverse)) - insertOutput (outDAT2Name, false, {cutCol}); - - if ( - ui.cb_edges->isChecked() && - checkProgPath (Isecalc) && - runUtilityProcess (Isecalc, prog_isecalc, join ( {inDATName, cutDATName, edgesDATName})) - ) - insertOutput (edgesDATName, false, {}); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Coverer, 0) -{ setlocale (LC_ALL, "C"); - - if (!checkProgPath (Coverer)) - return; - - QDialog* dlg = new QDialog; - Ui::CovererUI ui; - ui.setupUi (dlg); - makeColorSelector (ui.cmb_col1); - makeColorSelector (ui.cmb_col2); - - short in1Col, in2Col; - - forever - { if (!dlg->exec()) - return; - - in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt(); - in2Col = ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt(); - - if (in1Col == in2Col) - { critical ("Cannot use the same color group for both input and cutter!"); - continue; - } - - break; - } - - QTemporaryFile in1dat, in2dat, outdat; - str in1DATName, in2DATName, outDATName; - - if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName)) - return; - - str argv = join ( - { (ui.cb_oldsweep->isChecked() ? "-s" : ""), - (ui.cb_reverse->isChecked() ? "-r" : ""), - (ui.dsb_segsplit->value() != 0 ? fmt ("-l %1", ui.dsb_segsplit->value()) : ""), - (ui.sb_bias->value() != 0 ? fmt ("-s %1", ui.sb_bias->value()) : ""), - in1DATName, - in2DATName, - outDATName - }); - - writeColorGroup (in1Col, in1DATName); - writeColorGroup (in2Col, in2DATName); - - if (!runUtilityProcess (Coverer, prog_coverer, argv)) - return; - - insertOutput (outDATName, false, {}); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Isecalc, 0) -{ setlocale (LC_ALL, "C"); - - if (!checkProgPath (Isecalc)) - return; - - Ui::IsecalcUI ui; - QDialog* dlg = new QDialog; - ui.setupUi (dlg); - - makeColorSelector (ui.cmb_col1); - makeColorSelector (ui.cmb_col2); - - short in1Col, in2Col; - - // Run the dialog and validate input - forever - { if (!dlg->exec()) - return; - - in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt(), - in2Col = ui.cmb_col1->itemData (ui.cmb_col2->currentIndex()).toInt(); - - if (in1Col == in2Col) - { critical ("Cannot use the same color group for both input and cutter!"); - continue; - } - - break; - } - - QTemporaryFile in1dat, in2dat, outdat; - str in1DATName, in2DATName, outDATName; - - if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName)) - return; - - str argv = join ( - { in1DATName, - in2DATName, - outDATName - }); - - writeColorGroup (in1Col, in1DATName); - writeColorGroup (in2Col, in2DATName); - runUtilityProcess (Isecalc, prog_isecalc, argv); - insertOutput (outDATName, false, {}); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Edger2, 0) -{ setlocale (LC_ALL, "C"); - - if (!checkProgPath (Edger2)) - return; - - QDialog* dlg = new QDialog; - Ui::Edger2Dialog ui; - ui.setupUi (dlg); - - if (!dlg->exec()) - return; - - QTemporaryFile in, out; - str inName, outName; - - if (!mkTempFile (in, inName) || !mkTempFile (out, outName)) - return; - - int unmatched = ui.unmatched->currentIndex(); - - str argv = join ( - { fmt ("-p %1", ui.precision->value()), - fmt ("-af %1", ui.flatAngle->value()), - fmt ("-ac %1", ui.condAngle->value()), - fmt ("-ae %1", ui.edgeAngle->value()), - ui.delLines->isChecked() ? "-de" : "", - ui.delCondLines->isChecked() ? "-dc" : "", - ui.colored->isChecked() ? "-c" : "", - ui.bfc->isChecked() ? "-b" : "", - ui.convex->isChecked() ? "-cx" : "", - ui.concave->isChecked() ? "-cv" : "", - unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""), - inName, - outName, - }); - - writeSelection (inName); - - if (!runUtilityProcess (Edger2, prog_edger2, argv)) - return; - - insertOutput (outName, true, {}); -}
--- a/src/file.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1194 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * ===================================================================== - * - * file.cpp: File I/O and management. - * - File loading, parsing, manipulation, saving, closing. - * - LDraw path verification. - */ - -#include <QMessageBox> -#include <QFileDialog> -#include <QDir> -#include <QApplication> -#include "common.h" -#include "config.h" -#include "file.h" -#include "misc.h" -#include "gui.h" -#include "history.h" -#include "dialogs.h" -#include "gldraw.h" -#include "gldata.h" -#include "moc_file.cpp" - -cfg (String, io_ldpath, ""); -cfg (List, io_recentfiles, {}); -extern_cfg (String, net_downloadpath); -extern_cfg (Bool, gl_logostuds); - -static bool g_loadingMainFile = false; -static const int g_MaxRecentFiles = 5; -static bool g_aborted = false; -static LDFile* g_logoedStud = null; -static LDFile* g_logoedStud2 = null; - -LDFile* LDFile::m_curfile = null; - -DEFINE_PROPERTY (QListWidgetItem*, LDFile, listItem, setListItem) - -// ============================================================================= -// ----------------------------------------------------------------------------- -namespace LDPaths -{ static str pathError; - - struct - { str LDConfigPath; - str partsPath, primsPath; - } pathInfo; - - void initPaths() - { if (!tryConfigure (io_ldpath)) - { LDrawPathDialog dlg (false); - - if (!dlg.exec()) - exit (0); - - io_ldpath = dlg.filename(); - } - } - - bool tryConfigure (str path) - { QDir dir; - - if (!dir.cd (path)) - { pathError = "Directory does not exist."; - return false; - } - - QStringList mustHave = { "LDConfig.ldr", "parts", "p" }; - QStringList contents = dir.entryList (mustHave); - - if (contents.size() != mustHave.size()) - { pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/."; - return false; - } - - pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path); - pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path); - pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path); - - return true; - } - - // Accessors - str getError() - { return pathError; - } - - str ldconfig() - { return pathInfo.LDConfigPath; - } - - str prims() - { return pathInfo.primsPath; - } - - str parts() - { return pathInfo.partsPath; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile::LDFile() -{ setImplicit (true); - setSavePos (-1); - setListItem (null); - m_history.setFile (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile::~LDFile() -{ // Clear everything from the model - for (LDObject* obj : objects()) - delete obj; - - // Clear the cache as well - for (LDObject* obj : cache()) - delete obj; - - // Remove this file from the list of files - g_loadedFiles.removeOne (this); - - // If we just closed the current file, we need to set the current - // file as something else. - if (this == LDFile::current()) - { 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 (LDFile* file : g_loadedFiles) - { if (!file->implicit()) - { LDFile::setCurrent (file); - found = true; - break; - } - } - - if (!found) - newFile(); - } - - g_win->updateFileList(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile* findLoadedFile (str name) -{ for (LDFile * file : g_loadedFiles) - if (!file->name().isEmpty() && file->getShortName() == name) - return file; - - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str dirname (str path) -{ long lastpos = path.lastIndexOf (DIRSLASH); - - if (lastpos > 0) - return path.left (lastpos); - -#ifndef _WIN32 - - if (path[0] == DIRSLASH_CHAR) - return DIRSLASH; - -#endif // _WIN32 - - return ""; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str basename (str path) -{ long lastpos = path.lastIndexOf (DIRSLASH); - - if (lastpos != -1) - return path.mid (lastpos + 1); - - return path; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File* openLDrawFile (str relpath, bool subdirs) -{ log ("Opening %1...\n", relpath); - File* f = new File; - str fullPath; - - // LDraw models use Windows-style path separators. If we're not on Windows, - // replace the path separator now before opening any files. -#ifndef WIN32 - relpath.replace ("\\", "/"); -#endif // WIN32 - - if (LDFile::current()) - { // First, try find the file in the current model's file path. We want a file - // in the immediate vicinity of the current model to override stock LDraw stuff. - str partpath = fmt ("%1" DIRSLASH "%2", dirname (LDFile::current()->name()), relpath); - - if (f->open (partpath, File::Read)) - return f; - } - - if (f->open (relpath, File::Read)) - return f; - - // Try with just the LDraw path first - fullPath = fmt ("%1" DIRSLASH "%2", io_ldpath, relpath); - - if (f->open (fullPath, File::Read)) - return f; - - if (subdirs) - { // Look in sub-directories: parts and p. Also look in net_downloadpath, since that's - // where we download parts from the PT to. - for (const str& topdir : initlist<str> ({ io_ldpath, net_downloadpath })) - { for (const str& subdir : initlist<str> ({ "parts", "p" })) - { fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); - - if (f->open (fullPath, File::Read)) - return f; - } - } - } - - // Did not find the file. - log ("Could not find %1.\n", relpath); - delete f; - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void FileLoader::start() -{ setDone (false); - setProgress (0); - setAborted (false); - - if (concurrent()) - { g_aborted = false; - - // Show a progress dialog if we're loading the main file here so we can - // show progress updates and keep the WM posted that we're still here. - // Of course we cannot exec() the dialog because then the dialog would - // block. - dlg = new OpenProgressDialog (g_win); - dlg->setNumLines (lines().size()); - dlg->setModal (true); - dlg->show(); - - // Connect the loader in so we can show updates - connect (this, SIGNAL (workDone()), dlg, SLOT (accept())); - connect (dlg, SIGNAL (rejected()), this, SLOT (abort())); - } - else - dlg = null; - - // Begin working - work (0); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void FileLoader::work (int i) -{ // User wishes to abort, so stop here now. - if (aborted()) - { for (LDObject* obj : m_objs) - delete obj; - - m_objs.clear(); - setDone (true); - return; - } - - // Parse up to 300 lines per iteration - int max = i + 300; - - for (; i < max && i < (int) lines().size(); ++i) - { str line = lines() [i]; - - // Trim the trailing newline - qchar c; - while (!line.isEmpty() && ((c = line[line.length() - 1]) == '\n' || c == '\r')) - line.chop (1); - - LDObject* obj = parseLine (line); - - // Check for parse errors and warn about tthem - if (obj->getType() == LDObject::Error) - { log ("Couldn't parse line #%1: %2", m_progress + 1, static_cast<LDError*> (obj)->reason); - - if (m_warningsPointer) - (*m_warningsPointer)++; - } - - m_objs << obj; - setProgress (i); - - // If we have a dialog pointer, update the progress now - if (concurrent()) - dlg->updateProgress (i); - } - - // If we're done now, tell the environment we're done and stop. - if (i >= ((int) lines().size()) - 1) - { emit workDone(); - setDone (true); - return; - } - - // Otherwise, continue, by recursing back. - if (!done()) - { // 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 (concurrent()) - QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i)); - else - work (i + 1); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void FileLoader::abort() -{ setAborted (true); - - if (concurrent()) - g_aborted = true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok) -{ QList<str> lines; - QList<LDObject*> objs; - - if (numWarnings) - *numWarnings = 0; - - // Calculate the amount of lines - for (str line : *f) - lines << line; - - f->rewind(); - - FileLoader* loader = new FileLoader; - loader->setWarningsPointer (numWarnings); - loader->setLines (lines); - loader->setConcurrent (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->done() == false) - qApp->processEvents(); - - // If we wanted the success value, supply that now - if (ok) - *ok = !loader->aborted(); - - objs = loader->objs(); - return objs; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile* openDATFile (str path, bool search) -{ // Convert the file name to lowercase since some parts contain uppercase - // file names. I'll assume here that the library will always use lowercase - // file names for the actual parts.. - File* f; - - if (search) - f = openLDrawFile (path.toLower(), true); - else - { f = new File (path, File::Read); - - if (!*f) - { delete f; - return null; - } - } - - if (!f) - return null; - - LDFile* load = new LDFile; - load->setName (path); - - int numWarnings; - bool ok; - QList<LDObject*> objs = loadFileContents (f, &numWarnings, &ok); - - if (!ok) - return null; - - for (LDObject* obj : objs) - load->addObject (obj); - - delete f; - g_loadedFiles << load; - - if (g_loadingMainFile) - { LDFile::setCurrent (load); - g_win->R()->setFile (load); - log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); - } - - return load; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDFile::safeToClose() -{ typedef QMessageBox msgbox; - setlocale (LC_ALL, "C"); - - // If we have unsaved changes, warn and give the option of saving. - if (hasUnsavedChanges()) - { str message = fmt ("There are unsaved changes to %1. Should it be saved?", - (name().length() > 0) ? name() : "<anonymous>"); - - int button = msgbox::question (g_win, "Unsaved Changes", message, - (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); - - switch (button) - { case msgbox::Yes: - - // If we don't have a file path yet, we have to ask the user for one. - if (name().length() == 0) - { str newpath = QFileDialog::getSaveFileName (g_win, "Save As", - LDFile::current()->name(), "LDraw files (*.dat *.ldr)"); - - if (newpath.length() == 0) - return false; - - setName (newpath); - } - - if (!save()) - { message = fmt (QObject::tr ("Failed to save %1: %2\nDo you still want to close?"), - name(), strerror (errno)); - - if (msgbox::critical (g_win, "Save Failure", message, - (msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No) - { return false; - } - } - - break; - - case msgbox::Cancel: - return false; - - default: - break; - } - } - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void closeAll() -{ // Remove all loaded files and the objects they contain - QList<LDFile*> files = g_loadedFiles; - -for (LDFile * file : files) - delete file; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void newFile() -{ // Create a new anonymous file and set it to our current - LDFile* f = new LDFile; - f->setName (""); - f->setImplicit (false); - g_loadedFiles << f; - LDFile::setCurrent (f); - - LDFile::closeInitialFile(); - - g_win->R()->setFile (f); - g_win->fullRefresh(); - g_win->updateTitle(); - f->history().updateActions(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void addRecentFile (str path) -{ alias rfiles = io_recentfiles.value; - int idx = rfiles.indexOf (path); - - // If this file already is in the list, pop it out. - if (idx != -1) - { if (rfiles.size() == 1) - return; // only recent file - abort and do nothing - - // Pop it out. - rfiles.removeAt (idx); - } - - // If there's too many recent files, drop one out. - while (rfiles.size() > (g_MaxRecentFiles - 1)) - rfiles.removeAt (0); - - // Add the file - rfiles << path; - - Config::save(); - g_win->updateRecentFilesMenu(); -} - -// ============================================================================= -// Open an LDraw file and set it as the main model -// ----------------------------------------------------------------------------- -void openMainFile (str path) -{ g_loadingMainFile = true; - LDFile* file = openDATFile (path, false); - - if (!file) - { // Loading failed, thus drop down to a new file since we - // closed everything prior. - newFile(); - - if (!g_aborted) - { // Tell the user loading failed. - setlocale (LC_ALL, "C"); - critical (fmt (QObject::tr ("Failed to open %1: %2"), path, strerror (errno))); - } - - g_loadingMainFile = false; - return; - } - - file->setImplicit (false); - - // If we have an anonymous, unchanged file open as the only open file - // (aside of the one we just opened), close it now. - LDFile::closeInitialFile(); - - // Rebuild the object tree view now. - LDFile::setCurrent (file); - g_win->fullRefresh(); - - // Add it to the recent files list. - addRecentFile (path); - g_loadingMainFile = false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDFile::save (str savepath) -{ if (!savepath.length()) - savepath = name(); - - File f (savepath, File::Write); - - if (!f) - return false; - - // If the second object in the list holds the file name, update that now. - // Only do this if the file is explicitly open. If it's saved into a directory - // called "s" or "48", prepend that into the name. - LDComment* fpathComment = null; - LDObject* first = object (1); - - if (!implicit() && first != null && first->getType() == LDObject::Comment) - { fpathComment = static_cast<LDComment*> (first); - - if (fpathComment->text.left (6) == "Name: ") - { str newname; - str dir = basename (dirname (savepath)); - - if (dir == "s" || dir == "48") - newname = dir + "\\"; - - newname += basename (savepath); - fpathComment->text = fmt ("Name: %1", newname); - g_win->buildObjList(); - } - } - - // File is open, now save the model to it. Note that LDraw requires files to - // have DOS line endings, so we terminate the lines with \r\n. - for (LDObject* obj : objects()) - f.write (obj->raw() + "\r\n"); - - // File is saved, now clean up. - f.close(); - - // We have successfully saved, update the save position now. - setSavePos (history().pos()); - setName (savepath); - - g_win->updateFileListItem (this); - g_win->updateTitle(); - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -#define CHECK_TOKEN_COUNT(N) \ - if (tokens.size() != N) \ - return new LDError (line, "Bad amount of tokens"); - -#define CHECK_TOKEN_NUMBERS(MIN, MAX) \ - for (int i = MIN; i <= MAX; ++i) \ - if (!numeric (tokens[i])) \ - return new LDError (line, fmt ("Token #%1 was `%2`, expected a number", (i + 1), tokens[i])); - -// ============================================================================= -// ----------------------------------------------------------------------------- -static vertex parseVertex (QStringList& s, const int n) -{ vertex v; - - for (const Axis ax : g_Axes) - v[ax] = s[n + ax].toDouble(); - - return v; -} - -// ============================================================================= -// This is the LDraw code parser function. It takes in a string containing LDraw -// code and returns the object parsed from it. parseLine never returns null, -// the object will be LDError if it could not be parsed properly. -// ----------------------------------------------------------------------------- -LDObject* parseLine (str line) -{ QStringList tokens = line.split (" ", str::SkipEmptyParts); - - if (tokens.size() <= 0) - { // Line was empty, or only consisted of whitespace - return new LDEmpty; - } - - if (tokens[0].length() != 1 || tokens[0][0].isDigit() == false) - return new LDError (line, "Illogical line code"); - - int num = tokens[0][0].digitValue(); - - switch (num) - { case 0: - { // Comment - str comm = line.mid (line.indexOf ("0") + 1); - - // Remove any leading whitespace - while (comm[0] == ' ') - comm.remove (0, 1); - - // Handle BFC statements - if (tokens.size() > 2 && tokens[1] == "BFC") - { for (short i = 0; i < LDBFC::NumStatements; ++i) - if (comm == fmt ("BFC %1", LDBFC::statements [i])) - return new LDBFC ( (LDBFC::Type) i); - - // MLCAD is notorious for stuffing these statements in parts it - // creates. The above block only handles valid statements, so we - // need to handle MLCAD-style invertnext, clip and noclip separately. - struct - { const char* a; - LDBFC::Type b; - } BFCData[] = - { { "INVERTNEXT", LDBFC::InvertNext }, - { "NOCLIP", LDBFC::NoClip }, - { "CLIP", LDBFC::Clip } - }; - - for (const auto & i : BFCData) - if (comm == fmt ("BFC CERTIFY %1", i.a)) - return new LDBFC (i.b); - } - - if (tokens.size() > 2 && tokens[1] == "!LDFORGE") - { // Handle LDForge-specific types, they're embedded into comments too - if (tokens[2] == "VERTEX") - { // Vertex (0 !LDFORGE VERTEX) - CHECK_TOKEN_COUNT (7) - CHECK_TOKEN_NUMBERS (3, 6) - - LDVertex* obj = new LDVertex; - obj->setColor (tokens[3].toLong()); - - for (const Axis ax : g_Axes) - obj->pos[ax] = tokens[4 + ax].toDouble(); // 4 - 6 - - return obj; - } elif (tokens[2] == "OVERLAY") - - { CHECK_TOKEN_COUNT (9); - CHECK_TOKEN_NUMBERS (5, 8) - - LDOverlay* obj = new LDOverlay; - obj->setFilename (tokens[3]); - obj->setCamera (tokens[4].toLong()); - obj->setX (tokens[5].toLong()); - obj->setY (tokens[6].toLong()); - obj->setWidth (tokens[7].toLong()); - obj->setHeight (tokens[8].toLong()); - return obj; - } - } - - // Just a regular comment: - LDComment* obj = new LDComment; - obj->text = comm; - return obj; - } - - case 1: - { // Subfile - CHECK_TOKEN_COUNT (15) - CHECK_TOKEN_NUMBERS (1, 13) - - // Try open the file. Disable g_loadingMainFile temporarily since we're - // not loading the main file now, but the subfile in question. - bool tmp = g_loadingMainFile; - g_loadingMainFile = false; - LDFile* load = getFile (tokens[14]); - g_loadingMainFile = tmp; - - // If we cannot open the file, mark it an error - if (!load) - { LDError* obj = new LDError (line, fmt ("Could not open %1", tokens[14])); - obj->setFileRef (tokens[14]); - return obj; - } - - LDSubfile* obj = new LDSubfile; - obj->setColor (tokens[1].toLong()); - obj->setPosition (parseVertex (tokens, 2)); // 2 - 4 - - matrix transform; - - for (short i = 0; i < 9; ++i) - transform[i] = tokens[i + 5].toDouble(); // 5 - 13 - - obj->setTransform (transform); - obj->setFileInfo (load); - return obj; - } - - case 2: - { CHECK_TOKEN_COUNT (8) - CHECK_TOKEN_NUMBERS (1, 7) - - // Line - LDLine* obj = new LDLine; - obj->setColor (tokens[1].toLong()); - - for (short i = 0; i < 2; ++i) - obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 7 - - return obj; - } - - case 3: - { CHECK_TOKEN_COUNT (11) - CHECK_TOKEN_NUMBERS (1, 10) - - // Triangle - LDTriangle* obj = new LDTriangle; - obj->setColor (tokens[1].toLong()); - - for (short i = 0; i < 3; ++i) - obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 10 - - return obj; - } - - case 4: - case 5: - { CHECK_TOKEN_COUNT (14) - CHECK_TOKEN_NUMBERS (1, 13) - - // Quadrilateral / Conditional line - LDObject* obj; - - if (num == 4) - obj = new LDQuad; - else - obj = new LDCndLine; - - obj->setColor (tokens[1].toLong()); - - for (short i = 0; i < 4; ++i) - obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 13 - - return obj; - } - - default: // Strange line we couldn't parse - return new LDError (line, "Unknown line code number"); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile* getFile (str filename) -{ // Try find the file in the list of loaded files - LDFile* load = findLoadedFile (filename); - - // If it's not loaded, try open it - if (!load) - load = openDATFile (filename, true); - - return load; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void reloadAllSubfiles() -{ if (!LDFile::current()) - return; - - g_loadedFiles.clear(); - g_loadedFiles << LDFile::current(); - - // Go through all objects in the current file and reload the subfiles -for (LDObject * obj : LDFile::current()->objects()) - { if (obj->getType() == LDObject::Subfile) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - LDFile* fileInfo = getFile (ref->fileInfo()->name()); - - if (fileInfo) - ref->setFileInfo (fileInfo); - else - ref->replace (new LDError (ref->raw(), "Could not open referred file")); - } - - // Reparse gibberish files. It could be that they are invalid because - // of loading errors. Circumstances may be different now. - if (obj->getType() == LDObject::Error) - obj->replace (parseLine (static_cast<LDError*> (obj)->contents)); - } - - // Close all files left unused - LDFile::closeUnused(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int LDFile::addObject (LDObject* obj) -{ m_history.add (new AddHistory (objects().size(), obj)); - m_objects << obj; - - if (obj->getType() == LDObject::Vertex) - m_vertices << obj; - - obj->setFile (this); - return numObjs() - 1; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::addObjects (const QList<LDObject*> objs) -{ for (LDObject * obj : objs) - if (obj) - addObject (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::insertObj (int pos, LDObject* obj) -{ m_history.add (new AddHistory (pos, obj)); - m_objects.insert (pos, obj); - obj->setFile (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::forgetObject (LDObject* obj) -{ int idx = obj->getIndex(); - m_history.add (new DelHistory (idx, obj)); - m_objects.removeAt (idx); - obj->setFile (null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool safeToCloseAll() -{ for (LDFile* f : g_loadedFiles) - if (!f->safeToClose()) - return false; - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::setObject (int idx, LDObject* obj) -{ assert (idx < numObjs()); - - // Mark this change to history - str oldcode = object (idx)->raw(); - str newcode = obj->raw(); - m_history << new EditHistory (idx, oldcode, newcode); - - obj->setFile (this); - m_objects[idx] = obj; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static QList<LDFile*> getFilesUsed (LDFile* node) -{ QList<LDFile*> filesUsed; - -for (LDObject * obj : node->objects()) - { if (obj->getType() != LDObject::Subfile) - continue; - - LDSubfile* ref = static_cast<LDSubfile*> (obj); - filesUsed << ref->fileInfo(); - filesUsed << getFilesUsed (ref->fileInfo()); - } - - return filesUsed; -} - -// ============================================================================= -// Find out which files are unused and close them. -// ----------------------------------------------------------------------------- -void LDFile::closeUnused() -{ QList<LDFile*> filesUsed = getFilesUsed (LDFile::current()); - - // Anything that's explicitly opened must not be closed - for (LDFile* file : g_loadedFiles) - if (!file->implicit()) - filesUsed << file; - - // Remove duplicated entries - removeDuplicates (filesUsed); - - // Close all open files that aren't in filesUsed - for (LDFile* file : g_loadedFiles) - { bool isused = false; - - for (LDFile* usedFile : filesUsed) - { if (file == usedFile) - { isused = true; - break; - } - } - - if (!isused) - delete file; - } - - g_loadedFiles.clear(); - g_loadedFiles << filesUsed; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDFile::object (int pos) const -{ if (m_objects.size() <= pos) - return null; - - return m_objects[pos]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDFile::obj (int pos) const -{ return object (pos); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int LDFile::numObjs() const -{ return objects().size(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDFile::hasUnsavedChanges() const -{ return !implicit() && history().pos() != savePos(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDFile::getShortName() -{ if (!name().isEmpty()) - return basename (name()); - - if (!defaultName().isEmpty()) - return defaultName(); - - return tr ("<anonymous>"); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDObject*> LDFile::inlineContents (LDSubfile::InlineFlags flags) -{ // Possibly substitute with logoed studs: - // stud.dat -> stud-logo.dat - // stud2.dat -> stud-logo2.dat - if (gl_logostuds && (flags & LDSubfile::RendererInline)) - { if (name() == "stud.dat" && g_logoedStud) - return g_logoedStud->inlineContents (flags); - - elif (name() == "stud2.dat" && g_logoedStud2) - return g_logoedStud2->inlineContents (flags); - } - - QList<LDObject*> objs, objcache; - - bool deep = flags & LDSubfile::DeepInline, - doCache = flags & LDSubfile::CacheInline; - - // If we have this cached, just clone that - if (deep && cache().size()) -{ for (LDObject * obj : cache()) - objs << obj->clone(); - } - else - { if (!deep) - doCache = false; - - for (LDObject * obj : objects()) - { // Skip those without scemantic meaning - if (!obj->isScemantic()) - continue; - - // Got another sub-file reference, inline it if we're deep-inlining. If not, - // just add it into the objects normally. Also, we only cache immediate - // subfiles and this is not one. Yay, recursion! - if (deep && obj->getType() == LDObject::Subfile) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - - // We only want to cache immediate subfiles, so shed the caching - // flag when recursing deeper in hierarchy. - QList<LDObject*> otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline)); - - for (LDObject * otherobj : otherobjs) - { // Cache this object, if desired - if (doCache) - objcache << otherobj->clone(); - - objs << otherobj; - } - } - else - { if (doCache) - objcache << obj->clone(); - - objs << obj->clone(); - } - } - - if (doCache) - setCache (objcache); - } - - return objs; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile* LDFile::current() -{ return m_curfile; -} - -// ============================================================================= -// Sets the given file as the current one on display. At some point in time this -// was an operation completely unheard of. ;) -// -// FIXME: f can be temporarily null. This probably should not be the case. -// ----------------------------------------------------------------------------- -void LDFile::setCurrent (LDFile* f) -{ // Implicit files were loaded for caching purposes and must never be set - // current. - if (f && f->implicit()) - return; - - m_curfile = f; - - if (g_win && f) - { // A ton of stuff needs to be updated - g_win->updateFileListItem (f); - g_win->buildObjList(); - g_win->updateTitle(); - g_vertexCompiler.needMerge(); - g_win->R()->setFile (f); - g_win->R()->resetAllAngles(); - g_win->R()->repaint(); - - log ("Changed file to %1", f->getShortName()); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int LDFile::countExplicitFiles() -{ int count = 0; - - for (LDFile* f : g_loadedFiles) - if (f->implicit() == false) - count++; - - return count; -} - -// ============================================================================= -// This little beauty closes the initial file that was open at first when opening -// a new file over it. -// ----------------------------------------------------------------------------- -void LDFile::closeInitialFile() -{ if ( - countExplicitFiles() == 2 && - g_loadedFiles[0]->name() == "" && - !g_loadedFiles[0]->hasUnsavedChanges() - ) - delete g_loadedFiles[0]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void loadLogoedStuds() -{ log ("Loading logoed studs...\n"); - - delete g_logoedStud; - delete g_logoedStud2; - - g_logoedStud = openDATFile ("stud-logo.dat", true); - g_logoedStud2 = openDATFile ("stud2-logo.dat", true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::addToSelection (LDObject* obj) // [protected] -{ if (obj->selected()) - return; - - assert (obj->file() == this); - m_sel << obj; - obj->setSelected (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::removeFromSelection (LDObject* obj) // [protected] -{ if (!obj->selected()) - return; - - assert (obj->file() == this); - m_sel.removeOne (obj); - obj->setSelected (false); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDFile::clearSelection() -{ for (LDObject* obj : m_sel) - removeFromSelection (obj); - - assert (m_sel.isEmpty()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const QList<LDObject*>& LDFile::selection() const -{ return m_sel; -}
--- a/src/file.h Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,212 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef FILE_H -#define FILE_H - -#include "common.h" -#include "ldtypes.h" -#include "history.h" -#include <QObject> - -#define curfile LDFile::current() - -class History; -class OpenProgressDialog; - -namespace LDPaths -{ void initPaths(); - bool tryConfigure (str path); - - str ldconfig(); - str prims(); - str parts(); - str getError(); -} - -// ============================================================================= -// LDFile -// -// The LDFile class stores a file 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 LDFile : public QObject -{ Q_OBJECT - READ_PROPERTY (QList<LDObject*>, objects, setObjects) - READ_PROPERTY (History, history, setHistory) - READ_PROPERTY (QList<LDObject*>, vertices, setVertices) - PROPERTY (str, name, setName) - PROPERTY (str, defaultName, setDefaultName) - PROPERTY (bool, implicit, setImplicit) - PROPERTY (QList<LDObject*>, cache, setCache) - PROPERTY (long, savePos, setSavePos) - DECLARE_PROPERTY (QListWidgetItem*, listItem, setListItem) - - public: - LDFile(); - ~LDFile(); - - int addObject (LDObject* obj); // Adds an object to this file at the end of the file. - void addObjects (const QList<LDObject*> objs); - void clearSelection(); - void forgetObject (LDObject* obj); // Deletes the given object from the object chain. - str getShortName(); - const QList<LDObject*>& selection() const; - bool hasUnsavedChanges() const; // Does this file have unsaved changes? - QList<LDObject*> inlineContents (LDSubfile::InlineFlags flags); - void insertObj (int pos, LDObject* obj); - int numObjs() const; - LDObject* object (int pos) const; - LDObject* obj (int pos) const; - bool save (str path = ""); // Saves this file to disk. - bool safeToClose(); // Perform safety checks. Do this before closing any files! - void setObject (int idx, LDObject* obj); - - inline LDFile& operator<< (LDObject* obj) - { addObject (obj); - return *this; - } - - inline void openHistory() - { m_history.open(); - } - - inline void closeHistory() - { m_history.close(); - } - - 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 LDFile* current(); - static void setCurrent (LDFile* f); - static void closeInitialFile(); - static int countExplicitFiles(); - - protected: - void addToSelection (LDObject* obj); - void removeFromSelection (LDObject* obj); - friend class LDObject; - - private: - QList<LDObject*> m_sel; - - static LDFile* m_curfile; -}; - -// 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 (str path); - -// Finds an OpenFile by name or null if not open -LDFile* findLoadedFile (str name); - -// Opens the given file and parses the LDraw code within. Returns a pointer -// to the opened file or null on error. -LDFile* openDATFile (str path, bool search); - -// Opens the given file and returns a pointer to it, potentially looking in /parts and /p -File* openLDrawFile (str relpath, bool subdirs); - -// 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 (str line); - -// Retrieves the pointer to - or loads - the given subfile. -LDFile* getFile (str filename); - -// Re-caches all subfiles. -void reloadAllSubfiles(); - -// Is it safe to close all files? -bool safeToCloseAll(); - -QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok = null); - -extern QList<LDFile*> g_loadedFiles; - -inline const QList<LDObject*>& selection() -{ return LDFile::current()->selection(); -} - -void addRecentFile (str path); -void loadLogoedStuds(); -str basename (str path); -str dirname (str path); - -extern QList<LDFile*> 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 FileLoader : public QObject -{ Q_OBJECT - READ_PROPERTY (QList<LDObject*>, objs, setObjects) - READ_PROPERTY (bool, done, setDone) - READ_PROPERTY (int, progress, setProgress) - READ_PROPERTY (bool, aborted, setAborted) - PROPERTY (QList<str>, lines, setLines) - PROPERTY (int*, warningsPointer, setWarningsPointer) - PROPERTY (bool, concurrent, setConcurrent) - - public slots: - void start(); - void abort(); - - private: - OpenProgressDialog* dlg; - - private slots: - void work (int i); - - signals: - void progressUpdate (int progress); - void workDone(); -}; - -#endif // FILE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gldraw.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,1993 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QGLWidget> +#include <QWheelEvent> +#include <QMouseEvent> +#include <QContextMenuEvent> +#include <QInputDialog> +#include <QToolTip> +#include <QTimer> +#include <GL/glu.h> + +#include "main.h" +#include "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 "gldata.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 +}; + +// ============================================================================= +// +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); + glLineStipple (1, 0x6666); + + setAutoFillBackground (false); + setMouseTracking (true); + setFocusPolicy (Qt::WheelFocus); + + g_vertexCompiler.compileFile(); +} + +// ============================================================================= +// +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::refresh() +{ + update(); + swapBuffers(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::hardRefresh() +{ + g_vertexCompiler.compileFile(); + 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); + } + + // Draw the VAOs now + glEnableClientState (GL_VERTEX_ARRAY); + glEnableClientState (GL_COLOR_ARRAY); + glDisableClientState (GL_NORMAL_ARRAY); + + if (gl_colorbfc) { + glEnable (GL_CULL_FACE); + glCullFace (GL_CCW); + } else + glDisable (GL_CULL_FACE); + + drawVAOs ((isPicking() ? PickArray : gl_colorbfc ? BFCArray : MainArray), GL_TRIANGLES); + drawVAOs ((isPicking() ? EdgePickArray : EdgeArray), GL_LINES); + + // Draw conditional lines. Note that conditional lines are drawn into + // EdgePickArray in the picking scene, so when picking, don't do anything + // here. + if (!isPicking()) + { + glEnable (GL_LINE_STIPPLE); + drawVAOs (CondEdgeArray, GL_LINES); + glDisable (GL_LINE_STIPPLE); + } + + glPopMatrix(); + glMatrixMode (GL_MODELVIEW); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); +} + +// ============================================================================= +// +void GLRenderer::drawVAOs (VAOType arrayType, GLenum type) +{ + const VertexCompiler::Array* array = g_vertexCompiler.getMergedBuffer (arrayType); + glVertexPointer (3, GL_FLOAT, sizeof (VertexCompiler::Vertex), &array->data()[0].x); + glColorPointer (4, GL_UNSIGNED_BYTE, sizeof (VertexCompiler::Vertex), &array->data()[0].color); + glDrawArrays (type, 0, array->writtenSize() / sizeof (VertexCompiler::Vertex)); +} + +// ============================================================================= +// 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<Vertex> verts, verts2; + const double dist0 = getCircleDrawDist (0), + dist1 = (m_drawedVerts.size() >= 2) ? getCircleDrawDist (1) : -1; + const int segs = lores; + const double angleUnit = (2 * pi) / segs; + Axis relX, relY; + QVector<QPoint> ringpoints, circlepoints, circle2points; + + getRelativeAxes (relX, relY); + + // Calculate the preview positions of vertices + for (int i = 0; i < segs; ++i) + { + Vertex v = g_origin; + v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist0); + v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist0); + verts << v; + + if (dist1 != -1) + { + v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist1); + v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist1); + verts2 << v; + } + } + + int i = 0; + for (const Vertex& v : verts + verts2) + { + // Calculate the 2D point of the vertex + QPoint point = coordconv3_2 (v); + + // Draw a green blip at where it is + drawBlip (paint, point); + + // Add it to the list of points for the green ring fill. + ringpoints << point; + + // Also add the circle points to separate lists + if (i < verts.size()) + circlepoints << point; + else + circle2points << point; + + ++i; + } + + // Insert the first point as the seventeenth one so that + // the ring polygon is closed properly. + if (ringpoints.size() >= 16) + ringpoints.insert (16, ringpoints[0]); + + // Same for the outer ring. Note that the indices are offset by 1 + // because of the insertion done above bumps the values. + if (ringpoints.size() >= 33) + ringpoints.insert (33, ringpoints[17]); + + // Draw the ring + paint.setBrush ((m_drawedVerts.size() >= 2) ? polybrush : Qt::NoBrush); + paint.setPen (Qt::NoPen); + paint.drawPolygon (QPolygon (ringpoints)); + + // Draw the circles + paint.setBrush (Qt::NoBrush); + paint.setPen (linepen); + paint.drawPolygon (QPolygon (circlepoints)); + paint.drawPolygon (QPolygon (circle2points)); + + { // Draw the current radius in the middle of the circle. + QPoint origin = coordconv3_2 (m_drawedVerts[0]); + 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() +{ + g_vertexCompiler.compileFile(); +} + +// ============================================================================= +// +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<double> (gl_linethickness, 6.5f)); + + // Clear the selection if we do not wish to add to it. + if (!m_addpick) + { + LDObjectList oldsel = selection(); + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : oldsel) + compileObject (obj); + } + + setPicking (true); + + // Paint the picking scene + glDisable (GL_DITHER); + glClearColor (1.0f, 1.0f, 1.0f, 1.0f); + drawGLScene(); + + int x0 = mouseX, + y0 = mouseY; + int x1, y1; + + // Determine how big an area to read - with range picking, we pick by + // the area given, with single pixel picking, we use an 1 x 1 area. + if (m_rangepick) + { + x1 = m_rangeStart.x(); + y1 = m_rangeStart.y(); + } + else + { + x1 = x0 + 1; + y1 = y0 + 1; + } + + // x0 and y0 must be less than x1 and y1, respectively. + if (x0 > x1) + qSwap (x0, x1); + + if (y0 > y1) + qSwap (y0, y1); + + // Clamp the values to ensure they're within bounds + x0 = max (0, x0); + y0 = max (0, y0); + x1 = min (x1, m_width); + y1 = min (y1, m_height); + const int areawidth = (x1 - x0); + const int areaheight = (y1 - y0); + const qint32 numpixels = areawidth * areaheight; + + // Allocate space for the pixel data. + uchar* const pixeldata = new uchar[4 * numpixels]; + uchar* pixelptr = &pixeldata[0]; + + // Read pixels from the color buffer. + glReadPixels (x0, m_height - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata); + + LDObject* removedObj = null; + + // Go through each pixel read and add them to the selection. + for (qint32 i = 0; i < numpixels; ++i) + { + qint32 idx = + (*(pixelptr + 0) * 0x10000) + + (*(pixelptr + 1) * 0x00100) + + (*(pixelptr + 2) * 0x00001); + pixelptr += 4; + + if (idx == 0xFFFFFF) + continue; // White is background; skip + + LDObject* obj = LDObject::fromID (idx); + assert (obj != null); + + // If this is an additive single pick and the object is currently selected, + // we remove it from selection instead. + if (!m_rangepick && m_addpick) + { + if (obj->isSelected()) + { + obj->unselect(); + removedObj = obj; + break; + } + } + + obj->select(); + } + + delete[] pixeldata; + + // Update everything now. + g_win->updateSelection(); + + // Recompile the objects now to update their color + for (LDObject* obj : selection()) + compileObject (obj); + + if (removedObj) + compileObject (removedObj); + + // Restore line thickness + glLineWidth (gl_linethickness); + + setPicking (false); + m_rangepick = false; + glEnable (GL_DITHER); + + setBackground(); + repaint(); +} + +// ============================================================================= +// +void GLRenderer::setEditMode (EditMode const& a) +{ + m_EditMode = a; + + switch (a) + { + case ESelectMode: + { + unsetCursor(); + setContextMenuPolicy (Qt::DefaultContextMenu); + } break; + + case EDrawMode: + case ECircleMode: + { + // Cannot draw into the free camera - use top instead. + if (m_camera == EFreeCamera) + setCamera (ETopCamera); + + // Disable the context menu - we need the right mouse button + // for removing vertices. + setContextMenuPolicy (Qt::NoContextMenu); + + // Use the crosshair cursor when drawing. + setCursor (Qt::CrossCursor); + + // Clear the selection when beginning to draw. + LDObjectList priorsel = selection(); + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : priorsel) + compileObject (obj); + + g_win->updateSelection(); + m_drawedVerts.clear(); + } break; + } + + g_win->updateEditModeActions(); + update(); +} + +// ============================================================================= +// +void GLRenderer::setFile (LDDocument* const& a) +{ + m_File = a; + g_vertexCompiler.setFile (a); + + if (a != null) + { + initOverlaysFromObjects(); + + if (currentDocumentData().init == false) + { + resetAllAngles(); + currentDocumentData().init = true; + } + } +} + +// ============================================================================= +// +Matrix GLRenderer::getCircleDrawMatrix (double scale) +{ + Matrix transform = g_circleDrawMatrixTemplates[camera() % 3]; + + for (int i = 0; i < 9; ++i) + { + if (transform[i] == 2) + transform[i] = scale; + elif (transform[i] == 1 && camera() >= 3) + transform[i] = -1; + } + + return transform; +} + +// ============================================================================= +// +void GLRenderer::endDraw (bool accept) +{ + (void) accept; + + // Clean the selection and create the object + QList<Vertex>& verts = m_drawedVerts; + LDObjectList objs; + + switch (getEditMode()) + { + case EDrawMode: + { + if (m_rectdraw) + { + LDQuad* quad = new LDQuad; + + // Copy the vertices from m_rectverts + updateRectVerts(); + + for (int i = 0; i < quad->vertices(); ++i) + quad->setVertex (i, m_rectverts[i]); + + quad->setColor (maincolor); + objs << quad; + } + else + { + switch (verts.size()) + { + case 1: + { + // 1 vertex - add a vertex object + LDVertex* obj = new LDVertex; + obj->pos = verts[0]; + obj->setColor (maincolor); + objs << obj; + } break; + + case 2: + { + // 2 verts - make a line + LDLine* obj = new LDLine (verts[0], verts[1]); + obj->setColor (edgecolor); + objs << obj; + } break; + + case 3: + case 4: + { + LDObject* obj = (verts.size() == 3) ? + static_cast<LDObject*> (new LDTriangle) : + static_cast<LDObject*> (new LDQuad); + + obj->setColor (maincolor); + + for (int i = 0; i < obj->vertices(); ++i) + obj->setVertex (i, verts[i]); + + objs << obj; + } break; + } + } + } break; + + case ECircleMode: + { + const int segs = lores, divs = lores; // TODO: make customizable + double dist0 = getCircleDrawDist (0), + dist1 = getCircleDrawDist (1); + LDDocument* refFile = null; + Matrix transform; + bool circleOrDisc = false; + + if (dist1 < dist0) + std::swap<double> (dist0, dist1); + + if (dist0 == dist1) + { + // If the radii are the same, there's no ring space to fill. Use a circle. + refFile = ::getDocument ("4-4edge.dat"); + transform = getCircleDrawMatrix (dist0); + circleOrDisc = true; + } + elif (dist0 == 0 || dist1 == 0) + { + // If either radii is 0, use a disc. + refFile = ::getDocument ("4-4disc.dat"); + transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); + circleOrDisc = true; + } + elif (g_RingFinder (dist0, dist1)) + { + // The ring finder found a solution, use that. Add the component rings to the file. + for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents()) + { + // Get a ref file for this primitive. If we cannot find it in the + // LDraw library, generate it. + if ((refFile = ::getDocument (radialFileName (::Ring, lores, lores, cmp.num))) == null) + { + refFile = generatePrimitive (::Ring, lores, lores, cmp.num); + refFile->setImplicit (false); + } + + LDSubfile* ref = new LDSubfile; + ref->setFileInfo (refFile); + ref->setTransform (getCircleDrawMatrix (cmp.scale)); + ref->setPosition (m_drawedVerts[0]); + ref->setColor (maincolor); + objs << ref; + } + } + else + { + // Ring finder failed, last resort: draw the ring with quads + QList<QLineF> c0, c1; + Axis relX, relY, relZ; + getRelativeAxes (relX, relY); + relZ = (Axis) (3 - relX - relY); + double x0 = m_drawedVerts[0][relX], + y0 = m_drawedVerts[0][relY]; + + Vertex templ; + templ[relX] = x0; + templ[relY] = y0; + templ[relZ] = getDepthValue(); + + // Calculate circle coords + makeCircle (segs, divs, dist0, c0); + makeCircle (segs, divs, dist1, c1); + + for (int i = 0; i < segs; ++i) + { + Vertex v0, v1, v2, v3; + v0 = v1 = v2 = v3 = templ; + v0[relX] += c0[i].x1(); + v0[relY] += c0[i].y1(); + v1[relX] += c0[i].x2(); + v1[relY] += c0[i].y2(); + v2[relX] += c1[i].x2(); + v2[relY] += c1[i].y2(); + v3[relX] += c1[i].x1(); + v3[relY] += c1[i].y1(); + + LDQuad* q = new LDQuad (v0, v1, v2, v3); + q->setColor (maincolor); + + // Ensure the quads always are BFC-front towards the camera + if (camera() % 3 <= 0) + q->invert(); + + objs << q; + } + } + + if (circleOrDisc) + { + LDSubfile* ref = new LDSubfile; + ref->setFileInfo (refFile); + ref->setTransform (transform); + ref->setPosition (m_drawedVerts[0]); + ref->setColor (maincolor); + objs << ref; + } + } break; + + case ESelectMode: + { + // this shouldn't happen + assert (false); + return; + } break; + } + + if (objs.size() > 0) + { + for (LDObject* obj : objs) + { + getFile()->addObject (obj); + compileObject (obj); + } + + g_win->refresh(); + g_win->endAction(); + } + + m_drawedVerts.clear(); + m_rectdraw = false; +} + +// ============================================================================= +// +double GLRenderer::getCircleDrawDist (int pos) const +{ + assert (m_drawedVerts.size() >= pos + 1); + const Vertex& v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : m_hoverpos; + Axis relX, relY; + getRelativeAxes (relX, relY); + + const double dx = m_drawedVerts[0][relX] - v1[relX]; + const double dy = m_drawedVerts[0][relY] - v1[relY]; + return sqrt ((dx * dx) + (dy * dy)); +} + +// ============================================================================= +// +void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const +{ + const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; + relX = cam->axisX; + relY = cam->axisY; +} + +// ============================================================================= +// +static QList<Vertex> getVertices (LDObject* obj) +{ + QList<Vertex> verts; + + if (obj->vertices() >= 2) + { + for (int i = 0; i < obj->vertices(); ++i) + verts << obj->getVertex (i); + } elif (obj->getType() == LDObject::ESubfile) + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline); + + for (LDObject* obj : objs) + { + verts << getVertices (obj); + obj->deleteSelf(); + } + } + + return verts; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void GLRenderer::compileObject (LDObject* obj) +{ + g_vertexCompiler.stageForCompilation (obj); + + // Mark in known vertices of this object + QList<Vertex> verts = getVertices (obj); + m_knownVerts << verts; + removeDuplicates (m_knownVerts); + + obj->setGLInit (true); +} + +// ============================================================================= +// +uchar* GLRenderer::getScreencap (int& w, int& h) +{ + w = m_width; + h = m_height; + uchar* cap = new uchar[4 * w * h]; + + m_screencap = true; + update(); + m_screencap = false; + + // Capture the pixels + glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap); + + return cap; +} + +// ============================================================================= +// +void GLRenderer::slot_toolTipTimer() +{ + // We come here if the cursor has stayed in one place for longer than a + // a second. Check if we're holding it over a camera icon - if so, draw + // a tooltip. +for (CameraIcon & icon : m_cameraIcons) + { + if (icon.destRect.contains (m_pos)) + { + m_toolTipCamera = icon.cam; + m_drawToolTip = true; + update(); + break; + } + } +} + +// ============================================================================= +// +void GLRenderer::deleteLists (LDObject* obj) +{ + // Delete the lists but only if they have been initialized + if (!obj->isGLInit()) + return; + + for (const GL::ListType listType : g_glListTypes) + glDeleteLists (obj->glLists[listType], 1); + + obj->setGLInit (false); +} + +// ============================================================================= +// +Axis GLRenderer::getCameraAxis (bool y, GLRenderer::EFixedCamera camid) +{ + if (camid == (GL::EFixedCamera) - 1) + camid = m_camera; + + const LDFixedCameraInfo* cam = &g_FixedCameras[camid]; + return (y) ? cam->axisY : cam->axisX; +} + +// ============================================================================= +// +bool GLRenderer::setupOverlay (EFixedCamera cam, QString file, int x, int y, int w, int h) +{ + QImage* img = new QImage (QImage (file).convertToFormat (QImage::Format_ARGB32)); + LDGLOverlay& info = getOverlay (cam); + + if (img->isNull()) + { + critical (tr ("Failed to load overlay image!")); + delete img; + return false; + } + + delete info.img; // delete the old image + + info.fname = file; + info.lw = w; + info.lh = h; + info.ox = x; + info.oy = y; + info.img = img; + + if (info.lw == 0) + info.lw = (info.lh * img->width()) / img->height(); + elif (info.lh == 0) + info.lh = (info.lw * img->height()) / img->width(); + + const Axis x2d = getCameraAxis (false, cam), + y2d = getCameraAxis (true, cam); + const double negXFac = g_FixedCameras[cam].negX ? -1 : 1, + negYFac = g_FixedCameras[cam].negY ? -1 : 1; + + info.v0 = info.v1 = g_origin; + info.v0[x2d] = - (info.ox * info.lw * negXFac) / img->width(); + info.v0[y2d] = (info.oy * info.lh * negYFac) / img->height(); + info.v1[x2d] = info.v0[x2d] + info.lw; + info.v1[y2d] = info.v0[y2d] + info.lh; + + // Set alpha of all pixels to 0.5 + for (long i = 0; i < img->width(); ++i) + for (long j = 0; j < img->height(); ++j) + { + uint32 pixel = img->pixel (i, j); + img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF)); + } + + updateOverlayObjects(); + return true; +} + +// ============================================================================= +// +void GLRenderer::clearOverlay() +{ + if (camera() == EFreeCamera) + return; + + LDGLOverlay& info = currentDocumentData().overlays[camera()]; + delete info.img; + info.img = null; + + updateOverlayObjects(); +} + +// ============================================================================= +// +void GLRenderer::setDepthValue (double depth) +{ + assert (camera() < EFreeCamera); + currentDocumentData().depthValues[camera()] = depth; +} + +// ============================================================================= +// +double GLRenderer::getDepthValue() const +{ + assert (camera() < EFreeCamera); + return currentDocumentData().depthValues[camera()]; +} + +// ============================================================================= +// +const char* GLRenderer::getCameraName() const +{ + return g_CameraNames[camera()]; +} + +// ============================================================================= +// +LDGLOverlay& GLRenderer::getOverlay (int newcam) +{ + return currentDocumentData().overlays[newcam]; +} + +// ============================================================================= +// +void GLRenderer::zoomNotch (bool inward) +{ + if (zoom() > 15) + zoom() *= inward ? 0.833f : 1.2f; + else + zoom() += inward ? -1.2f : 1.2f; +} + +// ============================================================================= +// +void GLRenderer::zoomToFit() +{ + if (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<uint32*> (cap); + bool filled = false; + + // Check the top and bottom rows + for (int i = 0; i < w; ++i) + { + if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white) + { + filled = true; + goto endOfLoop; + } + } + + // Left and right edges + for (int i = 0; i < h; ++i) + { + if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white) + { + filled = true; + goto endOfLoop; + } + } + +endOfLoop: + + delete[] cap; + + if (firstrun) + { + // If this is the first run, we don't know enough to determine + // whether the zoom was to fit, so we mark in our knowledge so + // far and start over. + inward = !filled; + firstrun = false; + } + else + { + // If this run filled the screen and the last one did not, the + // last run had ideal zoom - zoom a bit back and we should reach it. + if (filled && !lastfilled) + { + zoomNotch (false); + break; + } + + // If this run did not fill the screen and the last one did, we've + // now reached ideal zoom so we're done here. + if (!filled && lastfilled) + break; + + inward = !filled; + } + + lastfilled = filled; + } + + setBackground(); + setPicking (false); +} + +// ============================================================================= +// +void GLRenderer::zoomAllToFit() +{ + EFixedCamera oldcam = camera(); + + for (int i = 0; i < 7; ++i) + { + setCamera ((EFixedCamera) i); + zoomToFit(); + } + + setCamera (oldcam); +} + +// ============================================================================= +// +void GLRenderer::updateRectVerts() +{ + if (!m_rectdraw) + return; + + if (m_drawedVerts.isEmpty()) + { + for (int i = 0; i < 4; ++i) + m_rectverts[i] = m_hoverpos; + + return; + } + + Vertex v0 = m_drawedVerts[0], + v1 = (m_drawedVerts.size() >= 2) ? m_drawedVerts[1] : m_hoverpos; + + const Axis ax = getCameraAxis (false), + ay = getCameraAxis (true), + az = (Axis) (3 - ax - ay); + + for (int i = 0; i < 4; ++i) + m_rectverts[i][az] = getDepthValue(); + + m_rectverts[0][ax] = v0[ax]; + m_rectverts[0][ay] = v0[ay]; + m_rectverts[1][ax] = v1[ax]; + m_rectverts[1][ay] = v0[ay]; + m_rectverts[2][ax] = v1[ax]; + m_rectverts[2][ay] = v1[ay]; + m_rectverts[3][ax] = v0[ax]; + m_rectverts[3][ay] = v1[ay]; +} + +// ============================================================================= +// +void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev) +{ + if (!(ev->buttons() & Qt::LeftButton) || 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<LDOverlay*> (obj)->getCamera() == cam) + { + ovlobj = static_cast<LDOverlay*> (obj); + break; + } + } + + return ovlobj; +} + +// ============================================================================= +// +// Read in overlays from the current file and update overlay info accordingly. +// +void GLRenderer::initOverlaysFromObjects() +{ + for (EFixedCamera cam : g_Cameras) + { + if (cam == EFreeCamera) + continue; + + LDGLOverlay& meta = 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(); +}
--- a/src/gldraw.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1785 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QGLWidget> -#include <QWheelEvent> -#include <QMouseEvent> -#include <QContextMenuEvent> -#include <QInputDialog> -#include <QToolTip> -#include <QTimer> -#include <GL/glu.h> -#include "common.h" -#include "config.h" -#include "file.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 "gldata.h" -#include "primitives.h" -#include "moc_gldraw.cpp" - -static const LDFixedCameraInfo g_FixedCameras[6] = -{ {{ 1, 0, 0 }, X, Z, false, false }, - {{ 0, 0, 0 }, X, Y, false, true }, - {{ 0, 1, 0 }, Z, Y, true, true }, - {{ -1, 0, 0 }, X, Z, false, true }, - {{ 0, 0, 0 }, X, Y, true, true }, - {{ 0, -1, 0 }, Z, Y, false, true }, -}; - -static const matrix g_circleDrawTransforms[3] = -{ { 2, 0, 0, 0, 1, 0, 0, 0, 2 }, - { 2, 0, 0, 0, 0, 2, 0, 1, 0 }, - { 0, 1, 0, 2, 0, 0, 0, 0, 2 }, -}; - -cfg (String, gl_bgcolor, "#CCCCD9"); -cfg (String, gl_maincolor, "#707078"); -cfg (Float, gl_maincolor_alpha, 1.0); -cfg (Int, gl_linethickness, 2); -cfg (Bool, gl_colorbfc, false); -cfg (Int, gl_camera, GLRenderer::Free); -cfg (Bool, gl_axes, false); -cfg (Bool, gl_wireframe, false); -cfg (Bool, gl_logostuds, false); -cfg (Bool, gl_aa, true); - -// argh -const char* g_CameraNames[7] = -{ QT_TRANSLATE_NOOP ("GLRenderer", "Top"), - QT_TRANSLATE_NOOP ("GLRenderer", "Front"), - QT_TRANSLATE_NOOP ("GLRenderer", "Left"), - QT_TRANSLATE_NOOP ("GLRenderer", "Bottom"), - QT_TRANSLATE_NOOP ("GLRenderer", "Back"), - QT_TRANSLATE_NOOP ("GLRenderer", "Right"), - QT_TRANSLATE_NOOP ("GLRenderer", "Free") -}; - -const GL::Camera g_Cameras[7] = -{ GL::Top, - GL::Front, - GL::Left, - GL::Bottom, - GL::Back, - GL::Right, - GL::Free -}; - -const struct LDGLAxis -{ const QColor col; - const vertex vert; -} g_GLAxes[3] = -{ { QColor (255, 0, 0), vertex (10000, 0, 0) }, - { QColor (80, 192, 0), vertex (0, 10000, 0) }, - { QColor (0, 160, 192), vertex (0, 0, 10000) }, -}; - -// ============================================================================= -// ----------------------------------------------------------------------------- -GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) -{ m_picking = m_rangepick = false; - m_camera = (GL::Camera) gl_camera.value; - m_drawToolTip = false; - m_editMode = Select; - 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::Camera cam : g_Cameras) - { str iconname = fmt ("camera-%1", tr (g_CameraNames[cam]).toLower()); - - CameraIcon* info = &m_cameraIcons[cam]; - info->img = new QPixmap (getIcon (iconname)); - info->cam = cam; - } - - for (int i = 0; i < 6; ++i) - { m_overlays[i].img = null; - m_depthValues[i] = 0.0f; - } - - calcCameraIcons(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -GLRenderer::~GLRenderer() -{ for (int i = 0; i < 6; ++i) - delete m_overlays[i].img; - - for (CameraIcon& info : m_cameraIcons) - delete info.img; -} - -// ============================================================================= -// Calculates the "hitboxes" of the camera icons so that we can tell when the -// cursor is pointing at the camera icon. -// ----------------------------------------------------------------------------- -void GLRenderer::calcCameraIcons() -{ int i = 0; - - for (CameraIcon& info : m_cameraIcons) - { // MATH - const long x1 = (m_width - (info.cam != Free ? 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() -{ Camera oldcam = camera(); - - for (int i = 0; i < 7; ++i) - { setCamera ((Camera) i); - resetAngles(); - } - - setCamera (oldcam); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::initializeGL() -{ setBackground(); - - glLineWidth (gl_linethickness); - glLineStipple (1, 0x6666); - - setAutoFillBackground (false); - setMouseTracking (true); - setFocusPolicy (Qt::WheelFocus); - - g_vertexCompiler.compileFile(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -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::refresh() { - update(); - swapBuffers(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::hardRefresh() { - g_vertexCompiler.compileFile(); - 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 (file() == null) - return; - - if (gl_wireframe && !picking()) - glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); - - glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable (GL_DEPTH_TEST); - - if (m_camera != Free) - { 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 != Front && m_camera != Back) - { 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 == GL::Back) - { 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); - } - - // Draw the VAOs now - glEnableClientState (GL_VERTEX_ARRAY); - glEnableClientState (GL_COLOR_ARRAY); - glDisableClientState (GL_NORMAL_ARRAY); - - if (gl_colorbfc) { - glEnable (GL_CULL_FACE); - glCullFace (GL_CCW); - } else - glDisable (GL_CULL_FACE); - - drawVAOs ((m_picking ? PickArray : gl_colorbfc ? BFCArray : MainArray), GL_TRIANGLES); - drawVAOs ((m_picking ? EdgePickArray : EdgeArray), GL_LINES); - - // Draw conditional lines. Note that conditional lines are drawn into - // EdgePickArray in the picking scene, so when picking, don't do anything - // here. - if (!m_picking) { - glEnable (GL_LINE_STIPPLE); - drawVAOs (CondEdgeArray, GL_LINES); - glDisable (GL_LINE_STIPPLE); - } - - glPopMatrix(); - glMatrixMode (GL_MODELVIEW); - glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::drawVAOs (VAOType arrayType, GLenum type) { - const VertexCompiler::Array* array = g_vertexCompiler.getMergedBuffer (arrayType); - glVertexPointer (3, GL_FLOAT, sizeof (VertexCompiler::Vertex), &array->data()[0].x); - glColorPointer (4, GL_UNSIGNED_BYTE, sizeof (VertexCompiler::Vertex), &array->data()[0].color); - glDrawArrays (type, 0, array->writtenSize() / sizeof (VertexCompiler::Vertex)); -} - -// ============================================================================= -// 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() != Free); - - vertex pos3d; - const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; - const Axis axisX = cam->axisX; - const Axis axisY = cam->axisY; - const short negXFac = cam->negX ? -1 : 1, - negYFac = cam->negY ? -1 : 1; - - // Calculate cx and cy - these are the LDraw unit coords the cursor is at. - double cx = (-m_virtWidth + ((2 * pos2d.x() * m_virtWidth) / m_width) - pan (X)); - double cy = (m_virtHeight - ((2 * pos2d.y() * m_virtHeight) / m_height) - pan (Y)); - - if (snap) - { cx = Grid::snap (cx, (Grid::Config) axisX); - cy = Grid::snap (cy, (Grid::Config) axisY); - } - - cx *= negXFac; - cy *= negYFac; - - str tmp; - // Create the vertex from the coordinates - pos3d[axisX] = tmp.sprintf ("%.3f", cx).toDouble(); - pos3d[axisY] = tmp.sprintf ("%.3f", cy).toDouble(); - pos3d[3 - axisX - axisY] = depthValue(); - return pos3d; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -// Inverse operation for the above - convert a 3D position to a 2D screen -// position -// ----------------------------------------------------------------------------- -QPoint GLRenderer::coordconv3_2 (const vertex& pos3d) const -{ GLfloat m[16]; - const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; - const Axis axisX = cam->axisX; - const Axis axisY = cam->axisY; - const short negXFac = cam->negX ? -1 : 1, - negYFac = cam->negY ? -1 : 1; - - glGetFloatv (GL_MODELVIEW_MATRIX, m); - - const double x = pos3d.x(); - const double y = pos3d.y(); - const double z = pos3d.z(); - - vertex transformed; - transformed[X] = (m[0] * x) + (m[1] * y) + (m[2] * z) + m[3]; - transformed[Y] = (m[4] * x) + (m[5] * y) + (m[6] * z) + m[7]; - transformed[Z] = (m[8] * x) + (m[9] * y) + (m[10] * z) + m[11]; - - double rx = (((transformed[axisX] * negXFac) + m_virtWidth + pan (X)) * m_width) / (2 * m_virtWidth); - double ry = (((transformed[axisY] * negYFac) - m_virtHeight + pan (Y)) * m_height) / (2 * m_virtHeight); - - return QPoint (rx, -ry); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::paintEvent (QPaintEvent* ev) -{ Q_UNUSED (ev) - - makeCurrent(); - m_virtWidth = zoom(); - m_virtHeight = (m_height * m_virtWidth) / m_width; - - initGLData(); - drawGLScene(); - - const QPen textpen = getTextPen(); - const QBrush polybrush (QColor (64, 192, 0, 128)); - QPainter paint (this); - QFontMetrics metrics = QFontMetrics (QFont()); - paint.setRenderHint (QPainter::HighQualityAntialiasing); - - // If we wish to only draw the brick, stop here - if (drawOnly()) - return; - - if (m_camera != Free && !picking()) - { // Paint the overlay image if we have one - const LDGLOverlay& overlay = m_overlays[m_camera]; - - if (overlay.img != null) - { QPoint v0 = coordconv3_2 (m_overlays[m_camera].v0), - v1 = coordconv3_2 (m_overlays[m_camera].v1); - - QRect targRect (v0.x(), v0.y(), abs (v1.x() - v0.x()), abs (v1.y() - v0.y())), - srcRect (0, 0, overlay.img->width(), overlay.img->height()); - paint.drawImage (targRect, *overlay.img, srcRect); - } - - // Paint the coordinates onto the screen. - str text = fmt (tr ("X: %1, Y: %2, Z: %3"), m_hoverpos[X], m_hoverpos[Y], m_hoverpos[Z]); - - QFontMetrics metrics = QFontMetrics (font()); - QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); - - paint.setPen (getTextPen()); - paint.drawText (m_width - textSize.width(), m_height - 16, textSize.width(), - textSize.height(), Qt::AlignCenter, text); - - QPen linepen = m_thinBorderPen; - linepen.setWidth (2); - linepen.setColor (luma (m_bgcolor) < 40 ? Qt::white : Qt::black); - - // If we're drawing, draw the vertices onto the screen. - if (editMode() == Draw) - { int numverts = 4; - - if (!m_rectdraw) - numverts = m_drawedVerts.size() + 1; - - if (numverts > 0) - { QPoint poly[4]; - vertex polyverts[4]; - - if (!m_rectdraw) - { uchar i = 0; - - for (vertex& vert : m_drawedVerts) - { poly[i] = coordconv3_2 (vert); - polyverts[i] = vert; - ++i; - } - - // Draw the cursor vertex as the last one in the list. - if (numverts <= 4) - { poly[i] = coordconv3_2 (m_hoverpos); - polyverts[i] = m_hoverpos; - } - else - { numverts = 4; - } - } - else - { if (m_drawedVerts.size() > 0) - { // Get vertex information from m_rectverts - for (int i = 0; i < numverts; ++i) - { polyverts[i] = m_rectverts[i]; - poly[i] = coordconv3_2 (polyverts[i]); - } - } - else - { poly[0] = coordconv3_2 (m_hoverpos); - polyverts[0] = m_hoverpos; - } - } - - // Draw the polygon-to-be - paint.setPen (linepen); - paint.setBrush (polybrush); - paint.drawPolygon (poly, numverts); - - // Draw vertex blips - for (int i = 0; i < numverts; ++i) - { QPoint& blip = poly[i]; - drawBlip (paint, blip); - - // Draw their coordinates - paint.drawText (blip.x(), blip.y() - 8, polyverts[i].stringRep (true)); - } - } - } - elif (editMode() == CircleMode) - { // If we have not specified the center point of the circle yet, preview it on the screen. - if (m_drawedVerts.isEmpty()) - drawBlip (paint, coordconv3_2 (m_hoverpos)); - else - { QVector<vertex> verts, verts2; - const double dist0 = circleDrawDist (0), - dist1 = (m_drawedVerts.size() >= 2) ? circleDrawDist (1) : -1; - const int segs = lores; - const double angleUnit = (2 * pi) / segs; - Axis relX, relY; - QVector<QPoint> ringpoints, circlepoints, circle2points; - - getRelativeAxes (relX, relY); - - // Calculate the preview positions of vertices - for (int i = 0; i < segs; ++i) - { vertex v = g_origin; - v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist0); - v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist0); - verts << v; - - if (dist1 != -1) - { v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist1); - v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist1); - verts2 << v; - } - } - - int i = 0; - for (const vertex& v : verts + verts2) - { // Calculate the 2D point of the vertex - QPoint point = coordconv3_2 (v); - - // Draw a green blip at where it is - drawBlip (paint, point); - - // Add it to the list of points for the green ring fill. - ringpoints << point; - - // Also add the circle points to separate lists - if (i < verts.size()) - circlepoints << point; - else - circle2points << point; - - ++i; - } - - // Insert the first point as the seventeenth one so that - // the ring polygon is closed properly. - if (ringpoints.size() >= 16) - ringpoints.insert (16, ringpoints[0]); - - // Same for the outer ring. Note that the indices are offset by 1 - // because of the insertion done above bumps the values. - if (ringpoints.size() >= 33) - ringpoints.insert (33, ringpoints[17]); - - // Draw the ring - paint.setBrush ((m_drawedVerts.size() >= 2) ? polybrush : Qt::NoBrush); - paint.setPen (Qt::NoPen); - paint.drawPolygon (QPolygon (ringpoints)); - - // Draw the circles - paint.setBrush (Qt::NoBrush); - paint.setPen (linepen); - paint.drawPolygon (QPolygon (circlepoints)); - paint.drawPolygon (QPolygon (circle2points)); - - { // Draw the current radius in the middle of the circle. - QPoint origin = coordconv3_2 (m_drawedVerts[0]); - str label = str::number (dist0); - paint.setPen (textpen); - paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label); - - if (m_drawedVerts.size() >= 2) - { label = str::number (dist1); - paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y() + metrics.height(), label); - } - } - } - } - } - - // Camera icons - if (!m_picking) - { // 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::Free] && editMode() != Select) - continue; - - paint.drawPixmap (info.destRect, *info.img, info.srcRect); - } - - str fmtstr = tr ("%1 Camera"); - - // Draw a label for the current camera in the bottom left corner - { const int margin = 4; - - str label; - label = fmt (fmtstr, tr (g_CameraNames[camera()])); - paint.setPen (textpen); - paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label); - } - - // Tool tips - if (m_drawToolTip) - { if (m_cameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false) - m_drawToolTip = false; - else - { str label = fmt (fmtstr, tr (g_CameraNames[m_toolTipCamera])); - QToolTip::showText (m_globalpos, label); - } - } - } - - // Message log - if (msglog()) - { int y = 0; - const int margin = 2; - QColor penColor = getTextPen(); - - for (const MessageManager::Line& line : msglog()->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 && !m_picking && m_totalmove >= 10) - { int x0 = m_rangeStart.x(), - y0 = m_rangeStart.y(), - x1 = m_pos.x(), - y1 = m_pos.y(); - - QRect rect (x0, y0, x1 - x0, y1 - y0); - QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF"); - fillColor.setAlphaF (0.2f); - - paint.setPen (m_thickBorderPen); - paint.setBrush (QBrush (fillColor)); - paint.drawRect (rect); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::drawBlip (QPainter& paint, QPoint pos) const -{ QPen pen = m_thinBorderPen; - const int blipsize = 8; - pen.setWidth (1); - paint.setPen (pen); - paint.setBrush (QColor (64, 192, 0)); - paint.drawEllipse (pos.x() - blipsize / 2, pos.y() - blipsize / 2, blipsize, blipsize); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QColor GLRenderer::getTextPen () const -{ return m_darkbg ? Qt::white : Qt::black; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::compileAllObjects() -{ g_vertexCompiler.compileFile(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::clampAngle (double& angle) const -{ while (angle < 0) - angle += 360.0; - - while (angle > 360.0) - angle -= 360.0; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::addDrawnVertex (vertex pos) -{ // If we picked an already-existing vertex, stop drawing - if (editMode() != CircleMode) - { for (vertex& vert : m_drawedVerts) - { if (vert == pos) - { endDraw (true); - return; - } - } - } - - m_drawedVerts << pos; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::mouseReleaseEvent (QMouseEvent* ev) -{ const bool wasLeft = (m_lastButtons & Qt::LeftButton) && ! (ev->buttons() & Qt::LeftButton), - wasRight = (m_lastButtons & Qt::RightButton) && ! (ev->buttons() & Qt::RightButton), - wasMid = (m_lastButtons & Qt::MidButton) && ! (ev->buttons() & Qt::MidButton); - - if (m_panning) - m_panning = false; - - if (wasLeft) - { // Check if we selected a camera icon - if (!m_rangepick) - { for (CameraIcon & info : m_cameraIcons) - { if (info.destRect.contains (ev->pos())) - { setCamera (info.cam); - goto end; - } - } - } - - switch (editMode()) - { - case Draw: - { 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 CircleMode: - { if (m_drawedVerts.size() == 3) - { endDraw (true); - return; - } - - addDrawnVertex (m_hoverpos); - } break; - - case Select: - { if (!drawOnly()) - { if (m_totalmove < 10) - m_rangepick = false; - - if (!m_rangepick) - m_addpick = (m_keymods & Qt::ControlModifier); - - if (m_totalmove < 10 || m_rangepick) - pick (ev->x(), ev->y()); - } - } break; - } - - m_rangepick = false; - } - - if (wasMid && editMode() != Select && 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() == Free) - { 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() != Free) ? 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 GL::Camera cam) -{ m_camera = cam; - gl_camera = (int) cam; - g_win->updateEditModeActions(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::pick (int mouseX, int mouseY) -{ GLint viewport[4]; - makeCurrent(); - - // Use particularly thick lines while picking ease up selecting lines. - glLineWidth (max<double> (gl_linethickness, 6.5f)); - - // Clear the selection if we do not wish to add to it. - if (!m_addpick) - { QList<LDObject*> oldsel = selection(); - LDFile::current()->clearSelection(); - - for (LDObject* obj : oldsel) - compileObject (obj); - } - - m_picking = true; - - // Paint the picking scene - glDisable (GL_DITHER); - glClearColor (1.0f, 1.0f, 1.0f, 1.0f); - - drawGLScene(); - - glGetIntegerv (GL_VIEWPORT, viewport); - - short x0 = mouseX, - y0 = mouseY; - short 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) - dataswap (x0, x1); - - if (y0 > y1) - dataswap (y0, y1); - - // Clamp the values to ensure they're within bounds - x0 = max<short> (0, x0); - y0 = max<short> (0, y0); - x1 = min<short> (x1, m_width); - y1 = min<short> (y1, m_height); - - const short areawidth = (x1 - x0); - const short areaheight = (y1 - y0); - const long numpixels = areawidth * areaheight; - - // Allocate space for the pixel data. - uchar* const pixeldata = new uchar[4 * numpixels]; - uchar* pixelptr = &pixeldata[0]; - - assert (viewport[3] == m_height); - - // Read pixels from the color buffer. - glReadPixels (x0, viewport[3] - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata); - - LDObject* removedObj = null; - - // Go through each pixel read and add them to the selection. - for (long i = 0; i < numpixels; ++i) - { long idx = - (* (pixelptr + 0) * 0x10000) + - (* (pixelptr + 1) * 0x00100) + - (* (pixelptr + 2) * 0x00001); - pixelptr += 4; - - if (idx == 0xFFFFFF) - continue; // White is background; skip - - LDObject* obj = LDObject::fromID (idx); - - // If this is an additive single pick and the object is currently selected, - // we remove it from selection instead. - if (!m_rangepick && m_addpick) - { if (obj->selected()) - { 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); - - m_picking = false; - m_rangepick = false; - glEnable (GL_DITHER); - - setBackground(); - repaint(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -READ_ACCESSOR (EditMode, GLRenderer::editMode) -{ return m_editMode; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -SET_ACCESSOR (EditMode, GLRenderer::setEditMode) -{ m_editMode = val; - - switch (editMode()) - { case Select: - { unsetCursor(); - setContextMenuPolicy (Qt::DefaultContextMenu); - } break; - - case Draw: - case CircleMode: - { // Cannot draw into the free camera - use top instead. - if (m_camera == Free) - setCamera (Top); - - // Disable the context menu - we need the right mouse button - // for removing vertices. - setContextMenuPolicy (Qt::NoContextMenu); - - // Use the crosshair cursor when drawing. - setCursor (Qt::CrossCursor); - - // Clear the selection when beginning to draw. - QList<LDObject*> priorsel = selection(); - LDFile::current()->clearSelection(); - - for (LDObject* obj : priorsel) - compileObject (obj); - - g_win->updateSelection(); - m_drawedVerts.clear(); - } break; - } - - g_win->updateEditModeActions(); - update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -READ_ACCESSOR (LDFile*, GLRenderer::file) -{ return m_file; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -SET_ACCESSOR (LDFile*, GLRenderer::setFile) { - m_file = val; - g_vertexCompiler.setFile (val); - - if (val != null) - overlaysFromObjects(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -matrix GLRenderer::getCircleDrawMatrix (double scale) -{ matrix transform = g_circleDrawTransforms[camera() % 3]; - - for (int i = 0; i < 9; ++i) - { if (transform[i] == 2) - transform[i] = scale; - elif (transform[i] == 1 && camera() >= 3) - transform[i] = -1; - } - - return transform; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::endDraw (bool accept) -{ (void) accept; - - // Clean the selection and create the object - QList<vertex>& verts = m_drawedVerts; - QList<LDObject*> objs; - - switch (editMode()) - { case Draw: - { if (m_rectdraw) - { LDQuad* quad = new LDQuad; - - // Copy the vertices from m_rectverts - updateRectVerts(); - - for (int i = 0; i < quad->vertices(); ++i) - quad->setVertex (i, m_rectverts[i]); - - quad->setColor (maincolor); - objs << quad; - } - else - { switch (verts.size()) - { case 1: - { // 1 vertex - add a vertex object - LDVertex* obj = new LDVertex; - obj->pos = verts[0]; - obj->setColor (maincolor); - objs << obj; - } break; - - case 2: - { // 2 verts - make a line - LDLine* obj = new LDLine (verts[0], verts[1]); - obj->setColor (edgecolor); - objs << obj; - } break; - - case 3: - case 4: - { LDObject* obj = (verts.size() == 3) ? - static_cast<LDObject*> (new LDTriangle) : - static_cast<LDObject*> (new LDQuad); - - obj->setColor (maincolor); - - for (int i = 0; i < obj->vertices(); ++i) - obj->setVertex (i, verts[i]); - - objs << obj; - } break; - } - } - } break; - - case CircleMode: - { const int segs = lores, divs = lores; // TODO: make customizable - double dist0 = circleDrawDist (0), - dist1 = circleDrawDist (1); - LDFile* refFile = null; - matrix transform; - bool circleOrDisc = false; - - if (dist1 < dist0) - std::swap<double> (dist0, dist1); - - if (dist0 == dist1) - { // If the radii are the same, there's no ring space to fill. Use a circle. - refFile = getFile ("4-4edge.dat"); - transform = getCircleDrawMatrix (dist0); - circleOrDisc = true; - } - elif (dist0 == 0 || dist1 == 0) - { // If either radii is 0, use a disc. - refFile = getFile ("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()->components()) - { if ((refFile = getFile (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 - { // Last resort: draw the ring with quads - QList<QLineF> c0, c1; - Axis relX, relY, relZ; - getRelativeAxes (relX, relY); - relZ = (Axis) (3 - relX - relY); - double x0 = m_drawedVerts[0][relX], - y0 = m_drawedVerts[0][relY]; - - vertex templ; - templ[relX] = x0; - templ[relY] = y0; - templ[relZ] = depthValue(); - - // 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 Select: - { assert (false); - return; - } break; - } - - if (objs.size() > 0) - { g_win->beginAction (null); - - for (LDObject* obj : objs) - { file()->addObject (obj); - compileObject (obj); - } - - g_win->refresh(); - g_win->endAction(); - } - - m_drawedVerts.clear(); - m_rectdraw = false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -double GLRenderer::circleDrawDist (int pos) const -{ assert (m_drawedVerts.size() >= pos + 1); - const vertex& v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : m_hoverpos; - Axis relX, relY; - getRelativeAxes (relX, relY); - - const double dx = m_drawedVerts[0][relX] - v1[relX]; - const double dy = m_drawedVerts[0][relY] - v1[relY]; - return sqrt ((dx * dx) + (dy * dy)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const -{ const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera]; - relX = cam->axisX; - relY = cam->axisY; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static QList<vertex> getVertices (LDObject* obj) -{ QList<vertex> verts; - - if (obj->vertices() >= 2) - { for (int i = 0; i < obj->vertices(); ++i) - verts << obj->getVertex (i); - } elif (obj->getType() == LDObject::Subfile) - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepCacheInline); - - for (LDObject* obj : objs) - { verts << getVertices (obj); - delete obj; - } - } - - return verts; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::compileObject (LDObject* obj) { - g_vertexCompiler.stageForCompilation (obj); - obj->m_glinit = true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -uchar* GLRenderer::screencap (int& w, int& h) -{ w = m_width; - h = m_height; - uchar* cap = new uchar[4 * w * h]; - - m_screencap = true; - update(); - m_screencap = false; - - // Capture the pixels - glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap); - - return cap; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::slot_toolTipTimer() -{ // We come here if the cursor has stayed in one place for longer than a - // a second. Check if we're holding it over a camera icon - if so, draw - // a tooltip. -for (CameraIcon & icon : m_cameraIcons) - { if (icon.destRect.contains (m_pos)) - { m_toolTipCamera = icon.cam; - m_drawToolTip = true; - update(); - break; - } - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::deleteLists (LDObject* obj) -{ // Delete the lists but only if they have been initialized - if (!obj->m_glinit) - return; - -for (const GL::ListType listType : g_glListTypes) - glDeleteLists (obj->glLists[listType], 1); - - obj->m_glinit = false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -Axis GLRenderer::cameraAxis (bool y, GL::Camera camid) -{ if (camid == (GL::Camera) - 1) - camid = m_camera; - - const LDFixedCameraInfo* cam = &g_FixedCameras[camid]; - return (y) ? cam->axisY : cam->axisX; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool GLRenderer::setupOverlay (GL::Camera cam, str file, int x, int y, int w, int h) -{ QImage* img = new QImage (file); - LDGLOverlay& info = getOverlay (cam); - - if (img->isNull()) - { critical (tr ("Failed to load overlay image!")); - delete img; - return false; - } - - delete info.img; // delete the old image - - info.fname = file; - info.lw = w; - info.lh = h; - info.ox = x; - info.oy = y; - info.img = img; - - if (info.lw == 0) - info.lw = (info.lh * img->width()) / img->height(); - elif (info.lh == 0) - info.lh = (info.lw * img->height()) / img->width(); - - const Axis x2d = cameraAxis (false, cam), - y2d = cameraAxis (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() == Free) - return; - - LDGLOverlay& info = m_overlays[camera()]; - delete info.img; - info.img = null; - - updateOverlayObjects(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::setDepthValue (double depth) -{ assert (camera() < Free); - m_depthValues[camera()] = depth; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -double GLRenderer::depthValue() const -{ assert (camera() < Free); - return m_depthValues[camera()]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const char* GLRenderer::cameraName() const -{ return g_CameraNames[camera()]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDGLOverlay& GLRenderer::getOverlay (int newcam) -{ return m_overlays[newcam]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::zoomNotch (bool inward) -{ if (zoom() > 15) - zoom() *= inward ? 0.833f : 1.2f; - else - zoom() += inward ? -1.2f : 1.2f; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::zoomToFit() -{ if (file() == null || m_width == -1 || m_height == -1) - { zoom() = 30.0f; - return; - } - - bool lastfilled = false; - bool firstrun = true; - const uint32 white = 0xFFFFFFFF; - bool inward = true; - const int w = m_width, h = m_height; - - glClearColor (1.0, 1.0, 1.0, 1.0); - glDisable (GL_DITHER); - - // Use the pick list while drawing the scene, this way we can tell whether borders - // are background or not. - m_picking = true; - - for (;;) - { if (zoom() > 10000.0 || zoom() < 0.0) - { // Obviously, there's nothing to draw if we get here. - // Default to 30.0f and break out. - zoom() = 30.0; - break; - } - - zoomNotch (inward); - - uchar* cap = new uchar[4 * w * h]; - drawGLScene(); - glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap); - uint32* imgdata = reinterpret_cast<uint32*> (cap); - bool filled = false; - - // Check the top and bottom rows - for (int i = 0; i < w && !filled; ++i) - if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white) - filled = true; - - // Left and right edges - for (int i = 0; i < h && !filled; ++i) - if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white) - filled = true; - - delete[] cap; - - if (firstrun) - { // If this is the first run, we don't know enough to determine - // whether the zoom was to fit, so we mark in our knowledge so - // far and start over. - inward = !filled; - firstrun = false; - } - else - { // If this run filled the screen and the last one did not, the - // last run had ideal zoom - zoom a bit back and we should reach it. - if (filled && !lastfilled) - { zoomNotch (false); - break; - } - - // If this run did not fill the screen and the last one did, we've - // now reached ideal zoom so we're done here. - if (!filled && lastfilled) - break; - - inward = !filled; - } - - lastfilled = filled; - } - - setBackground(); - m_picking = false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::zoomAllToFit() -{ Camera oldcam = camera(); - - for (int i = 0; i < 7; ++i) - { setCamera ((Camera) 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 = cameraAxis (false), - ay = cameraAxis (true), - az = (Axis) (3 - ax - ay); - - for (int i = 0; i < 4; ++i) - m_rectverts[i][az] = depthValue(); - - m_rectverts[0][ax] = v0[ax]; - m_rectverts[0][ay] = v0[ay]; - m_rectverts[1][ax] = v1[ax]; - m_rectverts[1][ay] = v0[ay]; - m_rectverts[2][ax] = v1[ax]; - m_rectverts[2][ay] = v1[ay]; - m_rectverts[3][ax] = v0[ax]; - m_rectverts[3][ay] = v1[ay]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev) -{ if (! (ev->buttons() & Qt::LeftButton) || editMode() != Select) - return; - - pick (ev->x(), ev->y()); - - if (selection().isEmpty()) - return; - - g_win->beginAction (null); - LDObject* obj = selection().first(); - AddObjectDialog::staticDialog (obj->getType(), obj); - g_win->endAction(); - ev->accept(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDOverlay* GLRenderer::findOverlayObject (GLRenderer::Camera cam) -{ LDOverlay* ovlobj = null; - - for (LDObject * obj : file()->objects()) - { if (obj->getType() == LDObject::Overlay && static_cast<LDOverlay*> (obj)->camera() == cam) - { ovlobj = static_cast<LDOverlay*> (obj); - break; - } - } - - return ovlobj; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -// Read in overlays from the current file and update overlay info accordingly. -// ----------------------------------------------------------------------------- -void GLRenderer::overlaysFromObjects() -{ for (Camera cam : g_Cameras) - { if (cam == Free) - continue; - - LDGLOverlay& meta = m_overlays[cam]; - LDOverlay* ovlobj = findOverlayObject (cam); - - if (!ovlobj && meta.img) - { delete meta.img; - meta.img = null; - } elif (ovlobj && (!meta.img || meta.fname != ovlobj->filename())) - setupOverlay (cam, ovlobj->filename(), ovlobj->x(), ovlobj->y(), ovlobj->width(), ovlobj->height()); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void GLRenderer::updateOverlayObjects() -{ for (Camera cam : g_Cameras) - { if (cam == Free) - continue; - - LDGLOverlay& meta = m_overlays[cam]; - LDOverlay* ovlobj = findOverlayObject (cam); - - if (!meta.img && ovlobj) - { // If this is the last overlay image, we need to remove the empty space after it as well. - LDObject* nextobj = ovlobj->next(); - - if (nextobj && nextobj->getType() == LDObject::Empty) - { m_file->forgetObject (nextobj); - delete nextobj; - } - - // If the overlay object was there and the overlay itself is - // not, remove the object. - m_file->forgetObject (ovlobj); - delete ovlobj; - } elif (meta.img && !ovlobj) - { // Inverse case: image is there but the overlay object is - // not, thus create the object. - ovlobj = new LDOverlay; - - // Find a suitable position to place this object. We want to place - // this into the header, which is everything up to the first scemantic - // object. If we find another overlay object, place this object after - // the last one found. Otherwise, place it before the first schemantic - // object and put an empty object after it (though don't do this if - // there was no schemantic elements at all) - int i, lastOverlay = -1; - bool found = false; - - for (i = 0; i < file()->numObjs(); ++i) - { LDObject* obj = file()->obj (i); - - if (obj->isScemantic()) - { found = true; - break; - } - - if (obj->getType() == LDObject::Overlay) - lastOverlay = i; - } - - if (lastOverlay != -1) - file()->insertObj (lastOverlay + 1, ovlobj); - else - { file()->insertObj (i, ovlobj); - - if (found) - file()->insertObj (i + 1, new LDEmpty); - } - } - - if (meta.img && ovlobj) - { ovlobj->setCamera (cam); - ovlobj->setFilename (meta.fname); - ovlobj->setX (meta.ox); - ovlobj->setY (meta.oy); - ovlobj->setWidth (meta.lw); - ovlobj->setHeight (meta.lh); - } - } - - if (g_win->R() == this) - g_win->refresh(); -}
--- a/src/gldraw.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/gldraw.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -16,12 +16,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GLDRAW_H -#define GLDRAW_H +#ifndef LDFORGE_GLDRAW_H +#define LDFORGE_GLDRAW_H #include <QGLWidget> -#include "common.h" +#include "main.h" #include "ldtypes.h" +#include "document.h" class MessageManager; class QDialogButtonBox; @@ -29,51 +30,93 @@ class QDoubleSpinBox; class QSpinBox; class QLineEdit; -class LDFile; class QTimer; enum EditMode -{ Select, - Draw, - CircleMode, +{ + ESelectMode, + EDrawMode, + ECircleMode, }; // Meta for overlays struct LDGLOverlay -{ vertex v0, v1; - int ox, oy; - double lw, lh; - str fname; - QImage* img; +{ + 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; +{ + const char glrotate[3]; + const Axis axisX, + axisY; + const bool negX, + negY; }; // ============================================================================= -// GLRenderer +// 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 -{ Q_OBJECT +{ + typedefs: + enum EFixedCamera + { + ETopCamera, + EFrontCamera, + ELeftCamera, + EBottomCamera, + EBackCamera, + ERightCamera, + EFreeCamera + }; - PROPERTY (bool, drawOnly, setDrawOnly) - PROPERTY (MessageManager*, msglog, setMessageLog) - READ_PROPERTY (bool, picking, setPicking) - DECLARE_PROPERTY (LDFile*, file, setFile) - DECLARE_PROPERTY (EditMode, editMode, setEditMode) + enum ListType + { + NormalList, + PickList, + BFCFrontList, + BFCBackList + }; - public: - enum Camera { Top, Front, Left, Bottom, Back, Right, Free }; - enum ListType { NormalList, PickList, BFCFrontList, BFCBackList }; - - enum VAOType { + enum VAOType + { MainArray, EdgeArray, CondEdgeArray, @@ -83,34 +126,56 @@ NumArrays }; + // 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 Camera camera() const - { return m_camera; + inline EFixedCamera camera() const + { + return m_camera; } - Axis cameraAxis (bool y, Camera camid = (Camera) - 1); - const char* cameraName() const; void clearOverlay(); void compileObject (LDObject* obj); void compileAllObjects(); - double depthValue() const; void drawGLScene(); void endDraw (bool accept); - static QColor getMainColor(); + 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 overlaysFromObjects(); + void initOverlaysFromObjects(); void refresh(); void resetAngles(); void resetAllAngles(); - uchar* screencap (int& w, int& h); void setBackground(); - void setCamera (const Camera cam); + void setCamera (const EFixedCamera cam); void setDepthValue (double depth); - bool setupOverlay (GLRenderer::Camera cam, str file, int x, int y, int w, int h); + bool setupOverlay (EFixedCamera cam, QString file, int x, int y, int w, int h); void updateOverlayObjects(); void zoomNotch (bool inward); void zoomToFit(); @@ -133,89 +198,74 @@ void wheelEvent (QWheelEvent* ev); private: - // 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; - Camera cam; - }; + CameraIcon m_cameraIcons[7]; + QTimer* m_toolTipTimer; + Qt::MouseButtons m_lastButtons; + Qt::KeyboardModifiers m_keymods; + Vertex m_hoverpos; + double m_virtWidth, + m_virtHeight; + bool m_darkbg, + m_rangepick, + m_addpick, + m_drawToolTip, + m_screencap, + m_panning; + QPoint m_pos, + m_globalpos, + m_rangeStart; + QPen m_thickBorderPen, + m_thinBorderPen; + EFixedCamera m_camera, + m_toolTipCamera; + GLuint m_axeslist; + int m_width, + m_height, + m_totalmove; + QList<Vertex> m_drawedVerts; + bool m_rectdraw; + Vertex m_rectverts[4]; + QColor m_bgcolor; + QList<Vertex> m_knownVerts; - CameraIcon m_cameraIcons[7]; - QTimer* m_toolTipTimer; - Qt::MouseButtons m_lastButtons; - Qt::KeyboardModifiers m_keymods; - vertex m_hoverpos; - double m_virtWidth, - m_virtHeight, - m_rotX[7], - m_rotY[7], - m_rotZ[7], - m_panX[7], - m_panY[7], - m_zoom[7]; - 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; - Camera m_camera, - m_toolTipCamera; - GLuint m_axeslist; - int m_width, - m_height, - m_totalmove; - QList<vertex> m_drawedVerts; - bool m_rectdraw; - vertex m_rectverts[4]; - QColor m_bgcolor; - double m_depthValues[6]; - LDGLOverlay m_overlays[6]; - QList<vertex> m_knownVerts; - - void addDrawnVertex (vertex m_hoverpos); - LDOverlay* findOverlayObject (Camera cam); + void addDrawnVertex (Vertex m_hoverpos); + LDOverlay* findOverlayObject (EFixedCamera cam); void updateRectVerts(); void getRelativeAxes (Axis& relX, Axis& relY) const; - matrix getCircleDrawMatrix (double scale); + 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 circleDrawDist (int pos) const; - + 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); - + 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; + Vertex coordconv2_3 (const QPoint& pos2d, bool snap) const; +<<<<<<< HEAD // Draw a VAO array void drawVAOs (VAOType arrayType, GLenum type); // Determine which color to draw text with QColor getTextPen() const; +======= + // Convert a 3D point to a 2D point + QPoint coordconv3_2 (const Vertex& pos3d) const; +>>>>>>> 5c835eb // Perform object selection void pick (int mouseX, int mouseY); @@ -223,27 +273,38 @@ // 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) ? m_rotX[camera()] : - (ax == Y) ? m_rotY[camera()] : - m_rotZ[camera()]; + { + return + (ax == X) ? currentDocumentData().rotX : + (ax == Y) ? currentDocumentData().rotY : + currentDocumentData().rotZ; } // Get a panning value inline double& pan (Axis ax) - { return (ax == X) ? m_panX[camera()] : m_panY[camera()]; + { + 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) ? m_panX[camera()] : m_panY[camera()]; + { + return (ax == X) ? currentDocumentData().panX[camera()] : + currentDocumentData().panY[camera()]; } // Get the zoom value inline double& zoom() - { return m_zoom[camera()]; + { + return currentDocumentData().zoom[camera()]; } private slots: @@ -254,13 +315,14 @@ typedef GLRenderer GL; static const GLRenderer::ListType g_glListTypes[] = -{ GL::NormalList, +{ + GL::NormalList, GL::PickList, GL::BFCFrontList, GL::BFCBackList, }; -extern const GL::Camera g_Cameras[7]; +extern const GL::EFixedCamera g_Cameras[7]; extern const char* g_CameraNames[7]; -#endif // GLDRAW_H +#endif // LDFORGE_GLDRAW_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,1011 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QGridLayout> +#include <QMessageBox> +#include <QEvent> +#include <QContextMenuEvent> +#include <QMenuBar> +#include <QStatusBar> +#include <QSplitter> +#include <QListWidget> +#include <QToolButton> +#include <QComboBox> +#include <QDialogButtonBox> +#include <QToolBar> +#include <QProgressBar> +#include <QLabel> +#include <QFileDialog> +#include <QPushButton> +#include <QCoreApplication> +#include <QTimer> +#include <QMetaMethod> + +#include "main.h" +#include "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<QAction*>()) + 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<QAction*>()) + { + 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<LDQuickColor> quickColorsFromConfig() +{ + QList<LDQuickColor> colors; + + for (QString colorname : gui_colortoolbar.split (":")) + { + if (colorname == "|") + colors << LDQuickColor::getSeparator(); + else + { + LDColor* col = getColor (colorname.toLong()); + + if (col != null) + colors << LDQuickColor (col, null); + } + } + + return colors; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void 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 (": <anonymous>"); + + if (getCurrentDocument()->getObjectCount() > 0 && + getCurrentDocument()->getObject (0)->getType() == LDObject::EComment) + { + // Append title + LDComment* comm = static_cast<LDComment*> (getCurrentDocument()->getObject (0)); + title += fmt (": %1", comm->text); + } + + if (getCurrentDocument()->getHistory()->getPosition() != getCurrentDocument()->getSavePosition()) + title += '*'; + } + +#ifdef DEBUG + title += " [debug build]"; +#elif BUILD_ID != BUILD_RELEASE + title += " [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<LDComment*> (obj)->text; + + // Remove leading whitespace + while (descr[0] == ' ') + descr.remove (0, 1); + } break; + + case LDObject::EEmpty: + break; // leave it empty + + case LDObject::ELine: + case LDObject::ETriangle: + case LDObject::EQuad: + case LDObject::ECondLine: + { + for (int i = 0; i < obj->vertices(); ++i) + { + if (i != 0) + descr += ", "; + + descr += obj->getVertex (i).toString (true); + } + } break; + + case LDObject::EError: + { + descr = fmt ("ERROR: %1", obj->raw()); + } break; + + case LDObject::EVertex: + { + descr = static_cast<LDVertex*> (obj)->pos.toString (true); + } break; + + case LDObject::ESubfile: + { + LDSubfile* ref = static_cast<LDSubfile*> (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<LDBFC*> (obj)->type]; + } break; + + case LDObject::EOverlay: + { + LDOverlay* ovl = static_cast<LDOverlay*> (obj); + descr = fmt ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->getCamera()], + basename (ovl->getFileName()), ovl->getX(), ovl->getY(), + ovl->getWidth(), ovl->getHeight()); + } + break; + + default: + { + descr = obj->getTypeName(); + } break; + } + + QListWidgetItem* item = new QListWidgetItem (descr); + item->setIcon (getIcon (obj->getTypeName())); + + // Use italic font if hidden + if (obj->isHidden()) + { + QFont font = item->font(); + font.setItalic (true); + item->setFont (font); + } + + // Color gibberish orange on red so it stands out. + if (obj->getType() == LDObject::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<QListWidgetItem*> items = ui->objectList->selectedItems(); + + for (LDObject* obj : getCurrentDocument()->getObjects()) + { + for (QListWidgetItem* item : items) + { + if (item == obj->qObjListEntry) + { + obj->select(); + break; + } + } + } + + // Update the GL renderer + 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<QAction*> (sender()); + openMainFile (qAct->text()); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void ForgeWindow::slot_quickColor() +{ + QToolButton* button = static_cast<QToolButton*> (sender()); + LDColor* col = null; + + for (const LDQuickColor& entry : m_quickColors) + { + if (entry.getToolButton() == button) + { + col = entry.getColor(); + break; + } + } + + if (col == null) + return; + + int newColor = col->index; + + for (LDObject* obj : selection()) + { + if (obj->isColored() == false) + continue; // uncolored object + + obj->setColor (newColor); + R()->compileObject (obj); + } + + 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<int, int> counts; + + for (LDObject* obj : getCurrentDocument()->getObjects()) + { + if (!obj->isColored()) + continue; + + if (counts.find (obj->getColor()) == counts.end()) + counts[obj->getColor()] = 1; + else + counts[obj->getColor()]++; + } + + box->clear(); + int row = 0; + + for (const auto& pair : counts) + { + LDColor* col = getColor (pair.first); + assert (col != null); + + QIcon ico = makeColorIcon (col, 16); + box->addItem (ico, fmt ("[%1] %2 (%3 object%4)", + pair.first, col->name, pair.second, plural (pair.second))); + box->setItemData (row, pair.first); + + ++row; + } +} + +void ForgeWindow::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; +}
--- a/src/gui.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,924 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QGridLayout> -#include <QMessageBox> -#include <QEvent> -#include <QContextMenuEvent> -#include <QMenuBar> -#include <QStatusBar> -#include <QSplitter> -#include <QListWidget> -#include <QToolButton> -#include <QComboBox> -#include <QDialogButtonBox> -#include <QToolBar> -#include <QProgressBar> -#include <QLabel> -#include <QFileDialog> -#include <QPushButton> -#include <QCoreApplication> -#include <QTimer> - -#include "common.h" -#include "gldraw.h" -#include "gui.h" -#include "file.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_bSelectionLocked = false; - -cfg (Bool, lv_colorize, true); -cfg (String, gui_colortoolbar, "16:24:|:1:2:4:14:0:15:|:33:34:36:46"); -cfg (Bool, gui_implicitfiles, false); -extern_cfg (List, io_recentfiles); -extern_cfg (Bool, gl_axes); -extern_cfg (String, gl_maincolor); -extern_cfg (Float, gl_maincolor_alpha); -extern_cfg (Bool, gl_wireframe); -extern_cfg (Bool, gl_colorbfc); - -#define act(N) extern_cfg (KeySequence, key_##N); -#include "actions.h" - -// ============================================================================= -// ----------------------------------------------------------------------------- -ForgeWindow::ForgeWindow() -{ g_win = this; - m_renderer = new GLRenderer; - - ui = new Ui_LDForgeUI; - ui->setupUi (this); - - // Stuff the renderer into its frame - QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame); - rendererLayout->addWidget (R()); - - connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged())); - connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*))); - connect (ui->fileList, SIGNAL (currentItemChanged (QListWidgetItem*, QListWidgetItem*)), this, SLOT (changeCurrentFile())); - - // Init message log manager - m_msglog = new MessageManager; - m_msglog->setRenderer (R()); - m_renderer->setMessageLog (m_msglog); - m_quickColors = quickColorsFromConfig(); - slot_selectionChanged(); - setStatusBar (new QStatusBar); - - // Init primitive loader task stuff - m_primLoaderBar = new QProgressBar; - m_primLoaderWidget = new QWidget; - QHBoxLayout* primLoaderLayout = new QHBoxLayout (m_primLoaderWidget); - primLoaderLayout->addWidget (new QLabel ("Loading primitives:")); - primLoaderLayout->addWidget (m_primLoaderBar); - statusBar()->addPermanentWidget (m_primLoaderWidget); - m_primLoaderWidget->hide(); - - // Make certain actions checkable - ui->actionAxes->setChecked (gl_axes); - ui->actionWireframe->setChecked (gl_wireframe); - ui->actionBFCView->setChecked (gl_colorbfc); - updateGridToolBar(); - updateEditModeActions(); - updateRecentFilesMenu(); - updateToolBars(); - updateTitle(); - - setMinimumSize (300, 200); - - connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup())); - - // Connect all actions and set shortcuts -#define act(N) \ - connect (ui->action##N, SIGNAL (triggered()), this, SLOT (slot_action())); \ - ui->action##N->setShortcut (key_##N); -#include "actions.h" -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_action() -{ // Find out which action triggered this -#define act(N) if (sender() == ui->action##N) invokeAction (ui->action##N, &actiondef_##N); -#include "actions.h" -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::invokeAction (QAction* act, void (*func) ()) -{ beginAction (act); - (*func) (); - endAction(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_lastSecondCleanup() -{ delete m_renderer; - delete ui; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateRecentFilesMenu() -{ // First, clear any items in the recent files menu -for (QAction * recent : m_recentFiles) - delete recent; - - m_recentFiles.clear(); - - QAction* first = null; - -for (const QVariant & it : io_recentfiles) - { str file = it.toString(); - QAction* recent = new QAction (getIcon ("open-recent"), file, this); - - connect (recent, SIGNAL (triggered()), this, SLOT (slot_recentFile())); - ui->menuOpenRecent->insertAction (first, recent); - m_recentFiles << recent; - first = recent; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDQuickColor> quickColorsFromConfig() -{ QList<LDQuickColor> colors; - -for (str colorname : gui_colortoolbar.value.split (":")) - { if (colorname == "|") - colors << LDQuickColor::getSeparator(); - else - { LDColor* col = getColor (colorname.toLong()); - - if (col != null) - colors << LDQuickColor (col, null); - } - } - - return colors; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateToolBars() -{ m_colorButtons.clear(); - ui->colorToolbar->clear(); - -for (LDQuickColor & entry : m_quickColors) - { if (entry.isSeparator()) - ui->colorToolbar->addSeparator(); - else - { QToolButton* colorButton = new QToolButton; - colorButton->setIcon (makeColorIcon (entry.color(), 22)); - colorButton->setIconSize (QSize (22, 22)); - colorButton->setToolTip (entry.color()->name); - - connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor())); - ui->colorToolbar->addWidget (colorButton); - m_colorButtons << colorButton; - - entry.setToolButton (colorButton); - } - } - - updateGridToolBar(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateGridToolBar() -{ // Ensure that the current grid - and only the current grid - is selected. - ui->actionGridCoarse->setChecked (grid == Grid::Coarse); - ui->actionGridMedium->setChecked (grid == Grid::Medium); - ui->actionGridFine->setChecked (grid == Grid::Fine); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateTitle() -{ str title = fmt (APPNAME " %1", fullVersionString()); - - // Append our current file if we have one - if (LDFile::current()) - { if (LDFile::current()->name().length() > 0) - title += fmt (": %1", basename (LDFile::current()->name())); - else - title += fmt (": <anonymous>"); - - if (LDFile::current()->numObjs() > 0 && - LDFile::current()->obj (0)->getType() == LDObject::Comment) - { // Append title - LDComment* comm = static_cast<LDComment*> (LDFile::current()->obj (0)); - title += fmt (": %1", comm->text); - } - - if (LDFile::current()->history().pos() != LDFile::current()->savePos()) - title += '*'; - } - -#ifdef DEBUG - title += " [debug build]"; -#elif BUILD_ID != BUILD_RELEASE - title += " [release build]"; -#endif // DEBUG - - setWindowTitle (title); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int ForgeWindow::deleteSelection() -{ if (selection().isEmpty()) - return 0; - - QList<LDObject*> selCopy = selection(); - - // Delete the objects that were being selected - for (LDObject* obj : selCopy) - delete obj; - - refresh(); - return selCopy.size(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::buildObjList() -{ if (!LDFile::current()) - 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_bSelectionLocked = true; - - for (int i = 0; i < ui->objectList->count(); ++i) - delete ui->objectList->item (i); - - ui->objectList->clear(); - - for (LDObject* obj : LDFile::current()->objects()) - { str descr; - - switch (obj->getType()) - { case LDObject::Comment: - { descr = static_cast<LDComment*> (obj)->text; - - // Remove leading whitespace - while (descr[0] == ' ') - descr.remove (0, 1); - } break; - - case LDObject::Empty: - break; // leave it empty - - case LDObject::Line: - case LDObject::Triangle: - case LDObject::Quad: - case LDObject::CndLine: - { for (short i = 0; i < obj->vertices(); ++i) - { if (i != 0) - descr += ", "; - - descr += obj->getVertex (i).stringRep (true); - } - } break; - - case LDObject::Error: - { descr = fmt ("ERROR: %1", obj->raw()); - } break; - - case LDObject::Vertex: - { descr = static_cast<LDVertex*> (obj)->pos.stringRep (true); - } break; - - case LDObject::Subfile: - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - - descr = fmt ("%1 %2, (", ref->fileInfo()->name(), ref->position().stringRep (true)); - - for (int i = 0; i < 9; ++i) - descr += fmt ("%1%2", ref->transform()[i], (i != 8) ? " " : ""); - - descr += ')'; - } break; - - case LDObject::BFC: - { descr = LDBFC::statements[static_cast<LDBFC*> (obj)->type]; - } break; - - case LDObject::Overlay: - { LDOverlay* ovl = static_cast<LDOverlay*> (obj); - descr = fmt ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->camera()], - basename (ovl->filename()), ovl->x(), ovl->y(), ovl->width(), ovl->height()); - } - break; - - default: - { descr = obj->typeName(); - } break; - } - - QListWidgetItem* item = new QListWidgetItem (descr); - item->setIcon (getIcon (obj->typeName())); - - // Use italic font if hidden - if (obj->hidden()) - { QFont font = item->font(); - font.setItalic (true); - item->setFont (font); - } - - // Color gibberish orange on red so it stands out. - if (obj->getType() == LDObject::Error) - { item->setBackground (QColor ("#AA0000")); - item->setForeground (QColor ("#FFAA00")); - } - elif (lv_colorize && obj->isColored() && obj->color() != maincolor && obj->color() != edgecolor) - { // If the object isn't in the main or edge color, draw this - // list entry in said color. - LDColor* col = getColor (obj->color()); - - if (col) - item->setForeground (col->faceColor); - } - - obj->qObjListEntry = item; - ui->objectList->insertItem (ui->objectList->count(), item); - } - - g_bSelectionLocked = 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_bSelectionLocked == true || LDFile::current() == 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->picking()) - return; - - QList<LDObject*> priorSelection = selection(); - - // Get the objects from the object list selection - LDFile::current()->clearSelection(); - const QList<QListWidgetItem*> items = ui->objectList->selectedItems(); - - for (LDObject* obj : LDFile::current()->objects()) - { for (QListWidgetItem* item : items) - { if (item == obj->qObjListEntry) - { obj->select(); - break; - } - } - } - - // Update the GL renderer - QList<LDObject*> compound = priorSelection + selection(); - removeDuplicates (compound); - - for (LDObject* obj : compound) - m_renderer->compileObject (obj); - - m_renderer->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_recentFile() -{ QAction* qAct = static_cast<QAction*> (sender()); - openMainFile (qAct->text()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_quickColor() -{ beginAction (null); - QToolButton* button = static_cast<QToolButton*> (sender()); - LDColor* col = null; - - for (const LDQuickColor & entry : m_quickColors) - { if (entry.toolButton() == button) - { col = entry.color(); - break; - } - } - - if (col == null) - return; - - short newColor = col->index; - - for (LDObject* obj : selection()) - { if (obj->isColored() == false) - continue; // uncolored object - - obj->setColor (newColor); - R()->compileObject (obj); - } - - refresh(); - endAction(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int ForgeWindow::getInsertionPoint() -{ // If we have a selection, put the item after it. - if (!selection().isEmpty()) - return selection().last()->getIndex() + 1; - - // Otherwise place the object at the end. - return LDFile::current()->numObjs(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::fullRefresh() -{ buildObjList(); - m_renderer->hardRefresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::refresh() -{ buildObjList(); - m_renderer->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateSelection() -{ g_bSelectionLocked = true; - - for (LDObject* obj : LDFile::current()->objects()) - obj->setSelected (false); - - ui->objectList->clearSelection(); - - for (LDObject* obj : selection()) - { if (obj->qObjListEntry == null) - continue; - - obj->qObjListEntry->setSelected (true); - obj->setSelected (true); - } - - g_bSelectionLocked = false; - slot_selectionChanged(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -short ForgeWindow::getSelectedColor() -{ short result = -1; - - for (LDObject* obj : selection()) - { if (obj->isColored() == false) - continue; // doesn't use color - - if (result != -1 && obj->color() != result) - return -1; // No consensus in object color - - if (result == -1) - result = obj->color(); - } - - return result; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject::Type ForgeWindow::uniformSelectedType() -{ LDObject::Type result = LDObject::Unidentified; - - for (LDObject * obj : selection()) - { if (result != LDObject::Unidentified && obj->color() != result) - return LDObject::Unidentified; - - if (result == LDObject::Unidentified) - result = obj->getType(); - } - - return result; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::closeEvent (QCloseEvent* ev) -{ // Check whether it's safe to close all files. - if (!safeToCloseAll()) - { ev->ignore(); - return; - } - - // Save the configuration before leaving so that, for instance, grid choice - // is preserved across instances. - Config::save(); - - ev->accept(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::spawnContextMenu (const QPoint pos) -{ const bool single = (selection().size() == 1); - LDObject* singleObj = (single) ? selection()[0] : null; - - QMenu* contextMenu = new QMenu; - - if (single && singleObj->getType() != LDObject::Empty) - { contextMenu->addAction (ACTION (Edit)); - contextMenu->addSeparator(); - } - - contextMenu->addAction (ACTION (Cut)); - contextMenu->addAction (ACTION (Copy)); - contextMenu->addAction (ACTION (Paste)); - contextMenu->addAction (ACTION (Delete)); - contextMenu->addSeparator(); - contextMenu->addAction (ACTION (SetColor)); - - if (single) - contextMenu->addAction (ACTION (EditRaw)); - - contextMenu->addAction (ACTION (Borders)); - contextMenu->addAction (ACTION (SetOverlay)); - contextMenu->addAction (ACTION (ClearOverlay)); - contextMenu->addAction (ACTION (ModeSelect)); - contextMenu->addAction (ACTION (ModeDraw)); - contextMenu->addAction (ACTION (ModeCircle)); - - if (R()->camera() != GL::Free) - { contextMenu->addSeparator(); - contextMenu->addAction (ACTION (SetDrawDepth)); - } - - contextMenu->exec (pos); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::deleteObjVector (QList<LDObject*> objs) -{ for (LDObject * obj : objs) - { LDFile::current()->forgetObject (obj); - delete obj; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::deleteByColor (const short colnum) -{ QList<LDObject*> objs; - -for (LDObject * obj : LDFile::current()->objects()) - { if (!obj->isColored() || obj->color() != colnum) - continue; - - objs << obj; - } - - deleteObjVector (objs); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::updateEditModeActions() -{ const EditMode mode = R()->editMode(); - ACTION (ModeSelect)->setChecked (mode == Select); - ACTION (ModeDraw)->setChecked (mode == Draw); - ACTION (ModeCircle)->setChecked (mode == CircleMode); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::slot_editObject (QListWidgetItem* listitem) -{ LDObject* obj = null; - - for (LDObject* it : LDFile::current()->objects()) - { if (it->qObjListEntry == listitem) - { obj = it; - break; - } - } - - AddObjectDialog::staticDialog (obj->getType(), obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::primitiveLoaderStart (int max) -{ m_primLoaderWidget->show(); - m_primLoaderBar->setRange (0, max); - m_primLoaderBar->setValue (0); - m_primLoaderBar->setFormat ("%p%"); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::primitiveLoaderUpdate (int prog) -{ m_primLoaderBar->setValue (prog); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::primitiveLoaderEnd() -{ QTimer* hidetimer = new QTimer; - connect (hidetimer, SIGNAL (timeout()), m_primLoaderWidget, SLOT (hide())); - hidetimer->setSingleShot (true); - hidetimer->start (1500); - m_primLoaderBar->setFormat (tr ("Done")); - log (tr ("Primitives scanned: %1 primitives listed"), m_primLoaderBar->value()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void ForgeWindow::save (LDFile* f, bool saveAs) -{ str path = f->name(); - - if (saveAs || path.isEmpty()) - { path = QFileDialog::getSaveFileName (g_win, tr ("Save As"), - (f->name().isEmpty()) ? f->name() : f->defaultName(), tr ("LDraw files (*.dat *.ldr)")); - - if (path.isEmpty()) - { // User didn't give a file name, abort. - return; - } - } - - if (f->save (path)) - { f->setName (path); - - if (f == LDFile::current()) - g_win->updateTitle(); - - log ("Saved to %1.", path); - - // Add it to recent files - addRecentFile (path); - } - else - { str message = fmt (tr ("Failed to save to %1: %2"), path, strerror (errno)); - - // Tell the user the save failed, and give the option for saving as with it. - QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win); - - // Add a save-as button - QPushButton* saveAsBtn = new QPushButton (tr ("Save As")); - saveAsBtn->setIcon (getIcon ("file-save-as")); - dlg.addButton (saveAsBtn, QMessageBox::ActionRole); - dlg.setDefaultButton (QMessageBox::Close); - dlg.exec(); - - if (dlg.clickedButton() == saveAsBtn) - save (f, true); // yay recursion! - } -} - -void ForgeWindow::addMessage (str msg) -{ m_msglog->addLine (msg); -} - -// ============================================================================ -void ObjectList::contextMenuEvent (QContextMenuEvent* ev) -{ g_win->spawnContextMenu (ev->globalPos()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QPixmap getIcon (str iconName) -{ return (QPixmap (fmt (":/icons/%1.png", iconName))); -} - -// ============================================================================= -bool confirm (str msg) -{ return confirm (ForgeWindow::tr ("Confirm"), msg); -} - -bool confirm (str title, str msg) -{ return QMessageBox::question (g_win, title, msg, - (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes; -} - -// ============================================================================= -void critical (str msg) -{ QMessageBox::critical (g_win, ForgeWindow::tr ("Error"), msg, - (QMessageBox::Close), QMessageBox::Close); -} - -// ============================================================================= -QIcon makeColorIcon (LDColor* colinfo, const int size) -{ // Create an image object and link a painter to it. - QImage img (size, size, QImage::Format_ARGB32); - QPainter paint (&img); - - QColor col = colinfo->faceColor; - - if (colinfo->index == maincolor) - { // Use the user preferences for main color here - col = gl_maincolor.value; - col.setAlphaF (gl_maincolor_alpha); - } - - // Paint the icon - paint.fillRect (QRect (0, 0, size, size), colinfo->edgeColor); - paint.drawPixmap (QRect (1, 1, size - 2, size - 2), getIcon ("checkerboard"), QRect (0, 0, 8, 8)); - paint.fillRect (QRect (1, 1, size - 2, size - 2), col); - return QIcon (QPixmap::fromImage (img)); -} - -// ============================================================================= -void makeColorSelector (QComboBox* box) -{ std::map<int, int> counts; - - for (LDObject * obj : LDFile::current()->objects()) - { if (!obj->isColored()) - continue; - - if (counts.find (obj->color()) == counts.end()) - counts[obj->color()] = 1; - else - counts[obj->color()]++; - } - - box->clear(); - int row = 0; - - for (const auto & pair : counts) - { LDColor* col = getColor (pair.first); - assert (col != null); - - QIcon ico = makeColorIcon (col, 16); - box->addItem (ico, fmt ("[%1] %2 (%3 object%4)", - pair.first, col->name, pair.second, plural (pair.second))); - box->setItemData (row, pair.first); - - ++row; - } -} - -void ForgeWindow::setStatusBarText (str text) -{ statusBar()->showMessage (text); -} - -Ui_LDForgeUI* ForgeWindow::interface() const -{ return ui; -} - -#define act(N) QAction* ForgeWindow::action##N() { return ui->action##N; } -#include "actions.h" - -void ForgeWindow::updateFileList() -{ ui->fileList->clear(); - - for (LDFile* f : g_loadedFiles) - { // Don't list implicit files unless explicitly desired. - if (f->implicit() && !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); - - updateFileListItem (f); - } -} - -void ForgeWindow::updateFileListItem (LDFile* f) -{ if (f->listItem() == 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. - updateFileList(); - return; - } - - // If this is the current file, it also needs to be the selected item on - // the list. - if (f == LDFile::current()) - ui->fileList->setCurrentItem (f->listItem()); - - // If we list implicit files, draw them with a shade of gray to make them - // distinct. - if (f->implicit()) - f->listItem()->setForeground (QColor (96, 96, 96)); - - f->listItem()->setText (f->getShortName()); - - // If the file has unsaved changes, draw a little icon next to it to mark that. - f->listItem()->setIcon (f->hasUnsavedChanges() ? getIcon ("file-save") : QIcon()); -} - -void ForgeWindow::beginAction (QAction* act) -{ // Open the history so we can record the edits done during this action. - if (act != ACTION (Undo) && act != ACTION (Redo) && act != ACTION (Open)) - LDFile::current()->openHistory(); -} - -void ForgeWindow::endAction() -{ // Close the history now. - LDFile::current()->closeHistory(); - - // Update the list item of the current file - we may need to draw an icon - // now that marks it as having unsaved changes. - updateFileListItem (LDFile::current()); -} - -// ============================================================================= -// 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() -{ LDFile* f = null; - QListWidgetItem* item = ui->fileList->currentItem(); - - // Find the file pointer of the item that was selected. -for (LDFile * it : g_loadedFiles) - { if (it->listItem() == item) - { f = it; - break; - } - } - - // If we picked the same file we're currently on, we don't need to do - // anything. - if (!f || f == LDFile::current()) - return; - - LDFile::setCurrent (f); -} - -void ForgeWindow::refreshObjectList() -{ -#if 0 - ui->objectList->clear(); - LDFile* f = LDFile::current(); - -for (LDObject * obj : *f) - ui->objectList->addItem (obj->qObjListEntry); - -#endif - - buildObjList(); -} - -QImage imageFromScreencap (uchar* data, int w, int h) -{ // GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well. - return QImage (data, w, h, QImage::Format_ARGB32).rgbSwapped().mirrored(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDQuickColor::LDQuickColor (LDColor* color, QToolButton* toolButton) : - m_color (color), - m_toolButton (toolButton) {} - -LDQuickColor LDQuickColor::getSeparator() -{ return LDQuickColor (null, null); -} - -bool LDQuickColor::isSeparator() const -{ return color() == null; -}
--- a/src/gui.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/gui.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -16,8 +16,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_H -#define GUI_H +#ifndef LDFORGE_GUI_H +#define LDFORGE_GUI_H #include <QMainWindow> #include <QAction> @@ -25,6 +25,7 @@ #include <QRadioButton> #include "config.h" #include "ldtypes.h" +#include "ui_ldforge.h" class MessageManager; class ForgeWindow; @@ -41,15 +42,13 @@ 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_##NAME, DEFSHORTCUT); \ - void actiondef_##NAME() - -#define ACTION(N) g_win->action##N() + cfg (KeySequence, key_action##NAME, DEFSHORTCUT); \ + void ForgeWindow::slot_action##NAME() // Convenience macros for key sequences. #define KEY(N) (Qt::Key_##N) @@ -59,8 +58,9 @@ // ============================================================================= class LDQuickColor -{ PROPERTY (LDColor*, color, setColor) - PROPERTY (QToolButton*, toolButton, setToolButton) +{ + PROPERTY (public, LDColor*, Color, NO_OPS, STOCK_WRITE) + PROPERTY (public, QToolButton*, ToolButton, NO_OPS, STOCK_WRITE) public: LDQuickColor (LDColor* color, QToolButton* toolButton); @@ -75,7 +75,8 @@ // Object list class for ForgeWindow // ============================================================================= class ObjectList : public QListWidget -{ Q_OBJECT +{ + Q_OBJECT protected: void contextMenuEvent (QContextMenuEvent* ev); @@ -89,13 +90,14 @@ // Large and in charge. // ============================================================================= class ForgeWindow : public QMainWindow -{ Q_OBJECT +{ + Q_OBJECT public: ForgeWindow(); void buildObjList(); void updateTitle(); - void fullRefresh(); + void doFullRefresh(); void refresh(); int getInsertionPoint(); void updateToolBars(); @@ -103,59 +105,144 @@ void updateSelection(); void updateGridToolBar(); void updateEditModeActions(); - void updateFileList(); - void updateFileListItem (LDFile* f); - short getSelectedColor(); - LDObject::Type uniformSelectedType(); + void updateDocumentList(); + void updateDocumentListItem (LDDocument* f); + int getSelectedColor(); + LDObject::Type getUniformSelectedType(); void scrollToSelection(); void spawnContextMenu (const QPoint pos); - void deleteObjVector (QList< LDObject* > objs); + void deleteObjects (LDObjectList objs); int deleteSelection(); - void deleteByColor (const short int colnum); - void save (LDFile* f, bool saveAs); + void deleteByColor (const int colnum); + bool save (LDDocument* f, bool saveAs); + void updateActions(); inline GLRenderer* R() - { return m_renderer; + { + return m_renderer; } inline void setQuickColors (QList<LDQuickColor>& colors) - { m_quickColors = colors; + { + m_quickColors = colors; } - void setStatusBarText (str text); - void addMessage (str msg); - Ui_LDForgeUI* interface() const; + void addMessage (QString msg); void refreshObjectList(); - void beginAction (QAction* act); + void updateActionShortcuts(); + KeySequenceConfig* shortcutForAction (QAction* act); void endAction(); -#define act(N) QAction* action##N(); -#include "actions.h" - public slots: - void primitiveLoaderStart (int max); - void primitiveLoaderUpdate (int prog); - void primitiveLoaderEnd(); + void changeCurrentFile(); void slot_action(); - void changeCurrentFile(); + 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; - QProgressBar* m_primLoaderBar; - QWidget* m_primLoaderWidget; - QList<LDObject*> m_sel; + LDObjectList m_sel; QList<LDQuickColor> m_quickColors; QList<QToolButton*> m_colorButtons; QList<QAction*> m_recentFiles; MessageManager* m_msglog; Ui_LDForgeUI* ui; - void invokeAction (QAction* act, void (*func)()); - - private slots: void slot_selectionChanged(); void slot_recentFile(); @@ -164,23 +251,19 @@ void slot_editObject (QListWidgetItem* listitem); }; -#define INVOKE_ACTION(N) actiondef_##N(); -#define act(N) void actiondef_##N(); -#include "actions.h" - // ----------------------------------------------------------------------------- // Pointer to the instance of ForgeWindow. extern ForgeWindow* g_win; // ----------------------------------------------------------------------------- // Other GUI-related stuff not directly part of ForgeWindow: -QPixmap getIcon (str iconName); // Get an icon from the resource dir +QPixmap getIcon (QString iconName); // Get an icon from the resource dir QList<LDQuickColor> quickColorsFromConfig(); // Make a list of quick colors based on config -bool confirm (str title, str msg); // Generic confirm prompt -bool confirm (str msg); // Generic confirm prompt -void critical (str msg); // Generic error prompt +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 makeColorSelector (QComboBox* box); // Fills the given combo-box with color information +void makeColorComboBox (QComboBox* box); // Fills the given combo-box with color information QImage imageFromScreencap (uchar* data, int w, int h); // ============================================================================= @@ -188,8 +271,10 @@ // Takes in pairs of radio buttons and respective values and returns the value of // the first found radio button that was checked. // ============================================================================= -template<class T> T radioSwitch (const T& defval, QList<pair<QRadioButton*, T>> haystack) -{ for (pair<QRadioButton*, const T&> i : haystack) +template<class T> +T radioSwitch (const T& defval, QList<pair<QRadioButton*, T>> haystack) +{ + for (pair<QRadioButton*, const T&> i : haystack) if (i.first->isChecked()) return i.second; @@ -201,13 +286,17 @@ // Takes in pairs of radio buttons and respective values and checks the first // found radio button to have the given value. // ============================================================================= -template<class T> void radioDefault (const T& expr, QList<pair<QRadioButton*, T>> haystack) -{ for (pair<QRadioButton*, const T&> i : haystack) - { if (i.second == expr) - { i.first->setChecked (true); +template<class T> +void radioDefault (const T& expr, QList<pair<QRadioButton*, T>> haystack) +{ + for (pair<QRadioButton*, const T&> i : haystack) + { + if (i.second == expr) + { + i.first->setChecked (true); return; } } } -#endif // GUI_H +#endif // LDFORGE_GUI_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui_actions.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,873 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QFileDialog> +#include <QMessageBox> +#include <QTextEdit> +#include <QBoxLayout> +#include <QDialogButtonBox> +#include <QPushButton> +#include <QInputDialog> + +#include "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: <untitled>.dat"), + new LDComment (fmt ("Author: %1", ui.le_author->text())), + new LDComment (fmt ("!LDRAW_ORG Unofficial_Part")), + (license != "" ? new LDComment (license) : null), + new LDEmpty, + new LDBFC (BFCType), + new LDEmpty, + }); + + 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<LDSubfile*> (selection()[0])->getFileInfo()->getName(); + + for (LDObject* obj : selection()) + if (static_cast<LDSubfile*> (obj)->getFileInfo()->getName() != refName) + return; + } + + getCurrentDocument()->clearSelection(); + + for (LDObject* obj : getCurrentDocument()->getObjects()) + { + if (obj->getType() != type) + continue; + + if (type == LDObject::ESubfile && static_cast<LDSubfile*> (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<LDComment*> (getCurrentDocument()->getObject (0)); + + // License text for the subfile + QString license = getLicenseText (ld_defaultlicense); + + // LDraw code body of the new subfile (i.e. code of the selection) + QStringList code; + + // Full path of the subfile to be + QString fullsubname; + + // Where to insert the subfile reference? + int refidx = selection()[0]->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 <b>%1</b> is suggested for " + "subfiles. This directory does not exist, create it?"), desiredPath); + + if (QDir (desiredPath).exists() || confirm (title, text)) + { + subdirname = desiredPath; + QDir().mkpath (subdirname); + } + } + + // Determine the body of the name of the subfile + if (!parentpath.isEmpty()) + { + if (parentpath.endsWith (".dat")) + parentpath.chop (4); + + // Remove the s?? suffix if it's there, otherwise we'll get filenames + // like s01s01.dat when subfiling subfiles. + QRegExp subfilesuffix ("s[0-9][0-9]$"); + if (subfilesuffix.indexIn (parentpath) != -1) + parentpath.chop (subfilesuffix.matchedLength()); + + int subidx = 1; + QString digits; + QFile f; + QString testfname; + + do + { + digits.setNum (subidx++); + + // pad it with a zero + if (digits.length() == 1) + digits.prepend ("0"); + + fullsubname = subdirname + "/" + basename (parentpath) + "s" + digits + ".dat"; + } while (findDocument ("s\\" + basename (fullsubname)) != null || QFile (fullsubname).exists()); + } + + // Determine the BFC winding type used in the main document - it is to + // be carried over to the subfile. + for (LDObject* obj : getCurrentDocument()->getObjects()) + { + LDBFC* bfc = dynamic_cast<LDBFC*> (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
--- a/src/gui_actions.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,636 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QFileDialog> -#include <QMessageBox> -#include <QTextEdit> -#include <QBoxLayout> -#include <QDialogButtonBox> -#include <QPushButton> -#include <QInputDialog> - -#include "gui.h" -#include "file.h" -#include "history.h" -#include "configDialog.h" -#include "addObjectDialog.h" -#include "misc.h" -#include "gldraw.h" -#include "dialogs.h" -#include "primitives.h" -#include "ui_newpart.h" -#include "widgets.h" - -extern_cfg (Bool, gl_wireframe); -extern_cfg (Bool, gl_colorbfc); -extern_cfg (String, ld_defaultname); -extern_cfg (String, ld_defaultuser); -extern_cfg (Int, ld_defaultlicense); - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (New, CTRL_SHIFT (N)) -{ QDialog* dlg = new QDialog (g_win); - Ui::NewPartUI ui; - ui.setupUi (dlg); - - str authortext = ld_defaultname; - - if (!ld_defaultuser.value.isEmpty()) - authortext.append (fmt (" [%1]", ld_defaultuser)); - - ui.le_author->setText (authortext); - - switch (ld_defaultlicense) - { case 0: - ui.rb_license_ca->setChecked (true); - break; - - case 1: - ui.rb_license_nonca->setChecked (true); - break; - - case 2: - ui.rb_license_none->setChecked (true); - break; - - default: - QMessageBox::warning (null, "Warning", - fmt ("Unknown ld_defaultlicense value %1!", ld_defaultlicense)); - break; - } - - if (dlg->exec() == false) - return; - - newFile(); - - const LDBFC::Type BFCType = - ui.rb_bfc_ccw->isChecked() ? LDBFC::CertifyCCW : - ui.rb_bfc_cw->isChecked() ? LDBFC::CertifyCW : LDBFC::NoCertify; - - const str license = - ui.rb_license_ca->isChecked() ? CALicense : - ui.rb_license_nonca->isChecked() ? NonCALicense : ""; - - LDFile::current()->addObjects ( - { new LDComment (ui.le_title->text()), - new LDComment ("Name: <untitled>.dat"), - new LDComment (fmt ("Author: %1", ui.le_author->text())), - new LDComment (fmt ("!LDRAW_ORG Unofficial_Part")), - (license != "" ? - new LDComment (license) : - null), - new LDEmpty, - new LDBFC (BFCType), - new LDEmpty, - }); - - g_win->fullRefresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewFile, CTRL (N)) -{ newFile(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Open, CTRL (O)) -{ str name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)"); - - if (name.length() == 0) - return; - - openMainFile (name); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Save, CTRL (S)) -{ g_win->save (LDFile::current(), false); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SaveAs, CTRL_SHIFT (S)) -{ g_win->save (LDFile::current(), true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SaveAll, CTRL (L)) -{ for (LDFile* file : g_loadedFiles) - { if (file->implicit()) - continue; - - g_win->save (file, false); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Close, CTRL (W)) -{ if (!LDFile::current()->safeToClose()) - return; - - delete LDFile::current(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (CloseAll, 0) -{ if (!safeToCloseAll()) - return; - - closeAll(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Settings, 0) -{ (new ConfigDialog)->exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SetLDrawPath, 0) -{ (new LDrawPathDialog (true))->exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Exit, CTRL (Q)) -{ exit (0); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewSubfile, 0) -{ AddObjectDialog::staticDialog (LDObject::Subfile, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewLine, 0) -{ AddObjectDialog::staticDialog (LDObject::Line, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewTriangle, 0) -{ AddObjectDialog::staticDialog (LDObject::Triangle, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewQuad, 0) -{ AddObjectDialog::staticDialog (LDObject::Quad, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewCLine, 0) -{ AddObjectDialog::staticDialog (LDObject::CndLine, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewComment, 0) -{ AddObjectDialog::staticDialog (LDObject::Comment, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewBFC, 0) -{ AddObjectDialog::staticDialog (LDObject::BFC, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (NewVertex, 0) -{ AddObjectDialog::staticDialog (LDObject::Vertex, null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Edit, 0) -{ if (selection().size() != 1) - return; - - LDObject* obj = selection() [0]; - AddObjectDialog::staticDialog (obj->getType(), obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Help, KEY (F1)) -{ -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (About, 0) -{ AboutDialog().exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (AboutQt, 0) -{ QMessageBox::aboutQt (g_win); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SelectAll, CTRL (A)) -{ for (LDObject* obj : LDFile::current()->objects()) - obj->select(); - - g_win->updateSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SelectByColor, CTRL_SHIFT (A)) -{ short colnum = g_win->getSelectedColor(); - - if (colnum == -1) - return; // no consensus on color - - LDFile::current()->clearSelection(); - - for (LDObject* obj : LDFile::current()->objects()) - if (obj->color() == colnum) - obj->select(); - - g_win->updateSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SelectByType, 0) -{ if (selection().isEmpty()) - return; - - LDObject::Type type = g_win->uniformSelectedType(); - - if (type == LDObject::Unidentified) - return; - - // If we're selecting subfile references, the reference filename must also - // be uniform. - str refName; - - if (type == LDObject::Subfile) - { refName = static_cast<LDSubfile*> (selection()[0])->fileInfo()->name(); - - for (LDObject* obj : selection()) - if (static_cast<LDSubfile*> (obj)->fileInfo()->name() != refName) - return; - } - - LDFile::current()->clearSelection(); - - for (LDObject* obj : LDFile::current()->objects()) - { if (obj->getType() != type) - continue; - - if (type == LDObject::Subfile && static_cast<LDSubfile*> (obj)->fileInfo()->name() != refName) - continue; - - obj->select(); - } - - g_win->updateSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (GridCoarse, 0) -{ grid = Grid::Coarse; - g_win->updateGridToolBar(); -} - -DEFINE_ACTION (GridMedium, 0) -{ grid = Grid::Medium; - g_win->updateGridToolBar(); -} - -DEFINE_ACTION (GridFine, 0) -{ grid = Grid::Fine; - g_win->updateGridToolBar(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ResetView, CTRL (0)) -{ g_win->R()->resetAngles(); - g_win->R()->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (InsertFrom, 0) -{ str fname = QFileDialog::getOpenFileName(); - int idx = g_win->getInsertionPoint(); - - if (!fname.length()) - return; - - File f (fname, File::Read); - - if (!f) - { critical (fmt ("Couldn't open %1 (%2)", fname, strerror (errno))); - return; - } - - QList<LDObject*> objs = loadFileContents (&f, null); - - LDFile::current()->clearSelection(); - - for (LDObject* obj : objs) - { LDFile::current()->insertObj (idx, obj); - obj->select(); - g_win->R()->compileObject (obj); - - idx++; - } - - g_win->refresh(); - g_win->scrollToSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ExportTo, 0) -{ if (selection().isEmpty()) - return; - - str fname = QFileDialog::getSaveFileName(); - - if (fname.length() == 0) - return; - - QFile file (fname); - - if (!file.open (QIODevice::WriteOnly | QIODevice::Text)) - { critical (fmt ("Unable to open %1 for writing (%2)", fname, strerror (errno))); - return; - } - - for (LDObject* obj : selection()) - { str contents = obj->raw(); - QByteArray data = contents.toUtf8(); - file.write (data, data.size()); - file.write ("\r\n", 2); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (InsertRaw, 0) -{ int idx = g_win->getInsertionPoint(); - - QDialog* const dlg = new QDialog; - QVBoxLayout* const layout = new QVBoxLayout; - QTextEdit* const te_edit = new QTextEdit; - QDialogButtonBox* const bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - - layout->addWidget (te_edit); - layout->addWidget (bbx_buttons); - dlg->setLayout (layout); - dlg->setWindowTitle (APPNAME ": Insert Raw"); - dlg->connect (bbx_buttons, SIGNAL (accepted()), dlg, SLOT (accept())); - dlg->connect (bbx_buttons, SIGNAL (rejected()), dlg, SLOT (reject())); - - if (dlg->exec() == false) - return; - - LDFile::current()->clearSelection(); - - for (str line : str (te_edit->toPlainText()).split ("\n")) - { LDObject* obj = parseLine (line); - - LDFile::current()->insertObj (idx, obj); - obj->select(); - g_win->R()->compileObject (obj); - idx++; - } - - g_win->refresh(); - g_win->scrollToSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Screenshot, 0) -{ setlocale (LC_ALL, "C"); - - int w, h; - uchar* imgdata = g_win->R()->screencap (w, h); - QImage img = imageFromScreencap (imgdata, w, h); - - str root = basename (LDFile::current()->name()); - - if (root.right (4) == ".dat") - root.chop (4); - - str defaultname = (root.length() > 0) ? fmt ("%1.png", root) : ""; - str fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname, - "PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)"); - - if (fname.length() > 0 && !img.save (fname)) - critical (fmt ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno))); - - delete[] imgdata; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -extern_cfg (Bool, gl_axes); -DEFINE_ACTION (Axes, 0) -{ gl_axes = !gl_axes; - ACTION (Axes)->setChecked (gl_axes); - g_win->R()->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (VisibilityToggle, 0) -{ for (LDObject* obj : selection()) - obj->setHidden (!obj->hidden()); - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (VisibilityHide, 0) -{ for (LDObject* obj : selection()) - obj->setHidden (true); - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (VisibilityReveal, 0) -{ for (LDObject* obj : selection()) - obj->setHidden (false); - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Wireframe, 0) -{ gl_wireframe = !gl_wireframe; - g_win->R()->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SetOverlay, 0) -{ OverlayDialog dlg; - - if (!dlg.exec()) - return; - - g_win->R()->setupOverlay ( (GL::Camera) dlg.camera(), dlg.fpath(), dlg.ofsx(), - dlg.ofsy(), dlg.lwidth(), dlg.lheight()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ClearOverlay, 0) -{ g_win->R()->clearOverlay(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ModeSelect, CTRL (1)) -{ g_win->R()->setEditMode (Select); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ModeDraw, CTRL (2)) -{ g_win->R()->setEditMode (Draw); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ModeCircle, CTRL (3)) -{ g_win->R()->setEditMode (CircleMode); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SetDrawDepth, 0) -{ if (g_win->R()->camera() == GL::Free) - return; - - bool ok; - double depth = QInputDialog::getDouble (g_win, "Set Draw Depth", - fmt ("Depth value for %1 Camera:", g_win->R()->cameraName()), - g_win->R()->depthValue(), -10000.0f, 10000.0f, 3, &ok); - - if (ok) - g_win->R()->setDepthValue (depth); -} - -#if 0 -// This is a test to draw a dummy axle. Meant to be used as a primitive gallery, -// but I can't figure how to generate these pictures properly. Multi-threading -// these is an immense pain. -DEFINE_ACTION (testpic, "Test picture", "", "", (0)) -{ LDFile* file = getFile ("axle.dat"); - setlocale (LC_ALL, "C"); - - if (!file) - { critical ("couldn't load axle.dat"); - return; - } - - int w, h; - - GLRenderer* rend = new GLRenderer; - rend->resize (64, 64); - rend->setAttribute (Qt::WA_DontShowOnScreen); - rend->show(); - rend->setFile (file); - rend->setDrawOnly (true); - rend->compileAllObjects(); - rend->initGLData(); - rend->drawGLScene(); - - uchar* imgdata = rend->screencap (w, h); - QImage img = imageFromScreencap (imgdata, w, h); - - if (img.isNull()) - { critical ("Failed to create the image!\n"); - } - else - { QLabel* label = new QLabel; - QDialog* dlg = new QDialog; - label->setPixmap (QPixmap::fromImage (img)); - QVBoxLayout* layout = new QVBoxLayout (dlg); - layout->addWidget (label); - dlg->exec(); - } - - delete[] imgdata; - rend->deleteLater(); -} -#endif - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ScanPrimitives, 0) -{ PrimitiveLister::start(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (BFCView, SHIFT (B)) -{ gl_colorbfc = !gl_colorbfc; - ACTION (BFCView)->setChecked (gl_colorbfc); - g_win->R()->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (JumpTo, CTRL (G)) -{ bool ok; - int defval = 0; - LDObject* obj; - - if (selection().size() == 1) - defval = selection()[0]->getIndex(); - - int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval, - 1, LDFile::current()->numObjs(), 1, &ok); - - if (!ok || (obj = LDFile::current()->object (idx - 1)) == null) - return; - - LDFile::current()->clearSelection(); - obj->select(); - g_win->updateSelection(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui_editactions.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,838 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QSpinBox> +#include <QCheckBox> +#include <QBoxLayout> +#include <QClipboard> + +#include "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<LDSubfile*> (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<LDTriangle*> triangles = static_cast<LDQuad*> (obj)->splitToTriangles(); + + // Replace the quad with the first triangle and add the second triangle + // after the first one. + getCurrentDocument()->setObject (index, triangles[0]); + getCurrentDocument()->insertObj (index + 1, triangles[1]); + + for (LDTriangle* t : triangles) + R()->compileObject (t); + + // Delete this quad now, it has been split. + obj->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<LDError*> (obj)->reason); + else + { + ui.errorDescription->hide(); + ui.errorIcon->hide(); + } + + if (!dlg->exec()) + return; + + LDObject* oldobj = obj; + + // Reinterpret it from the text of the input field + obj = parseLine (ui.code->text()); + oldobj->replace (obj); + + // Refresh + R()->compileObject (obj); + refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (SetColor, KEY (C)) +{ + if (selection().isEmpty()) + return; + + int colnum; + int defcol = -1; + + LDObjectList objs = selection(); + + // If all selected objects have the same color, said color is our default + // value to the color selection dialog. + defcol = getSelectedColor(); + + // Show the dialog to the user now and ask for a color. + if (ColorSelector::selectColor (colnum, defcol, g_win)) + { + for (LDObject* obj : objs) + { + if (obj->isColored() == false) + continue; + + obj->setColor (colnum); + R()->compileObject (obj); + } + + refresh(); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (Borders, CTRL_SHIFT (B)) +{ + LDObjectList objs = selection(); + int num = 0; + + for (LDObject* obj : objs) + { + const LDObject::Type type = obj->getType(); + if (type != LDObject::EQuad && type != LDObject::ETriangle) + continue; + + int numLines; + LDLine* lines[4]; + + if (type == LDObject::EQuad) + { + numLines = 4; + + LDQuad* quad = static_cast<LDQuad*> (obj); + lines[0] = new LDLine (quad->getVertex (0), quad->getVertex (1)); + lines[1] = new LDLine (quad->getVertex (1), quad->getVertex (2)); + lines[2] = new LDLine (quad->getVertex (2), quad->getVertex (3)); + lines[3] = new LDLine (quad->getVertex (3), quad->getVertex (0)); + } + else + { + numLines = 3; + + LDTriangle* tri = static_cast<LDTriangle*> (obj); + lines[0] = new LDLine (tri->getVertex (0), tri->getVertex (1)); + lines[1] = new LDLine (tri->getVertex (1), tri->getVertex (2)); + lines[2] = new LDLine (tri->getVertex (2), tri->getVertex (0)); + } + + for (int i = 0; i < numLines; ++i) + { + long idx = obj->getIndex() + i + 1; + + lines[i]->setColor (edgecolor); + getCurrentDocument()->insertObj (idx, lines[i]); + 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<Vertex*> queue; + const Vertex rotpoint = rotPoint (sel); + const double angle = (pi * *currentGrid().confs[Grid::Angle]) / 180, + cosangle = cos (angle), + sinangle = sin (angle); + + // ref: http://en.wikipedia.org/wiki/Transformation_matrix#Rotation_2 + Matrix transform ( + { + (l* l * (1 - cosangle)) + cosangle, + (m* l * (1 - cosangle)) - (n* sinangle), + (n* l * (1 - cosangle)) + (m* sinangle), + + (l* m * (1 - cosangle)) + (n* sinangle), + (m* m * (1 - cosangle)) + cosangle, + (n* m * (1 - cosangle)) - (l* sinangle), + + (l* n * (1 - cosangle)) - (m* sinangle), + (m* n * (1 - cosangle)) + (l* sinangle), + (n* n * (1 - cosangle)) + cosangle + }); + + // Apply the above matrix to everything + for (LDObject* obj : sel) + { + if (obj->vertices()) + { + for (int i = 0; i < obj->vertices(); ++i) + { + Vertex v = obj->getVertex (i); + rotateVertex (v, rotpoint, transform); + obj->setVertex (i, v); + } + } + elif (obj->hasMatrix()) + { + LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj); + + // Transform the position + Vertex v = mo->getPosition(); + rotateVertex (v, rotpoint, transform); + mo->setPosition (v); + + // Transform the matrix + mo->setTransform (transform * mo->getTransform()); + } + elif (obj->getType() == LDObject::EVertex) + { + LDVertex* vert = static_cast<LDVertex*> (obj); + Vertex v = vert->pos; + rotateVertex (v, rotpoint, transform); + vert->pos = v; + } + + g_win->R()->compileObject (obj); + } + + g_win->refresh(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (RotateXPos, CTRL (Right)) +{ + doRotate (1, 0, 0); +} +DEFINE_ACTION (RotateYPos, CTRL (End)) +{ + doRotate (0, 1, 0); +} +DEFINE_ACTION (RotateZPos, CTRL (Up)) +{ + doRotate (0, 0, 1); +} +DEFINE_ACTION (RotateXNeg, CTRL (Left)) +{ + doRotate (-1, 0, 0); +} +DEFINE_ACTION (RotateYNeg, CTRL (Home)) +{ + doRotate (0, -1, 0); +} +DEFINE_ACTION (RotateZNeg, CTRL (Down)) +{ + doRotate (0, 0, -1); +} + +DEFINE_ACTION (RotationPoint, (0)) +{ + configRotationPoint(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +DEFINE_ACTION (RoundCoordinates, 0) +{ + setlocale (LC_ALL, "C"); + int num = 0; + + for (LDObject* obj : selection()) + { + LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj); + + if (mo != null) + { + Vertex v = mo->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<Axis> sel; + int num = 0; + + if (ui.x->isChecked()) sel << X; + if (ui.y->isChecked()) sel << Y; + if (ui.z->isChecked()) sel << Z; + + for (LDObject* obj : selection()) + { + for (int i = 0; i < obj->vertices(); ++i) + { + Vertex v = obj->getVertex (i); + + for (Axis ax : sel) + { + double& coord = v[ax]; + + if (any || coord == search) + { + if (!rel) + coord = 0; + + coord += replacement; + num++; + } + } + + obj->setVertex (i, v); + 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<Axis> sel; + + if (ui.x->isChecked()) sel << X; + if (ui.y->isChecked()) sel << Y; + if (ui.z->isChecked()) sel << Z; + + for (LDObject* obj : selection()) + { + for (int i = 0; i < obj->vertices(); ++i) + { + Vertex v = obj->getVertex (i); + + for (Axis ax : sel) + v[ax] *= -1; + + obj->setVertex (i, v); + 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<LDCondLine*> (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<LDComment*> (obj); + + if (comm && comm->text.startsWith ("!HISTORY ")) + ishistory = true; + + if (prevIsHistory && !ishistory) + { + // Last line was history, this isn't, thus insert the new history + // line here. + break; + } + + prevIsHistory = ishistory; + } + + int idx = obj ? obj->getIndex() : 0; + getCurrentDocument()->insertObj (idx++, comm); + + // If we're adding a history line right before a scemantic object, pad it + // an empty line + if (obj && obj->next() && obj->next()->isScemantic()) + getCurrentDocument()->insertObj (idx, new LDEmpty); + + buildObjList(); + delete ui; +}
--- a/src/gui_editactions.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,741 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QSpinBox> -#include <QCheckBox> -#include <QBoxLayout> -#include <QClipboard> - -#include "gui.h" -#include "common.h" -#include "file.h" -#include "colorSelectDialog.h" -#include "misc.h" -#include "widgets.h" -#include "gldraw.h" -#include "dialogs.h" -#include "colors.h" -#include "ui_replcoords.h" -#include "ui_editraw.h" -#include "ui_flip.h" -#include "ui_addhistoryline.h" - -cfg (Bool, edit_schemanticinline, false); -extern_cfg (String, ld_defaultuser); - -// ============================================================================= -// ----------------------------------------------------------------------------- -static int copyToClipboard() -{ QList<LDObject*> objs = selection(); - int num = 0; - - // Clear the clipboard first. - qApp->clipboard()->clear(); - - // Now, copy the contents into the clipboard. - str data; - - for (LDObject* obj : objs) - { if (data.length() > 0) - data += "\n"; - - data += obj->raw(); - ++num; - } - - qApp->clipboard()->setText (data); - return num; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Cut, CTRL (X)) -{ int num = copyToClipboard(); - g_win->deleteSelection(); - log (ForgeWindow::tr ("%1 objects cut"), num); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Copy, CTRL (C)) -{ int num = copyToClipboard(); - log (ForgeWindow::tr ("%1 objects copied"), num); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Paste, CTRL (V)) -{ const str clipboardText = qApp->clipboard()->text(); - int idx = g_win->getInsertionPoint(); - LDFile::current()->clearSelection(); - int num = 0; - - for (str line : clipboardText.split ("\n")) - { LDObject* pasted = parseLine (line); - LDFile::current()->insertObj (idx++, pasted); - pasted->select(); - g_win->R()->compileObject (pasted); - ++num; - } - - log (ForgeWindow::tr ("%1 objects pasted"), num); - g_win->refresh(); - g_win->scrollToSelection(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Delete, KEY (Delete)) -{ int num = g_win->deleteSelection(); - log (ForgeWindow::tr ("%1 objects deleted"), num); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void doInline (bool deep) -{ QList<LDObject*> sel = selection(); - - for (LDObject* obj : sel) - { // Get the index of the subfile so we know where to insert the - // inlined contents. - long idx = obj->getIndex(); - - if (idx == -1) - continue; - - QList<LDObject*> objs; - - if (obj->getType() == LDObject::Subfile) - objs = static_cast<LDSubfile*> (obj)->inlineContents ( - (LDSubfile::InlineFlags) - ( (deep) ? LDSubfile::DeepInline : 0) | - LDSubfile::CacheInline - ); - else - continue; - - // Merge in the inlined objects - for (LDObject * inlineobj : objs) - { str line = inlineobj->raw(); - delete inlineobj; - - LDObject* newobj = parseLine (line); - LDFile::current()->insertObj (idx++, newobj); - newobj->select(); - g_win->R()->compileObject (newobj); - } - - // Delete the subfile now as it's been inlined. - LDFile::current()->forgetObject (obj); - delete obj; - } - - g_win->refresh(); -} - -DEFINE_ACTION (Inline, CTRL (I)) -{ doInline (false); -} - -DEFINE_ACTION (InlineDeep, CTRL_SHIFT (I)) -{ doInline (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SplitQuads, 0) -{ QList<LDObject*> objs = selection(); - int num = 0; - - for (LDObject* obj : objs) - { if (obj->getType() != LDObject::Quad) - continue; - - // Find the index of this quad - long index = obj->getIndex(); - - if (index == -1) - return; - - QList<LDTriangle*> triangles = static_cast<LDQuad*> (obj)->splitToTriangles(); - - // Replace the quad with the first triangle and add the second triangle - // after the first one. - LDFile::current()->setObject (index, triangles[0]); - LDFile::current()->insertObj (index + 1, triangles[1]); - - for (LDTriangle* t : triangles) - g_win->R()->compileObject (t); - - // Delete this quad now, it has been split. - delete obj; - - num++; - } - - log ("%1 quadrilaterals split", num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (EditRaw, KEY (F9)) -{ if (selection().size() != 1) - return; - - LDObject* obj = selection()[0]; - QDialog* dlg = new QDialog; - Ui::EditRawUI ui; - - ui.setupUi (dlg); - ui.code->setText (obj->raw()); - - if (obj->getType() == LDObject::Error) - ui.errorDescription->setText (static_cast<LDError*> (obj)->reason); - else - { ui.errorDescription->hide(); - ui.errorIcon->hide(); - } - - if (!dlg->exec()) - return; - - LDObject* oldobj = obj; - - // Reinterpret it from the text of the input field - obj = parseLine (ui.code->text()); - oldobj->replace (obj); - - // Refresh - g_win->R()->compileObject (obj); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (SetColor, KEY (C)) -{ if (selection().isEmpty()) - return; - - short colnum; - short defcol = -1; - - QList<LDObject*> objs = selection(); - - // If all selected objects have the same color, said color is our default - // value to the color selection dialog. - defcol = g_win->getSelectedColor(); - - // Show the dialog to the user now and ask for a color. - if (ColorSelector::getColor (colnum, defcol, g_win)) - { for (LDObject* obj : objs) - { if (obj->isColored() == false) - continue; - - obj->setColor (colnum); - g_win->R()->compileObject (obj); - } - - g_win->refresh(); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Borders, CTRL_SHIFT (B)) -{ QList<LDObject*> objs = selection(); - int num = 0; - - for (LDObject* obj : objs) - { const LDObject::Type type = obj->getType(); - if (type != LDObject::Quad && type != LDObject::Triangle) - continue; - - short numLines; - LDLine* lines[4]; - - if (type == LDObject::Quad) - { numLines = 4; - - LDQuad* quad = static_cast<LDQuad*> (obj); - lines[0] = new LDLine (quad->getVertex (0), quad->getVertex (1)); - lines[1] = new LDLine (quad->getVertex (1), quad->getVertex (2)); - lines[2] = new LDLine (quad->getVertex (2), quad->getVertex (3)); - lines[3] = new LDLine (quad->getVertex (3), quad->getVertex (0)); - } - else - { numLines = 3; - - LDTriangle* tri = static_cast<LDTriangle*> (obj); - lines[0] = new LDLine (tri->getVertex (0), tri->getVertex (1)); - lines[1] = new LDLine (tri->getVertex (1), tri->getVertex (2)); - lines[2] = new LDLine (tri->getVertex (2), tri->getVertex (0)); - } - - for (short i = 0; i < numLines; ++i) - { long idx = obj->getIndex() + i + 1; - - lines[i]->setColor (edgecolor); - LDFile::current()->insertObj (idx, lines[i]); - g_win->R()->compileObject (lines[i]); - } - - num += numLines; - } - - log (ForgeWindow::tr ("Added %1 border lines"), num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (CornerVerts, 0) -{ int num = 0; - - for (LDObject* obj : selection()) - { if (obj->vertices() < 2) - continue; - - int idx = obj->getIndex(); - - for (int i = 0; i < obj->vertices(); ++i) - { LDVertex* vert = new LDVertex; - vert->pos = obj->getVertex (i); - vert->setColor (obj->color()); - - LDFile::current()->insertObj (++idx, vert); - g_win->R()->compileObject (vert); - ++num; - } - } - - log (ForgeWindow::tr ("Added %1 vertices"), num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void doMoveSelection (const bool up) -{ QList<LDObject*> objs = selection(); - LDObject::moveObjects (objs, up); - g_win->buildObjList(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (MoveUp, KEY (PageUp)) -{ doMoveSelection (true); -} - -DEFINE_ACTION (MoveDown, KEY (PageDown)) -{ doMoveSelection (false); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Undo, CTRL (Z)) -{ LDFile::current()->undo(); -} - -DEFINE_ACTION (Redo, CTRL_SHIFT (Z)) -{ LDFile::current()->redo(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void doMoveObjects (vertex vect) -{ // Apply the grid values - vect[X] *= currentGrid().confs[Grid::X]->value; - vect[Y] *= currentGrid().confs[Grid::Y]->value; - vect[Z] *= currentGrid().confs[Grid::Z]->value; - - for (LDObject* obj : selection()) - { obj->move (vect); - g_win->R()->compileObject (obj); - } - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (MoveXNeg, KEY (Left)) -{ doMoveObjects ( { -1, 0, 0}); -} - -DEFINE_ACTION (MoveYNeg, KEY (Home)) -{ doMoveObjects ( {0, -1, 0}); -} - -DEFINE_ACTION (MoveZNeg, KEY (Down)) -{ doMoveObjects ( {0, 0, -1}); -} - -DEFINE_ACTION (MoveXPos, KEY (Right)) -{ doMoveObjects ( {1, 0, 0}); -} - -DEFINE_ACTION (MoveYPos, KEY (End)) -{ doMoveObjects ( {0, 1, 0}); -} - -DEFINE_ACTION (MoveZPos, KEY (Up)) -{ doMoveObjects ( {0, 0, 1}); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Invert, CTRL_SHIFT (W)) -{ QList<LDObject*> sel = selection(); - - for (LDObject* obj : sel) - { obj->invert(); - g_win->R()->compileObject (obj); - } - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void rotateVertex (vertex& v, const vertex& rotpoint, const matrix& transform) -{ v.move (-rotpoint); - v.transform (transform, g_origin); - v.move (rotpoint); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void doRotate (const short l, const short m, const short n) -{ QList<LDObject*> sel = selection(); - QList<vertex*> queue; - const vertex rotpoint = rotPoint (sel); - const double angle = (pi * currentGrid().confs[Grid::Angle]->value) / 180, - cosangle = cos (angle), - sinangle = sin (angle); - - // ref: http://en.wikipedia.org/wiki/Transformation_matrix#Rotation_2 - matrix transform ( - { (l* l * (1 - cosangle)) + cosangle, - (m* l * (1 - cosangle)) - (n* sinangle), - (n* l * (1 - cosangle)) + (m* sinangle), - - (l* m * (1 - cosangle)) + (n* sinangle), - (m* m * (1 - cosangle)) + cosangle, - (n* m * (1 - cosangle)) - (l* sinangle), - - (l* n * (1 - cosangle)) - (m* sinangle), - (m* n * (1 - cosangle)) + (l* sinangle), - (n* n * (1 - cosangle)) + cosangle - }); - - // Apply the above matrix to everything - for (LDObject* obj : sel) - { if (obj->vertices()) - { for (short i = 0; i < obj->vertices(); ++i) - { vertex v = obj->getVertex (i); - rotateVertex (v, rotpoint, transform); - obj->setVertex (i, v); - } - } elif (obj->hasMatrix()) - - { LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj); - - // Transform the position - vertex v = mo->position(); - rotateVertex (v, rotpoint, transform); - mo->setPosition (v); - - // Transform the matrix - mo->setTransform (mo->transform() * transform); - } elif (obj->getType() == LDObject::Vertex) - { LDVertex* vert = static_cast<LDVertex*> (obj); - vertex v = vert->pos; - rotateVertex (v, rotpoint, transform); - vert->pos = v; - } - - g_win->R()->compileObject (obj); - } - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (RotateXPos, CTRL (Right)) -{ doRotate (1, 0, 0); -} -DEFINE_ACTION (RotateYPos, CTRL (End)) -{ doRotate (0, 1, 0); -} -DEFINE_ACTION (RotateZPos, CTRL (Up)) -{ doRotate (0, 0, 1); -} -DEFINE_ACTION (RotateXNeg, CTRL (Left)) -{ doRotate (-1, 0, 0); -} -DEFINE_ACTION (RotateYNeg, CTRL (Home)) -{ doRotate (0, -1, 0); -} -DEFINE_ACTION (RotateZNeg, CTRL (Down)) -{ doRotate (0, 0, -1); -} - -DEFINE_ACTION (RotationPoint, (0)) -{ configRotationPoint(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (RoundCoordinates, 0) -{ setlocale (LC_ALL, "C"); - int num = 0; - - for (LDObject* obj : selection()) - { for (short i = 0; i < obj->vertices(); ++i) - { vertex v = obj->getVertex (i); - - for (const Axis ax : g_Axes) - { // HACK: .. should find a better way to do this - str valstr; - valstr.sprintf ("%.3f", v[ax]); - v[ax] = valstr.toDouble(); - } - - obj->setVertex (i, v); - g_win->R()->compileObject (obj); - num += 3; - } - } - - log (ForgeWindow::tr ("Rounded %1 coordinates"), num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Uncolorize, 0) -{ int num = 0; - - for (LDObject* obj : selection()) - { if (obj->isColored() == false) - continue; - - int col = maincolor; - - if (obj->getType() == LDObject::Line || obj->getType() == LDObject::CndLine) - col = edgecolor; - - obj->setColor (col); - g_win->R()->compileObject (obj); - num++; - } - - log (ForgeWindow::tr ("%1 objects uncolored"), num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (ReplaceCoords, CTRL (R)) -{ QDialog* dlg = new QDialog (g_win); - Ui::ReplaceCoordsUI ui; - ui.setupUi (dlg); - - if (!dlg->exec()) - return; - - const double search = ui.search->value(), - replacement = ui.replacement->value(); - const bool any = ui.any->isChecked(), - rel = ui.relative->isChecked(); - - QList<Axis> sel; - int num = 0; - - if (ui.x->isChecked()) sel << X; - if (ui.y->isChecked()) sel << Y; - if (ui.z->isChecked()) sel << Z; - - for (LDObject* obj : selection()) - { for (short i = 0; i < obj->vertices(); ++i) - { vertex v = obj->getVertex (i); - - for (Axis ax : sel) - { double& coord = v[ax]; - - if (any || coord == search) - { if (!rel) - coord = 0; - - coord += replacement; - num++; - } - } - - obj->setVertex (i, v); - g_win->R()->compileObject (obj); - } - } - - log (ForgeWindow::tr ("Altered %1 values"), num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Flip, CTRL_SHIFT (F)) -{ QDialog* dlg = new QDialog; - Ui::FlipUI ui; - ui.setupUi (dlg); - - if (!dlg->exec()) - return; - - QList<Axis> sel; - - if (ui.x->isChecked()) sel << X; - if (ui.y->isChecked()) sel << Y; - if (ui.z->isChecked()) sel << Z; - - for (LDObject* obj : selection()) - { for (short i = 0; i < obj->vertices(); ++i) - { vertex v = obj->getVertex (i); - - for (Axis ax : sel) - v[ax] *= -1; - - obj->setVertex (i, v); - g_win->R()->compileObject (obj); - } - } - - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Demote, 0) -{ QList<LDObject*> sel = selection(); - int num = 0; - - for (LDObject* obj : sel) - { if (obj->getType() != LDObject::CndLine) - continue; - - LDLine* repl = static_cast<LDCndLine*> (obj)->demote(); - g_win->R()->compileObject (repl); - ++num; - } - - log (ForgeWindow::tr ("Demoted %1 conditional lines"), num); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static bool isColorUsed (short colnum) -{ for (LDObject* obj : LDFile::current()->objects()) - if (obj->isColored() && obj->color() == colnum) - return true; - - return false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (Autocolor, 0) -{ short colnum = 0; - - while (colnum < MAX_COLORS && (getColor (colnum) == null || isColorUsed (colnum))) - colnum++; - - if (colnum >= MAX_COLORS) - { log (ForgeWindow::tr ("Cannot auto-color: all colors are in use!")); - return; - } - - for (LDObject* obj : selection()) - { if (obj->isColored() == false) - continue; - - obj->setColor (colnum); - g_win->R()->compileObject (obj); - } - - log (ForgeWindow::tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name); - g_win->refresh(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (AddHistoryLine, 0) -{ LDObject* obj; - bool ishistory = false, - prevIsHistory = false; - - QDialog* dlg = new QDialog; - Ui_AddHistoryLine* ui = new Ui_AddHistoryLine; - ui->setupUi (dlg); - ui->m_username->setText (ld_defaultuser); - ui->m_date->setDate (QDate::currentDate()); - ui->m_comment->setFocus(); - - if (!dlg->exec()) - return; - - // Create the comment object based on input - str commentText = fmt ("!HISTORY %1 [%2] %3", - ui->m_date->date().toString ("yyyy-MM-dd"), - ui->m_username->text(), - ui->m_comment->text()); - - LDComment* comm = new LDComment (commentText); - - // Find a spot to place the new comment - for ( - obj = LDFile::current()->object (0); - obj && obj->next() && !obj->next()->isScemantic(); - obj = obj->next() - ) - { LDComment* comm = dynamic_cast<LDComment*> (obj); - - if (comm && comm->text.startsWith ("!HISTORY ")) - ishistory = true; - - if (prevIsHistory && !ishistory) - { // Last line was history, this isn't, thus insert the new history - // line here. - break; - } - - prevIsHistory = ishistory; - } - - int idx = obj ? obj->getIndex() : 0; - LDFile::current()->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()) - LDFile::current()->insertObj (idx, new LDEmpty); - - g_win->buildObjList(); - delete ui; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/history.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,202 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "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
--- a/src/history.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,213 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "history.h" -#include "ldtypes.h" -#include "file.h" -#include "misc.h" -#include "gui.h" -#include "gldraw.h" - -bool g_fullRefresh = false; - -// ============================================================================= -// ----------------------------------------------------------------------------- -History::History() : - m_pos (-1), - m_opened (false) {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void History::undo() -{ if (m_changesets.isEmpty() || pos() == -1) - return; - - const Changeset& set = changeset (pos()); - g_fullRefresh = false; - - // Iterate the list in reverse and undo all actions - for (auto it = set.end() - 1; it != set.begin(); --it) - (*it)->undo(); - - setPos (pos() - 1); - - if (!g_fullRefresh) - g_win->refresh(); - else - g_win->fullRefresh(); - - updateActions(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void History::redo() -{ if (pos() == (long) m_changesets.size()) - return; - - const Changeset& set = changeset (pos() + 1); - g_fullRefresh = false; - - // Redo things - in the order as they were done in the first place - for (const AbstractHistoryEntry* change : set) - change->redo(); - - setPos (pos() + 1); - - if (!g_fullRefresh) - g_win->refresh(); - else - g_win->fullRefresh(); - - updateActions(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void History::clear() -{ for (Changeset set : m_changesets) - for (auto change : set) - delete change; - - m_changesets.clear(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void History::updateActions() const -{ ACTION (Undo)->setEnabled (pos() != -1); - ACTION (Redo)->setEnabled (pos() < (long) m_changesets.size() - 1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void History::open() -{ if (opened()) - return; - - setOpened (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void History::close() -{ if (!opened()) - return; - - setOpened (false); - - if (m_currentArchive.isEmpty()) - return; - - while (pos() < size() - 1) - m_changesets.removeLast(); - - m_changesets << m_currentArchive; - m_currentArchive.clear(); - setPos (pos() + 1); - updateActions(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void History::add (AbstractHistoryEntry* entry) -{ if (!opened()) - { delete entry; - return; - } - - entry->setParent (this); - m_currentArchive << entry; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddHistory::undo() const -{ LDFile* f = parent()->file(); - LDObject* obj = f->object (index()); - f->forgetObject (obj); - delete obj; - - g_fullRefresh = true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void AddHistory::redo() const -{ LDFile* f = parent()->file(); - LDObject* obj = parseLine (code()); - f->insertObj (index(), obj); - g_win->R()->compileObject (obj); -} - -AddHistory::~AddHistory() {} - -// ============================================================================= -// heh -// ----------------------------------------------------------------------------- -void DelHistory::undo() const -{ LDFile* f = parent()->file(); - LDObject* obj = parseLine (code()); - f->insertObj (index(), obj); - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void DelHistory::redo() const -{ LDFile* f = parent()->file(); - LDObject* obj = f->object (index()); - f->forgetObject (obj); - delete obj; - - g_fullRefresh = true; -} - -DelHistory::~DelHistory() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void EditHistory::undo() const -{ LDObject* obj = LDFile::current()->object (index()); - LDObject* newobj = parseLine (oldCode()); - obj->replace (newobj); - g_win->R()->compileObject (newobj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void EditHistory::redo() const -{ LDObject* obj = LDFile::current()->object (index()); - LDObject* newobj = parseLine (newCode()); - obj->replace (newobj); - g_win->R()->compileObject (newobj); -} - -EditHistory::~EditHistory() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void SwapHistory::undo() const -{ LDObject::fromID (a)->swap (LDObject::fromID (b)); -} - -void SwapHistory::redo() const -{ undo(); // :v -} - -SwapHistory::~SwapHistory() {}
--- a/src/history.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/history.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -16,62 +16,74 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef HISTORY_H -#define HISTORY_H +#ifndef LDFORGE_HISTORY_H +#define LDFORGE_HISTORY_H -#include "common.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::Type getType() const override { return History::N; } +#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 (long, pos, setPos) - PROPERTY (LDFile*, file, setFile) - READ_PROPERTY (bool, opened, setOpened) +{ + 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<AbstractHistoryEntry*> Changeset; - enum Type - { Del, - Edit, - Add, - Move, - Swap, + enum EHistoryType + { + EDelHistory, + EEditHistory, + EAddHistory, + EMoveHistory, + ESwapHistory, }; History(); void undo(); void redo(); void clear(); - void updateActions() const; - void open(); - void close(); + void addStep(); void add (AbstractHistoryEntry* entry); - inline long size() const - { return m_changesets.size(); + inline long getSize() const + { + return m_changesets.size(); } inline History& operator<< (AbstractHistoryEntry* entry) - { add (entry); + { + add (entry); return *this; } - inline const Changeset& changeset (long pos) const - { return m_changesets[pos]; + inline const Changeset& getChangeset (long pos) const + { + return m_changesets[pos]; } private: - Changeset m_currentArchive; + Changeset m_currentChangeset; QList<Changeset> m_changesets; }; @@ -79,103 +91,94 @@ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= class AbstractHistoryEntry -{ PROPERTY (History*, parent, setParent) +{ + PROPERTY (public, History*, Parent, NO_OPS, STOCK_WRITE) public: - virtual void undo() const {} - virtual void redo() const {} virtual ~AbstractHistoryEntry() {} - virtual History::Type getType() const - { return (History::Type) 0; - } + 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 -{ public: - enum Type - { Cut, // was deleted with a cut operation - Other, // was deleted witout specific reason - }; - - PROPERTY (int, index, setIndex) - PROPERTY (str, code, setCode) - PROPERTY (DelHistory::Type, type, setType) +{ + 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, Type type = Other) : - m_index (idx), - m_code (obj->raw()), - m_type (type) {} + DelHistory (int idx, LDObject* obj); }; // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= class EditHistory : public AbstractHistoryEntry -{ PROPERTY (int, index, setIndex) - PROPERTY (str, oldCode, setOldCode) - PROPERTY (str, newCode, setNewCode) +{ + 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, str oldCode, str newCode) : - m_index (idx), - m_oldCode (oldCode), - m_newCode (newCode) {} + EditHistory (int idx, QString oldCode, QString newCode) : + m_Index (idx), + m_OldCode (oldCode), + m_NewCode (newCode) {} }; // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= class AddHistory : public AbstractHistoryEntry -{ public: - enum Type - { Other, // was "just added" - Paste, // was added through a paste operation - }; - - PROPERTY (int, index, setIndex) - PROPERTY (str, code, setCode) - PROPERTY (AddHistory::Type, type, setType) +{ + 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, Type type = Other) : - m_index (idx), - m_code (obj->raw()), - m_type (type) {} + AddHistory (int idx, LDObject* obj) : + m_Index (idx), + m_Code (obj->raw()) {} }; // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= class MoveHistory : public AbstractHistoryEntry -{ public: +{ + public: IMPLEMENT_HISTORY_TYPE (Move) QList<int> indices; - vertex dest; + Vertex dest; - MoveHistory (QList<int> indices, vertex dest) : + MoveHistory (QList<int> indices, Vertex dest) : indices (indices), dest (dest) {} }; +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= class SwapHistory : public AbstractHistoryEntry -{ public: +{ + public: IMPLEMENT_HISTORY_TYPE (Swap) - int a, b; SwapHistory (int a, int b) : a (a), b (b) {} + + private: + int a, b; }; -#endif // HISTORY_H +#endif // LDFORGE_HISTORY_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldconfig.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#include <QFile> +#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); +}
--- a/src/ldconfig.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "file.h" -#include "ldconfig.h" -#include "gui.h" -#include "misc.h" -#include "colors.h" - -// ============================================================================= -// Helper function for parseLDConfig -// ----------------------------------------------------------------------------- -static bool parseLDConfigTag (LDConfigParser& pars, char const* tag, str& val) -{ short pos; - - // Try find the token and get its position - if (!pars.findToken (pos, tag, 1)) - return false; - - // Get the token after it and store it into val - return pars.getToken (val, pos + 1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void parseLDConfig() -{ File* f = openLDrawFile ("LDConfig.ldr", false); - - if (!f) - { critical (fmt (QObject::tr ("Unable to open LDConfig.ldr for parsing! (%1)"), - strerror (errno))); - return; - } - - // Read in the lines - for (str line : *f) - { if (line.length() == 0 || line[0] != '0') - continue; // empty or illogical - - line.remove ('\r'); - line.remove ('\n'); - - // Parse the line - LDConfigParser pars (line, ' '); - - short code = 0, alpha = 255; - str name, facename, edgename, valuestr; - - // Check 0 !COLOUR, parse the name - if (!pars.tokenCompare (0, "0") || !pars.tokenCompare (1, "!COLOUR") || !pars.getToken (name, 2)) - continue; - - // Replace underscores in the name with spaces for readability - name.replace ("_", " "); - - // Get the CODE tag - if (!parseLDConfigTag (pars, "CODE", valuestr)) - continue; - - if (!numeric (valuestr)) - continue; // not a number - - // Ensure that the code is within [0 - 511] - bool ok; - code = valuestr.toShort (&ok); - - if (!ok || code < 0 || code >= 512) - continue; - - // VALUE and EDGE tags - if (!parseLDConfigTag (pars, "VALUE", facename) || !parseLDConfigTag (pars, "EDGE", edgename)) - continue; - - // Ensure that our colors are correct - QColor faceColor (facename), - edgeColor (edgename); - - if (!faceColor.isValid() || !edgeColor.isValid()) - continue; - - // Parse alpha if given. - if (parseLDConfigTag (pars, "ALPHA", valuestr)) - alpha = clamp<short> (valuestr.toShort(), 0, 255); - - LDColor* col = new LDColor; - col->name = name; - col->faceColor = faceColor; - col->edgeColor = edgeColor; - col->hexcode = facename; - col->faceColor.setAlpha (alpha); - col->index = code; - setColor (code, col); - } - - delete f; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDConfigParser::LDConfigParser (str inText, char sep) -{ m_tokens = inText.split (sep, QString::SkipEmptyParts); - m_pos = -1; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::atBeginning() -{ return (m_pos == -1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::atEnd() -{ return (m_pos == m_tokens.size() - 1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::getToken (str& val, const int pos) -{ if (pos >= m_tokens.size()) - return false; - - val = m_tokens[pos]; - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::next (str& val) -{ return getToken (val, ++m_pos); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::peekNext (str& val) -{ return getToken (val, m_pos + 1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::findToken (short& result, char const* needle, short 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 (short amount, bool rel) -{ m_pos = (rel ? m_pos : 0) + amount; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -size_t LDConfigParser::size() -{ return m_tokens.size(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool LDConfigParser::tokenCompare (short inPos, const char* sOther) -{ str tok; - - if (!getToken (tok, inPos)) - return false; - - return (tok == sOther); -}
--- a/src/ldconfig.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/ldconfig.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -16,34 +16,36 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef LDCONFIG_H -#define LDCONFIG_H +#ifndef LDFORGE_LDCONFIG_H +#define LDFORGE_LDCONFIG_H #include "types.h" #include <QStringList> // ============================================================================= -// StringParser +// LDConfigParser // -// String parsing utility +// String parsing utility for parsing ldconfig.ldr // ============================================================================= class LDConfigParser -{ public: - LDConfigParser (str inText, char sep); +{ + public: + LDConfigParser (QString inText, char sep); - bool atEnd(); - bool atBeginning(); - bool next (str& val); - bool peekNext (str& val); - bool getToken (str& val, const int pos); - bool findToken (short& result, char const* needle, short args); - size_t size(); + 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 (short amount, bool rel); - bool tokenCompare (short inPos, const char* sOther); + void seek (int amount, bool rel); + bool tokenCompare (int inPos, const char* sOther); - str operator[] (const size_t idx) - { return m_tokens[idx]; + inline QString operator[] (const int idx) + { + return m_tokens[idx]; } private: @@ -53,4 +55,4 @@ void parseLDConfig(); -#endif // LDCONFIG_H +#endif // LDFORGE_LDCONFIG_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldtypes.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,836 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "main.h" +#include "ldtypes.h" +#include "document.h" +#include "misc.h" +#include "gui.h" +#include "history.h" +#include "gldraw.h" +#include "colors.h" +#include "gldata.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<LDTriangle*> LDQuad::splitToTriangles() +{ + // Create the two triangles based on this quadrilateral: + // 0---3 0---3 3 + // | | | / /| + // | | ==> | / / | + // | | |/ / | + // 1---2 1 1---2 + LDTriangle* tri1 = new LDTriangle (getVertex (0), getVertex (1), getVertex (3)); + LDTriangle* tri2 = new LDTriangle (getVertex (1), getVertex (2), getVertex (3)); + + // The triangles also inherit the quad's color + tri1->setColor (getColor()); + tri2->setColor (getColor()); + + QList<LDTriangle*> triangles; + triangles << tri1; + triangles << tri2; + return triangles; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::replace (LDObject* other) +{ + long idx = getIndex(); + assert (idx != -1); + + // Replace the instance of the old object with the new object + getFile()->setObject (idx, other); + + // Remove the old object + 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<LDSubfile*> (obj); + Matrix newMatrix = transform * ref->getTransform(); + Vertex newpos = ref->getPosition(); + + newpos.transform (transform, pos); + ref->setPosition (newpos); + ref->setTransform (newMatrix); + } + break; + + default: + break; + } + + if (obj->getColor() == maincolor) + obj->setColor (parentcolor); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +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<LDMatrixObject*> (this); + mo->setPosition (mo->getPosition() + vect); + } + elif (getType() == LDObject::EVertex) + { + // ugh + static_cast<LDVertex*> (this)->pos += vect; + } + else + { + for (int i = 0; i < vertices(); ++i) + setVertex (i, getVertex (i) + vect); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +#define CHECK_FOR_OBJ(N) \ + if (type == LDObject::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<LDBFC*> (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<class T> static void changeProperty (LDObject* obj, T* ptr, const T& val) +{ + long idx; + + if (*ptr == val) + return; + + if (obj->getFile() && (idx = obj->getIndex()) != -1) + { + 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<Vertex, LDSharedVertex*> g_sharedVerts; + +LDSharedVertex* LDSharedVertex::getSharedVertex (const Vertex& a) +{ + auto it = g_sharedVerts.find (a); + + if (it == g_sharedVerts.end()) + { + LDSharedVertex* v = new LDSharedVertex (a); + g_sharedVerts[a] = v; + return v; + } + + return *it; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDSharedVertex::addRef (LDObject* a) +{ + m_refs << a; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDSharedVertex::delRef (LDObject* a) +{ + m_refs.removeOne (a); + + if (m_refs.empty()) + { + g_sharedVerts.remove (m_data); + delete this; + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::select() +{ + if (!getFile()) + { + log ("Warning: Object #%1 cannot be selected as it is not assigned a file!\n", getID()); + return; + } + + getFile()->addToSelection (this); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void LDObject::unselect() +{ + if (!getFile()) + { + log ("Warning: Object #%1 cannot be unselected as it is not assigned a file!\n", getID()); + return; + } + + getFile()->removeFromSelection (this); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +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<LDMatrixObject*> (copy); + const LDMatrixObject* mo = static_cast<const LDMatrixObject*> (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<LDSubfile*> (copy); + const LDSubfile* ref = static_cast<const LDSubfile*> (this); + + copyRef->setFileInfo (ref->getFileInfo()); + } + } + */ + + LDObject* copy = parseLine (raw()); + return copy; +} \ No newline at end of file
--- a/src/ldtypes.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,778 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "common.h" -#include "ldtypes.h" -#include "file.h" -#include "misc.h" -#include "gui.h" -#include "history.h" -#include "gldraw.h" -#include "colors.h" -#include "gldata.h" - -cfg (String, ld_defaultname, ""); -cfg (String, ld_defaultuser, ""); -cfg (Int, ld_defaultlicense, 0); - -// List of all LDObjects -QList<LDObject*> g_LDObjects; - -// ============================================================================= -// LDObject constructors -// ----------------------------------------------------------------------------- -LDObject::LDObject() : - m_hidden (false), - m_selected (false), - m_parent (null), - m_file (null), - qObjListEntry (null), - m_glinit (false) -{ - memset (m_coords, 0, sizeof m_coords); - - // Determine ID - int32 id = 1; // 0 is invalid - - for (LDObject* obj : g_LDObjects) - if (obj->id() >= id) - id = obj->id() + 1; - - setID (id); - g_LDObjects << this; -} - -// ============================================================================= -// Default implementations for LDObject's virtual methods. These should never be -// actually called, for a subclass-less LDObject should never come into existance. -// These exist only to satisfy the linker. -// ----------------------------------------------------------------------------- -LDObject::Type LDObject::getType() const -{ return LDObject::Unidentified; -} - -bool LDObject::hasMatrix() const -{ return false; -} - -bool LDObject::isColored() const -{ return false; -} - -bool LDObject::isScemantic() const -{ return false; -} - -str LDObject::typeName() const -{ return ""; -} - -short LDObject::vertices() const -{ return 0; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::setVertexCoord (int i, Axis ax, double value) -{ vertex v = getVertex (i); - v[ax] = value; - setVertex (i, v); -} - -LDError::LDError() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDComment::raw() -{ return fmt ("0 %1", text); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDSubfile::raw() -{ str val = fmt ("1 %1 %2 ", color(), position()); - val += transform().stringRep(); - val += ' '; - val += fileInfo()->name(); - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDLine::raw() -{ str val = fmt ("2 %1", color()); - - for (int i = 0; i < 2; ++i) - val += fmt (" %1", getVertex (i)); - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDTriangle::raw() -{ str val = fmt ("3 %1", color()); - - for (int i = 0; i < 3; ++i) - val += fmt (" %1", getVertex (i)); - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDQuad::raw() -{ str val = fmt ("4 %1", color()); - - for (int i = 0; i < 4; ++i) - val += fmt (" %1", getVertex (i)); - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDCndLine::raw() -{ str val = fmt ("5 %1", color()); - - // Add the coordinates - for (int i = 0; i < 4; ++i) - val += fmt (" %1", getVertex (i)); - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDError::raw() -{ return contents; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDVertex::raw() -{ return fmt ("0 !LDFORGE VERTEX %1 %2", color(), pos); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDEmpty::raw() -{ return ""; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const char* LDBFC::statements[] = -{ "CERTIFY CCW", - "CCW", - "CERTIFY CW", - "CW", - "NOCERTIFY", - "INVERTNEXT", - "CLIP", - "CLIP CCW", - "CLIP CW", - "NOCLIP", -}; - -str LDBFC::raw() -{ return fmt ("0 BFC %1", LDBFC::statements[type]); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDTriangle*> LDQuad::splitToTriangles() -{ // Create the two triangles based on this quadrilateral: - // 0---3 0---3 3 - // | | | / /| - // | | ==> | / / | - // | | |/ / | - // 1---2 1 1---2 - LDTriangle* tri1 = new LDTriangle (getVertex (0), getVertex (1), getVertex (3)); - LDTriangle* tri2 = new LDTriangle (getVertex (1), getVertex (2), getVertex (3)); - - // The triangles also inherit the quad's color - tri1->setColor (color()); - tri2->setColor (color()); - - QList<LDTriangle*> triangles; - triangles << tri1; - triangles << tri2; - return triangles; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::replace (LDObject* other) -{ long idx = getIndex(); - assert (idx != -1); - - // Replace the instance of the old object with the new object - file()->setObject (idx, other); - - // Remove the old object - delete this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::swap (LDObject* other) -{ int i = 0; - - for (LDObject* obj : file()->objects()) - { if (obj == this) - file()->setObject (i, other); - elif (obj == other) - file()->setObject (i, this); - - ++i; - } - - file()->addToHistory (new SwapHistory (id(), other->id())); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDLine::LDLine (vertex v1, vertex v2) -{ setVertex (0, v1); - setVertex (1, v2); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDQuad::LDQuad (const vertex& v0, const vertex& v1, const vertex& v2, const vertex& v3) -{ setVertex (0, v0); - setVertex (1, v1); - setVertex (2, v2); - setVertex (3, v3); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject::~LDObject() -{ // If this object was selected, unselect it now - if (selected()) - unselect(); - - // If this object was associated to a file, remove it off it now - if (file()) - file()->forgetObject (this); - - // Delete the GL lists - GL::deleteLists (this); - - // Remove this object from the list of LDObjects - g_LDObjects.removeOne (this); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void transformObject (LDObject* obj, matrix transform, vertex pos, short parentcolor) -{ switch (obj->getType()) - { case LDObject::Line: - case LDObject::CndLine: - case LDObject::Triangle: - case LDObject::Quad: - - for (short i = 0; i < obj->vertices(); ++i) - { vertex v = obj->getVertex (i); - v.transform (transform, pos); - obj->setVertex (i, v); - } - - break; - - case LDObject::Subfile: - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - matrix newMatrix = transform * ref->transform(); - vertex newpos = ref->position(); - - newpos.transform (transform, pos); - ref->setPosition (newpos); - ref->setTransform (newMatrix); - } - break; - - default: - break; - } - - if (obj->color() == maincolor) - obj->setColor (parentcolor); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDObject*> LDSubfile::inlineContents (InlineFlags flags) -{ QList<LDObject*> objs = fileInfo()->inlineContents (flags); - - // Transform the objects -for (LDObject * obj : objs) - { // Set the parent now so we know what inlined this. - obj->setParent (this); - transformObject (obj, transform(), position(), color()); - } - - return objs; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -long LDObject::getIndex() const -{ -#ifndef RELEASE - assert (file() != null); -#endif - - for (int i = 0; i < file()->numObjs(); ++i) - if (file()->obj (i) == this) - return i; - - return -1; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::moveObjects (QList<LDObject*> objs, const bool up) -{ if (objs.isEmpty()) - return; - - // If we move down, we need to iterate the array in reverse order. - const long start = up ? 0 : (objs.size() - 1); - const long end = up ? objs.size() : -1; - const long incr = up ? 1 : -1; - QList<LDObject*> objsToCompile; - LDFile* file = objs[0]->file(); - - 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->objects().size() - 1))) - { // One of the objects hit the extrema. If this happens, this should be the first - // object to be iterated on. Thus, nothing has changed yet and it's safe to just - // abort the entire operation. - assert (i == start); - return; - } - - objsToCompile << obj; - objsToCompile << file->obj (target); - - obj->swap (file->obj (target)); - } - - removeDuplicates (objsToCompile); - - // The objects need to be recompiled, otherwise their pick lists are left with - // the wrong index colors which messes up selection. - for (LDObject* obj : objsToCompile) - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDObject::typeName (LDObject::Type type) -{ LDObject* obj = LDObject::getDefault (type); - str name = obj->typeName(); - delete obj; - return name; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDObject::objectListContents (const QList<LDObject*>& objs) -{ bool firstDetails = true; - str text = ""; - - if (objs.isEmpty()) - return "nothing"; // :) - - for (long i = 0; i < LDObject::NumTypes; ++i) - { LDObject::Type objType = (LDObject::Type) i; - int count = 0; - - for (LDObject * obj : objs) - if (obj->getType() == objType) - count++; - - if (count == 0) - continue; - - if (!firstDetails) - text += ", "; - - str noun = fmt ("%1%2", typeName (objType), plural (count)); - - // Plural of "vertex" is "vertices", correct that - - if (objType == LDObject::Vertex && count != 1) - noun = "vertices"; - - text += fmt ("%1 %2", count, noun); - firstDetails = false; - } - - return text; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDObject::topLevelParent() -{ if (!parent()) - return this; - - LDObject* it = this; - - while (it->parent()) - it = it->parent(); - - return it; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDObject::next() const -{ long idx = getIndex(); - assert (idx != -1); - - if (idx == (long) file()->numObjs() - 1) - return null; - - return file()->obj (idx + 1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDObject::prev() const -{ long idx = getIndex(); - assert (idx != -1); - - if (idx == 0) - return null; - - return file()->obj (idx - 1); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::move (vertex vect) -{ (void) vect; -} -void LDEmpty::move (vertex vect) -{ (void) vect; -} -void LDBFC::move (vertex vect) -{ (void) vect; -} -void LDComment::move (vertex vect) -{ (void) vect; -} -void LDError::move (vertex vect) -{ (void) vect; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDVertex::move (vertex vect) -{ pos += vect; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDSubfile::move (vertex vect) -{ setPosition (position() + vect); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDLine::move (vertex vect) -{ for (short i = 0; i < 2; ++i) - setVertex (i, getVertex (i) + vect); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDTriangle::move (vertex vect) -{ for (short i = 0; i < 3; ++i) - setVertex (i, getVertex (i) + vect); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDQuad::move (vertex vect) -{ for (short i = 0; i < 4; ++i) - setVertex (i, getVertex (i) + vect); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDCndLine::move (vertex vect) -{ for (short i = 0; i < 4; ++i) - setVertex (i, getVertex (i) + vect); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -#define CHECK_FOR_OBJ(N) \ - if (type == LDObject::N) \ - return new LD##N; - -LDObject* LDObject::getDefault (const LDObject::Type type) -{ CHECK_FOR_OBJ (Comment) - CHECK_FOR_OBJ (BFC) - CHECK_FOR_OBJ (Line) - CHECK_FOR_OBJ (CndLine) - CHECK_FOR_OBJ (Subfile) - CHECK_FOR_OBJ (Triangle) - CHECK_FOR_OBJ (Quad) - CHECK_FOR_OBJ (Empty) - CHECK_FOR_OBJ (BFC) - CHECK_FOR_OBJ (Error) - CHECK_FOR_OBJ (Vertex) - CHECK_FOR_OBJ (Overlay) - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::invert() {} -void LDBFC::invert() {} -void LDEmpty::invert() {} -void LDComment::invert() {} -void LDError::invert() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDTriangle::invert() -{ // Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1. - // Thus, we swap 1 and 2. - vertex tmp = getVertex (1); - setVertex (1, getVertex (2)); - setVertex (2, tmp); - - return; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDQuad::invert() -{ // Quad: 0 -> 1 -> 2 -> 3 - // rev: 0 -> 3 -> 2 -> 1 - // Thus, we swap 1 and 3. - vertex tmp = getVertex (1); - setVertex (1, getVertex (3)); - setVertex (3, tmp); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDSubfile::invert() -{ // Subfiles are inverted when they're prefixed with - // a BFC INVERTNEXT statement. Thus we need to toggle this status. - // For flat primitives it's sufficient that the determinant is - // flipped but I don't have a method for checking flatness yet. - // Food for thought... - - int idx = getIndex(); - - if (idx > 0) - { LDBFC* bfc = dynamic_cast<LDBFC*> (prev()); - - if (bfc && bfc->type == LDBFC::InvertNext) - { // This is prefixed with an invertnext, thus remove it. - file()->forgetObject (bfc); - delete bfc; - return; - } - } - - // Not inverted, thus prefix it with a new invertnext. - LDBFC* bfc = new LDBFC (LDBFC::InvertNext); - file()->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 LDCndLine::invert() -{ invertLine (this); -} - -void LDVertex::invert() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDLine* LDCndLine::demote() -{ LDLine* repl = new LDLine; - - for (int i = 0; i < repl->vertices(); ++i) - repl->setVertex (i, getVertex (i)); - - repl->setColor (color()); - - replace (repl); - return repl; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObject* LDObject::fromID (int id) -{ for (LDObject * obj : g_LDObjects) - if (obj->id() == id) - return obj; - - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str LDOverlay::raw() -{ return fmt ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6", - filename(), camera(), x(), y(), width(), height()); -} - -void LDOverlay::move (vertex vect) -{ Q_UNUSED (vect) -} - -void LDOverlay::invert() {} - -// ============================================================================= -// Hook the set accessors of certain properties to this changeProperty function. -// It takes care of history management so we can capture low-level changes, this -// makes history stuff work out of the box. -// ----------------------------------------------------------------------------- -template<class T> static void changeProperty (LDObject* obj, T* ptr, const T& val) -{ long idx; - - if (*ptr == val) - return; - - if (obj->file() && (idx = obj->getIndex()) != -1) - { str before = obj->raw(); - *ptr = val; - str after = obj->raw(); - - obj->file()->addToHistory (new EditHistory (idx, before, after)); - } - else - *ptr = val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -READ_ACCESSOR (short, LDObject::color) -{ return m_color; -} - -SET_ACCESSOR (short, LDObject::setColor) -{ 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 (linkPointer(), &m_position, LDSharedVertex::getSharedVertex (a)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -READ_ACCESSOR (matrix, LDMatrixObject::transform) -{ return m_transform; -} - -SET_ACCESSOR (matrix, LDMatrixObject::setTransform) -{ changeProperty (linkPointer(), &m_transform, val); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static QMap<vertex, LDSharedVertex*> g_sharedVerts; - -LDSharedVertex* LDSharedVertex::getSharedVertex (const vertex& a) -{ auto it = g_sharedVerts.find (a); - - if (it == g_sharedVerts.end()) - { LDSharedVertex* v = new LDSharedVertex (a); - g_sharedVerts[a] = v; - return v; - } - - return *it; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDSharedVertex::addRef (LDObject* a) -{ m_refs << a; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDSharedVertex::delRef (LDObject* a) -{ m_refs.removeOne (a); - - if (m_refs.empty()) - { g_sharedVerts.remove (m_data); - delete this; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDObject::select() -{ if (!file()) - { log ("Warning: Object #%1 cannot be selected as it is not assigned a file!\n", id()); - return; - } - - file()->addToSelection (this); -} - -void LDObject::unselect() -{ if (!file()) - { log ("Warning: Object #%1 cannot be unselected as it is not assigned a file!\n", id()); - return; - } - - file()->removeFromSelection (this); -} \ No newline at end of file
--- a/src/ldtypes.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/ldtypes.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -16,27 +16,30 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef LDTYPES_H -#define LDTYPES_H +#ifndef LDFORGE_LDTYPES_H +#define LDFORGE_LDTYPES_H -#include "common.h" +#include "main.h" #include "types.h" +#include "misc/documentPointer.h" -#define LDOBJ(T) \ -public: \ - virtual ~LD##T() {} \ - virtual LDObject::Type getType() const override { \ - return LDObject::T; \ - } \ - virtual str raw(); \ - virtual LD##T* clone() { \ - return new LD##T (*this); \ - } \ - virtual void move (vertex where); \ - virtual void invert(); +#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 str typeName() const override { return #N; } -#define LDOBJ_VERTICES(V) virtual short vertices() const override { return V; } +#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) @@ -51,7 +54,7 @@ class QListWidgetItem; class LDSubfile; -class LDFile; +class LDDocument; class LDSharedVertex; // ============================================================================= @@ -63,62 +66,113 @@ // sub-classes based on this enumerator. // ============================================================================= class LDObject -{ PROPERTY (bool, hidden, setHidden) - PROPERTY (bool, selected, setSelected) - PROPERTY (LDObject*, parent, setParent) - PROPERTY (LDFile*, file, setFile) - READ_PROPERTY (int32, id, setID) - DECLARE_PROPERTY (short, color, setColor) +{ + 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. Codes are sorted in order of significance. + // Object type codes. enum Type - { Subfile, // Object represents a sub-file reference - Quad, // Object represents a quadrilateral - Triangle, // Object represents a triangle - Line, // Object represents a line - CndLine, // Object represents a conditional line - Vertex, // Object is a vertex, LDForge extension object - BFC, // Object represents a BFC statement - Overlay, // Object contains meta-info about an overlay image. - Comment, // Object represents a comment - Error, // Object is the result of failed parsing - Empty, // Object represents an empty line - Unidentified, // Object is an uninitialized (SHOULD NEVER HAPPEN) - NumTypes // Amount of object types + { + 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(); - virtual ~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; - virtual LDObject* clone() - { return 0; // Creates a new LDObject identical to this one. - } - long getIndex() const; // Index (i.e. line number) of this object - virtual LDObject::Type getType() const; // Type enumerator of this object - const vertex& getVertex (int i) const; // Get a vertex by index - virtual bool hasMatrix() const; // Does this object have a matrix and position? (see LDMatrixObject) - virtual void invert(); // Inverts this object (winding is reversed) - virtual bool isColored() const; // Is this object colored? - virtual bool isScemantic() const; // Does this object have meaning in the part model? - virtual void move (vertex vect); // Moves this object using the given vertex as a movement List - LDObject* next() const; // Object after this in the current file - LDObject* prev() const; // Object prior to this in the current file - virtual str raw() { return ""; } // This object as LDraw code - void replace (LDObject* other); // Replace this LDObject with another LDObject. Object is deleted in the process. - void select(); - void setVertex (int i, const vertex& vert); // Set a vertex to the given value - void setVertexCoord (int i, Axis ax, double value); // Set a single coordinate of a vertex - void swap (LDObject* other); // Swap this object with another. - LDObject* topLevelParent(); // What object in the current file ultimately references this? - virtual str typeName() const; // Type name of this object - void unselect(); - virtual short vertices() const; // Number of vertices this object has + // 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); - static str typeName (LDObject::Type type); // Get type name by enumerator - static LDObject* getDefault (const LDObject::Type type); // Returns a sample object by the given enumerator - static void moveObjects (QList<LDObject*> objs, const bool up); // TODO: move this to LDFile? - static str objectListContents (const QList<LDObject*>& objs); // Get a description of a list of LDObjects + // 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! @@ -129,11 +183,18 @@ QListWidgetItem* qObjListEntry; protected: - bool m_glinit; - friend class GLRenderer; + // 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: - LDSharedVertex* m_coords[4]; + virtual LDObject* clone() = 0; + LDSharedVertex* m_coords[4]; }; // ============================================================================= @@ -142,26 +203,29 @@ // For use as coordinates of LDObjects. Keeps count of references. // ----------------------------------------------------------------------------- class LDSharedVertex -{ public: - inline const vertex& data() const - { return m_data; +{ + public: + inline const Vertex& data() const + { + return m_data; } - inline operator const vertex&() 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); + static LDSharedVertex* getSharedVertex (const Vertex& a); protected: - LDSharedVertex (const vertex& a) : m_data (a) {} + LDSharedVertex (const Vertex& a) : m_data (a) {} private: - QList<LDObject*> m_refs; - vertex m_data; + LDObjectList m_refs; + Vertex m_data; }; // ============================================================================= @@ -180,30 +244,34 @@ // this class distinct in case I get new extension ideas. :) // ============================================================================= class LDMatrixObject -{ DECLARE_PROPERTY (matrix, transform, setTransform) - PROPERTY (LDObject*, linkPointer, setLinkPointer) +{ + PROPERTY (public, LDObject*, LinkPointer, NO_OPS, STOCK_WRITE) + PROPERTY (public, Matrix, Transform, NO_OPS, CUSTOM_WRITE) public: - LDMatrixObject() {} - LDMatrixObject (const matrix& transform, const vertex& pos) : - m_transform (transform), m_position (LDSharedVertex::getSharedVertex (pos)) {} + LDMatrixObject() : + m_Position (LDSharedVertex::getSharedVertex (g_origin)) {} - const vertex& position() const - { return m_position->data(); + 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 setPosition (const vertex& a); - - const double& setCoordinate (const Axis ax, double value) - { vertex v = position(); + void setCoordinate (const Axis ax, double value) + { + Vertex v = getPosition(); v[ax] = value; setPosition (v); - - return position() [ax]; } + void setPosition (const Vertex& a); + private: - LDSharedVertex* m_position; + LDSharedVertex* m_Position; }; // ============================================================================= @@ -215,23 +283,24 @@ // zContent contains the contents of the unparsable line. // ============================================================================= class LDError : public LDObject -{ LDOBJ (Error) +{ + LDOBJ (Error) LDOBJ_NAME (error) LDOBJ_VERTICES (0) LDOBJ_UNCOLORED LDOBJ_SCEMANTIC LDOBJ_NO_MATRIX - PROPERTY (str, fileRef, setFileRef) + PROPERTY (public, QString, FileReferenced, STR_OPS, STOCK_WRITE) public: LDError(); - LDError (str contents, str reason) : contents (contents), reason (reason) {} + LDError (QString contents, QString reason) : contents (contents), reason (reason) {} // Content of this unknown line - str contents; + QString contents; // Why is this gibberish? - str reason; + QString reason; }; // ============================================================================= @@ -240,7 +309,9 @@ // Represents an empty line in the LDraw code file. // ============================================================================= class LDEmpty : public LDObject -{ LDOBJ (Empty) +{ + LDOBJ (Empty) + LDOBJ_NAME (empty) LDOBJ_VERTICES (0) LDOBJ_UNCOLORED LDOBJ_NON_SCEMANTIC @@ -254,7 +325,8 @@ // the text of the comment. // ============================================================================= class LDComment : public LDObject -{ LDOBJ (Comment) +{ + LDOBJ (Comment) LDOBJ_NAME (comment) LDOBJ_VERTICES (0) LDOBJ_UNCOLORED @@ -263,9 +335,9 @@ public: LDComment() {} - LDComment (str text) : text (text) {} + LDComment (QString text) : text (text) {} - str text; // The text of this comment + QString text; // The text of this comment }; // ============================================================================= @@ -275,9 +347,11 @@ // of this statement. // ============================================================================= class LDBFC : public LDObject -{ public: +{ + public: enum Type - { CertifyCCW, + { + CertifyCCW, CCW, CertifyCW, CW, @@ -299,7 +373,8 @@ public: LDBFC() {} - LDBFC (const LDBFC::Type type) : type (type) {} + LDBFC (const LDBFC::Type type) : + type (type) {} // Statement strings static const char* statements[]; @@ -313,17 +388,19 @@ // Represents a single code-1 subfile reference. // ============================================================================= class LDSubfile : public LDObject, public LDMatrixObject -{ LDOBJ (Subfile) +{ + LDOBJ (Subfile) LDOBJ_NAME (subfile) LDOBJ_VERTICES (0) LDOBJ_COLORED LDOBJ_SCEMANTIC LDOBJ_HAS_MATRIX - PROPERTY (LDFile*, fileInfo, setFileInfo) + PROPERTY (public, LDDocumentPointer, FileInfo, NO_OPS, STOCK_WRITE) public: enum InlineFlag - { DeepInline = (1 << 0), + { + DeepInline = (1 << 0), CacheInline = (1 << 1), RendererInline = (1 << 2), @@ -333,12 +410,16 @@ Q_DECLARE_FLAGS (InlineFlags, InlineFlag) LDSubfile() - { setLinkPointer (this); + { + setLinkPointer (this); } // Inlines this subfile. Note that return type is an array of heap-allocated - // LDObject-clones, they must be deleted one way or another. - QList<LDObject*> inlineContents (InlineFlags flags); + // LDObject copies, they must be deleted manually. + LDObjectList inlineContents (InlineFlags flags); + + protected: + ~LDSubfile(); }; Q_DECLARE_OPERATORS_FOR_FLAGS (LDSubfile::InlineFlags) @@ -351,7 +432,8 @@ // set. // ============================================================================= class LDLine : public LDObject -{ LDOBJ (Line) +{ + LDOBJ (Line) LDOBJ_NAME (line) LDOBJ_VERTICES (2) LDOBJ_COLORED @@ -360,17 +442,18 @@ public: LDLine() {} - LDLine (vertex v1, vertex v2); + LDLine (Vertex v1, Vertex v2); }; // ============================================================================= -// LDCndLine +// 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 LDCndLine : public LDLine -{ LDOBJ (CndLine) +class LDCondLine : public LDLine +{ + LDOBJ (CondLine) LDOBJ_NAME (condline) LDOBJ_VERTICES (4) LDOBJ_COLORED @@ -378,7 +461,7 @@ LDOBJ_NO_MATRIX public: - LDCndLine() {} + LDCondLine() {} LDLine* demote(); }; @@ -390,7 +473,8 @@ // triangle is colored with. // ============================================================================= class LDTriangle : public LDObject -{ LDOBJ (Triangle) +{ + LDOBJ (Triangle) LDOBJ_NAME (triangle) LDOBJ_VERTICES (3) LDOBJ_COLORED @@ -399,8 +483,9 @@ public: LDTriangle() {} - LDTriangle (vertex v0, vertex v1, vertex v2) - { setVertex (0, v0); + LDTriangle (Vertex v0, Vertex v1, Vertex v2) + { + setVertex (0, v0); setVertex (1, v1); setVertex (2, v2); } @@ -413,7 +498,8 @@ // of the quad, dColor is the color used for the quad. // ============================================================================= class LDQuad : public LDObject -{ LDOBJ (Quad) +{ + LDOBJ (Quad) LDOBJ_NAME (quad) LDOBJ_VERTICES (4) LDOBJ_COLORED @@ -422,7 +508,7 @@ public: LDQuad() {} - LDQuad (const vertex& v0, const vertex& v1, const vertex& v2, const vertex& v3); + LDQuad (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3); // Split this quad into two triangles (note: heap-allocated) QList<LDTriangle*> splitToTriangles(); @@ -437,7 +523,8 @@ // finished parts. // ============================================================================= class LDVertex : public LDObject -{ LDOBJ (Vertex) +{ + LDOBJ (Vertex) LDOBJ_NAME (vertex) LDOBJ_VERTICES (0) // TODO: move pos to vaCoords[0] LDOBJ_COLORED @@ -447,7 +534,7 @@ public: LDVertex() {} - vertex pos; + Vertex pos; }; // ============================================================================= @@ -457,24 +544,27 @@ // information. // ============================================================================= class LDOverlay : public LDObject -{ LDOBJ (Overlay) +{ + LDOBJ (Overlay) LDOBJ_NAME (overlay) LDOBJ_VERTICES (0) LDOBJ_UNCOLORED LDOBJ_NON_SCEMANTIC LDOBJ_NO_MATRIX - PROPERTY (int, camera, setCamera) - PROPERTY (int, x, setX) - PROPERTY (int, y, setY) - PROPERTY (int, width, setWidth) - PROPERTY (int, height, setHeight) - PROPERTY (str, filename, setFilename) + 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 str CALicense = "!LICENSE Redistributable under CCAL version 2.0 : see CAreadme.txt", +static const QString CALicense = "!LICENSE Redistributable under CCAL version 2.0 : see CAreadme.txt", NonCALicense = "!LICENSE Not redistributable : see NonCAreadme.txt"; -static const short lores = 16; -static const short hires = 48; +static const int lores = 16; +static const int hires = 48; -#endif // LDTYPES_H +QString getLicenseText (int id); + +#endif // LDFORGE_LDTYPES_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,129 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QApplication> +#include <QMessageBox> +#include <QAbstractButton> +#include <QFile> +#include <QTextStream> +#include <QDir> +#include "gui.h" +#include "document.h" +#include "misc.h" +#include "config.h" +#include "colors.h" +#include "types.h" +#include "primitives.h" +#include "gldraw.h" +#include "configDialog.h" +#include "dialogs.h" +#include "crashcatcher.h" + +QList<LDDocument*> g_loadedFiles; +ForgeWindow* g_win = null; +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<StringFormatArg> args) +{ + QString msg = DoFormat (args); + f.write (msg.toUtf8()); + f.flush(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void doPrint (FILE* fp, QList<StringFormatArg> 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
--- a/src/main.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QApplication> -#include <QMessageBox> -#include <QAbstractButton> -#include <QFile> -#include <QTextStream> -#include "gui.h" -#include "file.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<LDFile*> g_loadedFiles; -ForgeWindow* g_win = null; -const QApplication* g_app = null; -File g_file_stdout (stdout, File::Write); -File g_file_stderr (stderr, File::Write); -static str g_versionString, g_fullVersionString; - -const vertex g_origin (0.0f, 0.0f, 0.0f); -const matrix g_identity ( {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}); - -cfg (Bool, firststart, true); - -// ============================================================================= -// ----------------------------------------------------------------------------- -int main (int argc, char* argv[]) -{ QApplication app (argc, argv); - app.setOrganizationName (APPNAME); - app.setApplicationName (APPNAME); - g_app = &app; - - initCrashCatcher(); - LDFile::setCurrent (null); - - // Load or create the configuration - if (!Config::load()) - { log ("Creating configuration file...\n"); - - if (Config::save()) - log ("Configuration file successfully created.\n"); - else - log ("failed to create configuration file!\n"); - } - - LDPaths::initPaths(); - initColors(); - loadLogoedStuds(); - - ForgeWindow* win = new ForgeWindow; - newFile(); - win->show(); - - // If this is the first start, get the user to configuration. Especially point - // them to the profile tab, it's the most important form to fill in. - if (firststart) - { (new ConfigDialog (ConfigDialog::ProfileTab))->exec(); - firststart = false; - Config::save(); - } - - loadPrimitives(); - return app.exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void doPrint (File& f, initlist<StringFormatArg> args) -{ str msg = DoFormat (args); - f.write (msg.toUtf8()); - f.flush(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void doPrint (FILE* fp, initlist<StringFormatArg> args) -{ str msg = DoFormat (args); - fwrite (msg.toStdString().c_str(), 1, msg.length(), fp); - fflush (fp); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString versionString() -{ if (g_versionString.length() == 0) - { -#if VERSION_PATCH == 0 - g_versionString = fmt ("%1.%2", VERSION_MAJOR, VERSION_MINOR); -#else - g_versionString = fmt ("%1.%2.%3", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); -#endif // VERSION_PATCH - } - - return g_versionString; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString versionMoniker() -{ -#if BUILD_ID == BUILD_INTERNAL - return "Internal"; -#elif BUILD_ID == BUILD_ALPHA - return "Alpha"; -#elif BUILD_ID == BUILD_BETA - return "Beta"; -#elif BUILD_ID == BUILD_RC - return fmt ("RC %1", RC_NUMBER); -#else - return ""; -#endif // BUILD_ID -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString fullVersionString() -{ return fmt ("v%1 %2", versionString(), versionMoniker()); -} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.h Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +// ============================================================================= +// This file is included one way or another in every source file of LDForge. +// Stuff defined and included here is universally included. + +#ifndef LDFORGE_MAIN_H +#define LDFORGE_MAIN_H + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdarg.h> +#include <QString> +#include <QTextFormat> + +#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 + +// 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<const Axis> ({X, Y, Z})) + +// ----------------------------------------------------------------------------- +#ifdef IN_IDE_PARSER // KDevelop workarounds: +# error IN_IDE_PARSER is defined (this code is only for KDevelop workarounds) +# define COMPILE_DATE "14-01-10 10:31:09" + +# ifndef va_start +# define va_start(va, arg) +# endif // va_start + +# ifndef va_end +# define va_end(va) +# endif // va_end + +static const char* __func__ = ""; // Current function name +typedef void FILE; // :| +#endif // IN_IDE_PARSER + +#endif // LDFORGE_MAIN_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/messagelog.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,139 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QTimer> +#include <QDate> +#include "messagelog.h" +#include "gldraw.h" +#include "gui.h" +#include "moc_messagelog.cpp" + +static const int g_maxMessages = 5; +static const int g_expiry = 5; +static const int g_fadeTime = 500; // msecs + +// ============================================================================= +// ----------------------------------------------------------------------------- +MessageManager::MessageManager (QObject* parent) : + QObject (parent) +{ + m_ticker = new QTimer; + m_ticker->start (100); + connect (m_ticker, SIGNAL (timeout()), this, SLOT (tick())); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +MessageManager::Line::Line (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::Line>& MessageManager::getLines() const +{ + return m_lines; +} + +// ============================================================================= +// log() interface - format the argument list and add the resulting string to +// the main message manager. +// ----------------------------------------------------------------------------- +void DoLog (std::initializer_list<StringFormatArg> args) +{ + const 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); + } +}
--- a/src/messagelog.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QTimer> -#include <QDate> -#include "messagelog.h" -#include "gldraw.h" -#include "gui.h" -#include "moc_messagelog.cpp" - -static const int g_maxMessages = 5; -static const int g_expiry = 5; -static const int g_fadeTime = 500; // msecs - -// ============================================================================= -// ----------------------------------------------------------------------------- -MessageManager::MessageManager (QObject* parent) : - QObject (parent) -{ m_ticker = new QTimer; - m_ticker->start (100); - connect (m_ticker, SIGNAL (timeout()), this, SLOT (tick())); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -MessageManager::Line::Line (str text) : - text (text), - alpha (1.0f), - expiry (QDateTime::currentDateTime().addSecs (g_expiry)) {} - -// ============================================================================= -// Check this line's expiry and update alpha accordingly. Returns true if the -// line is to still stick around, false if it expired. 'changed' is updated to -// whether the line has somehow changed since the last update. -// ----------------------------------------------------------------------------- -bool MessageManager::Line::update (bool& changed) -{ changed = false; - QDateTime now = QDateTime::currentDateTime(); - int msec = now.msecsTo (expiry); - - if (now >= expiry) - { // Message line has expired - changed = true; - return false; - } - - if (msec <= g_fadeTime) - { // Message line has not expired but is fading out - alpha = ( (float) msec) / g_fadeTime; - changed = true; - } - - return true; -} - -// ============================================================================= -// Add a line to the message manager. -// ----------------------------------------------------------------------------- -void MessageManager::addLine (str line) -{ // If there's too many entries, pop the excess out - while (m_lines.size() >= g_maxMessages) - m_lines.removeFirst(); - - m_lines << Line (line); - - // Update the renderer view - if (renderer()) - renderer()->update(); -} - -// ============================================================================= -// Ticks the message manager. All lines are ticked and the renderer scene is -// redrawn if something changed. -// ----------------------------------------------------------------------------- -void MessageManager::tick() -{ if (m_lines.isEmpty()) - return; - - bool changed = false; - - for (int i = 0; i < m_lines.size(); ++i) - { bool lineChanged; - - if (!m_lines[i].update (lineChanged)) - m_lines.removeAt (i--); - - changed |= lineChanged; - } - - if (changed && renderer()) - renderer()->update(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -const QList<MessageManager::Line>& MessageManager::getLines() const -{ return m_lines; -} - -// ============================================================================= -// log() interface - format the argument list and add the resulting string to -// the main message manager. -// ----------------------------------------------------------------------------- -void DoLog (std::initializer_list<StringFormatArg> args) -{ const str msg = DoFormat (args); - - for (str& a : msg.split ("\n", QString::SkipEmptyParts)) - { if (g_win) - g_win->addMessage (a); - - // Also print it to stdout - fprint (stdout, "%1\n", a); - } -}
--- a/src/messagelog.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/messagelog.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -16,12 +16,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MESSAGELOG_H -#define MESSAGELOG_H +#ifndef LDFORGE_MESSAGELOG_H +#define LDFORGE_MESSAGELOG_H #include <QObject> #include <QDate> -#include "common.h" +#include "main.h" #include "types.h" class GLRenderer; @@ -38,23 +38,25 @@ * repainting. */ class MessageManager : public QObject -{ Q_OBJECT - PROPERTY (GLRenderer*, renderer, setRenderer) +{ + Q_OBJECT + PROPERTY (public, GLRenderer*, Renderer, NO_OPS, STOCK_WRITE) public: // Single line of the message log. class Line - { public: - Line (str text); + { + public: + Line (QString text); bool update (bool& changed); - str text; + QString text; float alpha; QDateTime expiry; }; explicit MessageManager (QObject* parent = 0); - void addLine (str line); + void addLine (QString line); const QList<Line>& getLines() const; private: @@ -65,4 +67,4 @@ void tick(); }; -#endif // MESSAGELOG_H +#endif // LDFORGE_MESSAGELOG_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include <locale.h> +#include <QColor> +#include "main.h" +#include "misc.h" +#include "gui.h" +#include "dialogs.h" +#include "document.h" +#include "ui_rotpoint.h" +#include "misc/documentPointer.cc" +#include "misc/ringFinder.cc" +#include "misc/invokationDeferer.cc" + +// Prime number table. +const int g_primes[NUM_PRIMES] = +{ + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, + 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, + 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, + 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, + 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, + 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, + 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, + 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, + 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, + 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, + 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, + 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, + 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, + 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, + 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, + 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, + 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, + 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, + 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, + 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, + 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, + 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, + 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, + 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, + 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, + 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, + 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, + 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, + 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, + 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, + 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, + 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, + 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, + 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, + 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, + 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, + 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, + 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, + 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, + 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, + 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, + 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, + 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, + 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, + 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, + 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, + 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, + 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, + 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, + 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, +}; + +static const long g_e10[] = +{ + 1l, + 10l, + 100l, + 1000l, + 10000l, + 100000l, + 1000000l, + 10000000l, + 100000000l, + 1000000000l, +}; + +// ============================================================================= +// ----------------------------------------------------------------------------- +// Grid stuff +cfg (Int, grid, Grid::Medium); +cfg (Float, grid_coarse_x, 5.0f); +cfg (Float, grid_coarse_y, 5.0f); +cfg (Float, grid_coarse_z, 5.0f); +cfg (Float, grid_coarse_angle, 45.0f); +cfg (Float, grid_medium_x, 1.0f); +cfg (Float, grid_medium_y, 1.0f); +cfg (Float, grid_medium_z, 1.0f); +cfg (Float, grid_medium_angle, 22.5f); +cfg (Float, grid_fine_x, 0.1f); +cfg (Float, grid_fine_y, 0.1f); +cfg (Float, grid_fine_z, 0.1f); +cfg (Float, grid_fine_angle, 7.5f); +cfg (Int, edit_rotpoint, 0); +cfg (Vertex, edit_customrotpoint, g_origin); + +const gridinfo g_GridInfo[3] = +{ + { "Coarse", { &grid_coarse_x, &grid_coarse_y, &grid_coarse_z, &grid_coarse_angle }}, + { "Medium", { &grid_medium_x, &grid_medium_y, &grid_medium_z, &grid_medium_angle }}, + { "Fine", { &grid_fine_x, &grid_fine_y, &grid_fine_z, &grid_fine_angle }} +}; + +// ============================================================================= +// Snap the given coordinate value on the current grid's given axis. +// ----------------------------------------------------------------------------- +double Grid::snap (double in, const Grid::Config axis) +{ + const double gridval = *currentGrid().confs[axis]; + const long mult = abs (in / gridval); + const bool neg = (in < 0); + double out = mult * gridval; + + if (abs<double> (in) - (mult * gridval) > gridval / 2) + out += gridval; + + if (neg && out != 0) + out *= -1; + + return out; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool numeric (const QString& tok) +{ + bool gotDot = false; + + for (int i = 0; i < tok.length(); ++i) + { + const QChar c = tok[i]; + + // Allow leading hyphen for negatives + if (i == 0 && c == '-') + continue; + + // Check for decimal point + if (!gotDot && c == '.') + { + gotDot = true; + continue; + } + + if (c >= '0' && c <= '9') + continue; // Digit + + // If the above cases didn't catch this character, it was + // illegal and this is therefore not a number. + return false; + } + + return true; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void simplify (int& numer, int& denom) +{ + bool repeat; + + do + { + repeat = false; + + for (int x = 0; x < NUM_PRIMES; x++) + { + const int prime = g_primes[NUM_PRIMES - x - 1]; + + if (numer <= prime || denom <= prime) + continue; + + if ((numer % prime == 0) && (denom % prime == 0)) + { + numer /= prime; + denom /= prime; + repeat = true; + break; + } + } + } + while (repeat); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +Vertex rotPoint (const LDObjectList& objs) +{ + switch ((ERotationPoint) edit_rotpoint) + { + case EObjectOrigin: + { + LDBoundingBox box; + + // Calculate center vertex + for (LDObject* obj : objs) + { + if (obj->hasMatrix()) + box << dynamic_cast<LDMatrixObject*> (obj)->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<StringFormatArg> 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
--- a/src/misc.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,410 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <math.h> -#include <locale.h> -#include <QColor> -#include "common.h" -#include "misc.h" -#include "gui.h" -#include "dialogs.h" -#include "ui_rotpoint.h" - -RingFinder g_RingFinder; - -// Prime number table. -const int g_primes[NUM_PRIMES] = -{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, - 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, - 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, - 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, - 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, - 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, - 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, - 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, - 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, - 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, - 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, - 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, - 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, - 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, - 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, - 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, - 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, - 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, - 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, - 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, - 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, - 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, - 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, - 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, - 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, - 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, - 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, - 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, - 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, - 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, - 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, - 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, - 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, - 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, - 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, - 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, - 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, - 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, - 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, - 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, - 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, - 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, - 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, - 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, - 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, - 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, - 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, - 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, - 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, - 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, -}; - -// ============================================================================= -// ----------------------------------------------------------------------------- -// Grid stuff -cfg (Int, grid, Grid::Medium); - -cfg (Float, grid_coarse_x, 5.0f); -cfg (Float, grid_coarse_y, 5.0f); -cfg (Float, grid_coarse_z, 5.0f); -cfg (Float, grid_coarse_angle, 45.0f); -cfg (Float, grid_medium_x, 1.0f); -cfg (Float, grid_medium_y, 1.0f); -cfg (Float, grid_medium_z, 1.0f); -cfg (Float, grid_medium_angle, 22.5f); -cfg (Float, grid_fine_x, 0.1f); -cfg (Float, grid_fine_y, 0.1f); -cfg (Float, grid_fine_z, 0.1f); -cfg (Float, grid_fine_angle, 7.5f); -cfg (Int, edit_rotpoint, 0); -cfg (Float, edit_rotpoint_x, 0.0f); // TODO: make a VertexConfig and use it here -cfg (Float, edit_rotpoint_y, 0.0f); -cfg (Float, edit_rotpoint_z, 0.0f); - -const gridinfo g_GridInfo[3] = -{ { "Coarse", { &grid_coarse_x, &grid_coarse_y, &grid_coarse_z, &grid_coarse_angle }}, - { "Medium", { &grid_medium_x, &grid_medium_y, &grid_medium_z, &grid_medium_angle }}, - { "Fine", { &grid_fine_x, &grid_fine_y, &grid_fine_z, &grid_fine_angle }} -}; - -// ============================================================================= -// Snap the given coordinate value on the current grid's given axis. -// ----------------------------------------------------------------------------- -double Grid::snap (double in, const Grid::Config axis) -{ const double gridval = currentGrid().confs[axis]->value; - const long mult = abs (in / gridval); - const bool neg = (in < 0); - double out = mult * gridval; - - if (abs<double> (in) - (mult * gridval) > gridval / 2) - out += gridval; - - if (neg && out != 0) - out *= -1; - - return out; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool numeric (const str& tok) -{ bool gotDot = false; - - for (int i = 0; i < tok.length(); ++i) - { const qchar c = tok[i]; - - // Allow leading hyphen for negatives - if (i == 0 && c == '-') - continue; - - // Check for decimal point - if (!gotDot && c == '.') - { gotDot = true; - continue; - } - - if (c >= '0' && c <= '9') - continue; // Digit - - // If the above cases didn't catch this character, it was - // illegal and this is therefore not a number. - return false; - } - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void simplify (int& numer, int& denom) -{ bool repeat; - - do - { repeat = false; - - for (int x = 0; x < NUM_PRIMES; x++) - { const int prime = g_primes[NUM_PRIMES - x - 1]; - - if (numer <= prime || denom <= prime) - continue; - - if ( (numer % prime == 0) && (denom % prime == 0)) - { numer /= prime; - denom /= prime; - repeat = true; - break; - } - } - } - while (repeat); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex rotPoint (const QList<LDObject*>& objs) -{ LDBoundingBox box; - - switch (edit_rotpoint) - { case ObjectOrigin: - - // Calculate center vertex - for (LDObject * obj : objs) - if (obj->hasMatrix()) - box << dynamic_cast<LDMatrixObject*> (obj)->position(); - else - box << obj; - - return box.center(); - - case WorldOrigin: - return g_origin; - - case CustomPoint: - return vertex (edit_rotpoint_x, edit_rotpoint_y, edit_rotpoint_z); - } - - return vertex(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void configRotationPoint() -{ QDialog* dlg = new QDialog; - Ui::RotPointUI ui; - ui.setupUi (dlg); - - switch (edit_rotpoint) - { case ObjectOrigin: - ui.objectPoint->setChecked (true); - break; - - case WorldOrigin: - ui.worldPoint->setChecked (true); - break; - - case CustomPoint: - ui.customPoint->setChecked (true); - break; - } - - ui.customX->setValue (edit_rotpoint_x); - ui.customY->setValue (edit_rotpoint_y); - ui.customZ->setValue (edit_rotpoint_z); - - if (!dlg->exec()) - return; - - edit_rotpoint = - (ui.objectPoint->isChecked()) ? ObjectOrigin : - (ui.worldPoint->isChecked()) ? WorldOrigin : - CustomPoint; - - edit_rotpoint_x = ui.customX->value(); - edit_rotpoint_y = ui.customY->value(); - edit_rotpoint_z = ui.customZ->value(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str join (initlist<StringFormatArg> vals, str delim) -{ QStringList list; - - for (const StringFormatArg& arg : vals) - list << arg.value(); - - return list.join (delim); -} - -// ============================================================================= -// This is the main algorithm of the ring finder. It tries to use math to find -// the one ring between r0 and r1. If it fails (the ring number is non-integral), -// it finds an intermediate radius (ceil of the ring number times scale) and -// splits the radius at this point, calling this function again to try find the -// rings between r0 - r and r - r1. -// -// This does not always yield into usable results. If at some point r == r0 or -// r == r1, there is no hope of finding the rings, at least with this algorithm, -// as it would fall into an infinite recursion. -// ----------------------------------------------------------------------------- -bool RingFinder::findRingsRecursor (double r0, double r1, Solution& currentSolution) -{ char tabs[64]; - memset (tabs, '\t', m_stack); - tabs[m_stack] = '\0'; - - // Don't recurse too deep. - if (m_stack >= 5) - return false; - - // Find the scale and number of a ring between r1 and r0. - assert (r1 >= r0); - double scale = r1 - r0; - double num = r0 / scale; - - // If the ring number is integral, we have found a fitting ring to r0 -> r1! - if (isInteger (num)) - { Component cmp; - cmp.scale = scale; - cmp.num = (int) round (num); - currentSolution.addComponent (cmp); - - // If we're still at the first recursion, this is the only - // ring and there's nothing left to do. Guess we found the winner. - if (m_stack == 0) - { m_solutions.push_back (currentSolution); - return true; - } - } - else - { // Try find solutions by splitting the ring in various positions. - if (isZero (r1 - r0)) - return false; - - double interval; - - // Determine interval. The smaller delta between radii, the more precise - // interval should be used. We can't really use a 0.5 increment when - // calculating rings to 10 -> 105... that would take ages to process! - if (r1 - r0 < 0.5) - interval = 0.1; - else if (r1 - r0 < 10) - interval = 0.5; - else if (r1 - r0 < 50) - interval = 1; - else - interval = 5; - - // Now go through possible splits and try find rings for both segments. - for (double r = r0 + interval; r < r1; r += interval) - { Solution sol = currentSolution; - - m_stack++; - bool res = findRingsRecursor (r0, r, sol) && findRingsRecursor (r, r1, sol); - m_stack--; - - if (res) - { // We succeeded in finding radii for this segment. If the stack is 0, this - // is the first recursion to this function. Thus there are no more ring segments - // to process and we can add the solution. - // - // If not, when this function ends, it will be called again with more arguments. - // Accept the solution to this segment by setting currentSolution to sol, and - // return true to continue processing. - if (m_stack == 0) - m_solutions.push_back (sol); - else - { currentSolution = sol; - return true; - } - } - } - - return false; - } - - return true; -} - -// ============================================================================= -// Main function. Call this with r0 and r1. If this returns true, use bestSolution -// for the solution that was presented. -// ----------------------------------------------------------------------------- -bool RingFinder::findRings (double r0, double r1) -{ m_solutions.clear(); - Solution sol; - - // Recurse in and try find solutions. - findRingsRecursor (r0, r1, sol); - - // Compare the solutions and find the best one. The solution class has an operator> - // overload to compare two solutions. - m_bestSolution = null; - - for (QVector<Solution>::iterator solp = m_solutions.begin(); solp != m_solutions.end(); ++solp) - { const Solution& sol = *solp; - - if (m_bestSolution == null || sol > *m_bestSolution) - m_bestSolution = / - } - - return (m_bestSolution != null); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool RingFinder::Solution::operator> (const RingFinder::Solution& other) const -{ // If this solution has less components than the other one, this one - // is definitely better. - if (components().size() < other.components().size()) - return true; - - // vice versa - if (other.components().size() < components().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 < components().size(); ++i) - { if (components()[i].num > maxA) - maxA = components()[i].num; - - if (other.components()[i].num > maxB) - maxB = other.components()[i].num; - } - - if (maxA < maxB) - return true; - - if (maxB < maxA) - return false; - - // Solutions have equal rings and equal maximum ring numbers. Let's - // just say this one is better, at this point it does not matter which - // one is chosen. - return true; -}
--- a/src/misc.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/misc.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -16,16 +16,17 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MISC_H -#define MISC_H +#ifndef LDFORGE_MISC_H +#define LDFORGE_MISC_H #include <QVector> #include "config.h" -#include "common.h" +#include "main.h" #include "types.h" #define NUM_PRIMES 500 +class LDDocument; class QColor; class QAction; @@ -33,17 +34,20 @@ extern const int g_primes[NUM_PRIMES]; // Returns whether a given string represents a floating point number. -bool numeric (const str& tok); +bool numeric (const QString& tok); // Simplifies the given fraction. void simplify (int& numer, int& denom); -str join (initlist<StringFormatArg> vals, str delim = " "); +void roundToDecimals (double& a, int decimals); + +QString join (initlist<StringFormatArg> vals, QString delim = " "); // Grid stuff struct gridinfo -{ const char* const name; - FloatConfig* const confs[4]; +{ + const char* const name; + float* const confs[4]; }; extern_cfg (Int, grid); @@ -51,29 +55,34 @@ extern const gridinfo g_GridInfo[3]; inline const gridinfo& currentGrid() -{ return g_GridInfo[grid]; +{ + return g_GridInfo[grid]; } // ============================================================================= -enum RotationPoint -{ ObjectOrigin, - WorldOrigin, - CustomPoint +enum ERotationPoint +{ + EObjectOrigin, + EWorldOrigin, + ECustomPoint }; -vertex rotPoint (const QList<LDObject*>& objs); +Vertex rotPoint (const LDObjectList& objs); void configRotationPoint(); // ============================================================================= namespace Grid -{ enum Type - { Coarse, +{ + enum Type + { + Coarse, Medium, Fine }; enum Config - { X, + { + X, Y, Z, Angle @@ -82,108 +91,52 @@ double snap (double value, const Grid::Config axis); } -// ============================================================================= -// 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<Component>& components() const - { return m_components; - } - - // Add a component to this solution - void addComponent (const Component& a) - { m_components.push_back (a); - } - - // Compare solutions - bool operator> (const Solution& other) const; - - private: - QVector<Component> m_components; - }; - - RingFinder() {} - bool findRings (double r0, double r1); - - inline const Solution* bestSolution() - { return m_bestSolution; - } - - inline const QVector<Solution>& allSolutions() const - { return m_solutions; - } - - inline bool operator() (double r0, double r1) - { return findRings (r0, r1); - } - -private: - QVector<Solution> m_solutions; - const Solution* m_bestSolution; - int m_stack; - - bool findRingsRecursor (double r0, double r1, Solution& currentSolution); -}; - -extern RingFinder g_RingFinder; - -// ============================================================================= -template<class T> void dataswap (T& a, T& b) -{ T c = a; - a = b; - b = c; -} - // ----------------------------------------------------------------------------- // Plural expression template<class T> static inline const char* plural (T n) -{ return (n != 1) ? "s" : ""; +{ + return (n != 1) ? "s" : ""; } // ----------------------------------------------------------------------------- // Templated clamp template<class T> static inline T clamp (T a, T min, T max) -{ return (a > max) ? max : (a < min) ? min : a; +{ + return (a > max) ? max : (a < min) ? min : a; } // Templated minimum template<class T> static inline T min (T a, T b) -{ return (a < b) ? a : b; +{ + return (a < b) ? a : b; } // Templated maximum template<class T> static inline T max (T a, T b) -{ return (a > b) ? a : b; +{ + return (a > b) ? a : b; } // Templated absolute value template<class T> static inline T abs (T a) -{ return (a >= 0) ? a : -a; +{ + return (a >= 0) ? a : -a; } template<class T> inline bool isZero (T a) -{ return abs<T> (a) < 0.0001; +{ + return abs<T> (a) < 0.0001; } template<class T> inline bool isInteger (T a) -{ return isZero (a - (int) a); +{ + return isZero (a - (int) a); } template<class T> void removeDuplicates (QList<T>& a) -{ std::sort (a.begin(), a.end()); - typename QList<T>::iterator pos = std::unique (a.begin(), a.end()); - a.erase (pos, a.end()); +{ + std::sort (a.begin(), a.end()); + a.erase (std::unique (a.begin(), a.end()), a.end()); } -#endif // MISC_H +#endif // LDFORGE_MISC_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/documentPointer.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,76 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "documentPointer.h" +#include "../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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/documentPointer.h Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/invokationDeferer.cc Mon Jan 20 15:04:26 2014 +0200 @@ -0,0 +1,57 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013, 2014 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/invokationDeferer.h Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef LDFORGE_MISC_INVOKATION_DEFERER_H +#define LDFORGE_MISC_INVOKATION_DEFERER_H + +#include <QObject> + +class InvokationDeferer : public QObject +{ + Q_OBJECT + + public: + using FunctionType = void(*)(); + + explicit InvokationDeferer (QObject* parent = 0); + void addFunctionCall (FunctionType func); + + signals: + void functionAdded(); + + private: + QList<FunctionType> m_funcs; + + private slots: + void invokeFunctions(); +}; + +void invokeLater (InvokationDeferer::FunctionType func); + +#endif // LDFORGE_MISC_INVOKATION_DEFERER_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/ringFinder.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#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<Solution>::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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc/ringFinder.h Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#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<Component>& 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<Component> m_components; + }; + + RingFinder() {} + bool findRings (double r0, double r1); + + inline const Solution* bestSolution() + { + return m_bestSolution; + } + + inline const QVector<Solution>& allSolutions() const + { + return m_solutions; + } + + inline bool operator() (double r0, double r1) + { + return findRings (r0, r1); + } + + private: + QVector<Solution> 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/primitives.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#include <QDir> +#include <QRegExp> +#include <QFileDialog> +#include "document.h" +#include "gui.h" +#include "primitives.h" +#include "ui_makeprim.h" +#include "misc.h" +#include "colors.h" +#include "moc_primitives.cpp" + +QList<PrimitiveCategory*> g_PrimitiveCategories; +QList<Primitive> g_primitives; +static 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<QString>& fnames) +{ + QFileInfoList flist = dir.entryInfoList (QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + + for (const QFileInfo& info : flist) + { + if (info.isDir()) + recursiveGetFilenames (QDir (info.absoluteFilePath()), fnames); + else + fnames << info.absoluteFilePath(); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +PrimitiveScanner::PrimitiveScanner (QObject* parent) : + QObject (parent), + m_i (0) +{ + g_activeScanner = this; + QDir dir (LDPaths::prims()); + assert (dir.exists()); + m_baselen = dir.absolutePath().length(); + recursiveGetFilenames (dir, m_files); + emit starting (m_files.size()); + 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<QLineF>& lines) +{ + for (int i = 0; i < segs; ++i) + { + double x0 = radius * radialPoint (i, divs, cos), + x1 = radius * radialPoint (i + 1, divs, cos), + z0 = radius * radialPoint (i, divs, sin), + z1 = radius * radialPoint (i + 1, divs, sin); + + lines << QLineF (QPointF (x0, z0), QPointF (x1, z1)); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObjectList makePrimitive (PrimitiveType type, int segs, int divs, int num) +{ + LDObjectList objs; + QList<int> condLineSegs; + QList<QLineF> circle; + + makeCircle (segs, divs, 1, circle); + + for (int i = 0; i < segs; ++i) + { + double x0 = circle[i].x1(), + x1 = circle[i].x2(), + z0 = circle[i].y1(), + z1 = circle[i].y2(); + + switch (type) + { + case Circle: + { + Vertex v0 (x0, 0.0f, z0), + v1 (x1, 0.0f, z1); + + LDLine* line = new LDLine; + line->setVertex (0, v0); + line->setVertex (1, v1); + line->setColor (edgecolor); + objs << line; + } break; + + case Cylinder: + case Ring: + case Cone: + { + double x2, x3, z2, z3; + double y0, y1, y2, y3; + + if (type == Cylinder) + { + x2 = x1; + x3 = x0; + z2 = z1; + z3 = z0; + + y0 = y1 = 0.0f; + y2 = y3 = 1.0f; + } + else + { + x2 = x1 * (num + 1); + x3 = x0 * (num + 1); + z2 = z1 * (num + 1); + z3 = z0 * (num + 1); + + x0 *= num; + x1 *= num; + z0 *= num; + z1 *= num; + + if (type == Ring) + y0 = y1 = y2 = y3 = 0.0f; + else + { + y0 = y1 = 1.0f; + y2 = y3 = 0.0f; + } + } + + Vertex v0 (x0, y0, z0), + v1 (x1, y1, z1), + v2 (x2, y2, z2), + v3 (x3, y3, z3); + + LDQuad* quad = new LDQuad; + quad->setColor (maincolor); + quad->setVertex (0, v0); + quad->setVertex (1, v1); + quad->setVertex (2, v2); + quad->setVertex (3, v3); + + if (type == Cylinder) + quad->invert(); + + objs << quad; + + if (type == Cylinder || type == Cone) + condLineSegs << i; + } break; + + case Disc: + case DiscNeg: + { + double x2, z2; + + if (type == Disc) + x2 = z2 = 0.0f; + else + { + x2 = (x0 >= 0.0f) ? 1.0f : -1.0f; + z2 = (z0 >= 0.0f) ? 1.0f : -1.0f; + } + + Vertex v0 (x0, 0.0f, z0), + v1 (x1, 0.0f, z1), + v2 (x2, 0.0f, z2); + + // Disc negatives need to go the other way around, otherwise + // they'll end up upside-down. + LDTriangle* seg = new LDTriangle; + seg->setColor (maincolor); + seg->setVertex (type == Disc ? 0 : 2, v0); + seg->setVertex (1, v1); + seg->setVertex (type == Disc ? 2 : 0, v2); + objs << seg; + } break; + } + } + + // If this is not a full circle, we need a conditional line at the other + // end, too. + if (segs < divs && condLineSegs.size() != 0) + condLineSegs << segs; + + for (int i : condLineSegs) + { + Vertex v0 (radialPoint (i, divs, cos), 0.0f, radialPoint (i, divs, sin)), + v1, + v2 (radialPoint (i + 1, divs, cos), 0.0f, radialPoint (i + 1, divs, sin)), + v3 (radialPoint (i - 1, divs, cos), 0.0f, radialPoint (i - 1, divs, sin)); + + if (type == Cylinder) + v1 = Vertex (v0[X], 1.0f, v0[Z]); + elif (type == Cone) + { + v1 = Vertex (v0[X] * (num + 1), 0.0f, v0[Z] * (num + 1)); + v0[X] *= num; + v0[Y] = 1.0f; + v0[Z] *= num; + } + + LDCondLine* line = new LDCondLine; + line->setColor (edgecolor); + line->setVertex (0, v0); + line->setVertex (1, v1); + line->setVertex (2, v2); + line->setVertex (3, v3); + objs << line; + } + + return objs; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +static QString primitiveTypeName (PrimitiveType type) +{ + // Not translated as primitives are in English. + return type == Circle ? "Circle" : + type == Cylinder ? "Cylinder" : + type == Disc ? "Disc" : + type == DiscNeg ? "Disc Negative" : + type == Ring ? "Ring" : "Cone"; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString radialFileName (PrimitiveType type, int segs, int divs, int num) +{ + int numer = segs, + denom = divs; + + // Simplify the fractional part, but the denominator must be at least 4. + simplify (numer, denom); + + if (denom < 4) + { + const int factor = 4 / denom; + numer *= factor; + denom *= factor; + } + + // Compose some general information: prefix, fraction, root, ring number + QString prefix = (divs == 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; +}
--- a/src/primitives.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,601 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QDir> -#include <QThread> -#include <QRegExp> -#include <QFileDialog> -#include "file.h" -#include "gui.h" -#include "primitives.h" -#include "ui_makeprim.h" -#include "misc.h" -#include "colors.h" -#include "moc_primitives.cpp" - -QList<PrimitiveCategory> g_PrimitiveCategories; -QList<Primitive> g_primitives; -static PrimitiveLister* g_activePrimLister = null; -static bool g_primListerMutex = false; -static const str g_Other = PrimitiveLister::tr ("Other"); - -static const str g_radialNameRoots[] = -{ "edge", - "cyli", - "disc", - "ndis", - "ring", - "con" -}; - -static void populateCategories(); -static void loadPrimitiveCatgories(); - -// ============================================================================= -// ----------------------------------------------------------------------------- -void loadPrimitives() -{ log ("Loading primitives...\n"); - loadPrimitiveCatgories(); - - // Try to load prims.cfg - File conf (Config::filepath ("prims.cfg"), File::Read); - - if (!conf) - { // No prims.cfg, build it - PrimitiveLister::start(); - } - else - { // Read primitives from prims.cfg - for (str line : conf) - { int space = line.indexOf (" "); - - if (space == -1) - continue; - - Primitive info; - info.name = line.left (space); - info.title = line.mid (space + 1); - g_primitives << info; - } - - populateCategories(); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void recursiveGetFilenames (QDir dir, QList<str>& fnames) -{ QFileInfoList flist = dir.entryInfoList(); - -for (const QFileInfo & info : flist) - { if (info.fileName() == "." || info.fileName() == "..") - continue; // skip . and .. - - if (info.isDir()) - recursiveGetFilenames (QDir (info.absoluteFilePath()), fnames); - else - fnames << info.absoluteFilePath(); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PrimitiveLister::work() -{ g_activePrimLister = this; - m_prims.clear(); - - QDir dir (LDPaths::prims()); - int baselen = dir.absolutePath().length(); - int i = 0; - QList<str> fnames; - - assert (dir.exists()); - recursiveGetFilenames (dir, fnames); - emit starting (fnames.size()); - -for (str fname : fnames) - { File f (fname, File::Read); - - Primitive info; - info.name = fname.mid (baselen + 1); // make full path relative - info.name.replace ('/', '\\'); // use DOS backslashes, they're expected - info.cat = null; - - if (!f.readLine (info.title)) - info.title = ""; - - info.title = info.title.simplified(); - - if (info.title[0] == '0') - { info.title.remove (0, 1); // remove 0 - info.title = info.title.simplified(); - } - - m_prims << info; - emit update (++i); - } - - // Save to a config file - File conf (Config::filepath ("prims.cfg"), File::Write); - -for (Primitive & info : m_prims) - fprint (conf, "%1 %2\n", info.name, info.title); - - conf.close(); - - g_primListerMutex = true; - g_primitives = m_prims; - populateCategories(); - g_primListerMutex = false; - g_activePrimLister = null; - emit workDone(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PrimitiveLister::start() -{ if (g_activePrimLister) - return; - - PrimitiveLister* lister = new PrimitiveLister; - QThread* listerThread = new QThread; - lister->moveToThread (listerThread); - connect (lister, SIGNAL (starting (int)), g_win, SLOT (primitiveLoaderStart (int))); - connect (lister, SIGNAL (update (int)), g_win, SLOT (primitiveLoaderUpdate (int))); - connect (lister, SIGNAL (workDone()), g_win, SLOT (primitiveLoaderEnd())); - connect (listerThread, SIGNAL (started()), lister, SLOT (work())); - connect (listerThread, SIGNAL (finished()), lister, SLOT (deleteLater())); - listerThread->start(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static PrimitiveCategory* findCategory (str name) -{ for (PrimitiveCategory & cat : g_PrimitiveCategories) - if (cat.name() == name) - return &cat; - - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void populateCategories() -{ for (PrimitiveCategory & cat : g_PrimitiveCategories) - cat.prims.clear(); - - PrimitiveCategory* unmatched = findCategory (g_Other); - - if (!unmatched) - { // Shouldn't happen.. but catch it anyway. - PrimitiveCategory cat; - cat.setName (g_Other); - g_PrimitiveCategories << cat; - unmatched = &g_PrimitiveCategories.last(); - } - - 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 (PrimitiveCategory::RegexEntry& entry : cat.regexes) - { switch (entry.type) - { case PrimitiveCategory::Filename: - // f-regex, check against filename - matched = entry.regex.exactMatch (prim.name); - break; - - case PrimitiveCategory::Title: - // 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) - break; - } - - // If there was a match, add the primitive to the category. - // Otherwise, add it to the list of unmatched primitives. - if (prim.cat) - prim.cat->prims << prim; - else - unmatched->prims << prim; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static void loadPrimitiveCatgories() -{ g_PrimitiveCategories.clear(); - File f (Config::dirpath() + "primregexps.cfg", File::Read); - - if (!f) - f.open (":/data/primitive-categories.cfg", File::Read); - - if (!f) - critical (QObject::tr ("Failed to open primitive categories!")); - - if (f) - { PrimitiveCategory cat; - - for (str line : f) - { int colon; - - if (line.length() == 0 || line[0] == '#') - continue; - - if ( (colon = line.indexOf (":")) == -1) - { if (cat.regexes.size() > 0) - g_PrimitiveCategories << cat; - - cat.regexes.clear(); - cat.prims.clear(); - cat.setName (line); - } - else - { str cmd = line.left (colon); - PrimitiveCategory::Type type = PrimitiveCategory::Filename; - - if (cmd == "f") - type = PrimitiveCategory::Filename; - - elif (cmd == "t") - type = PrimitiveCategory::Title; - else - continue; - - QRegExp regex (line.mid (colon + 1)); - PrimitiveCategory::RegexEntry entry = { regex, type }; - cat.regexes << entry; - } - } - - if (cat.regexes.size() > 0) - g_PrimitiveCategories << cat; - } - - // Add a category for unmatched primitives - PrimitiveCategory cat; - cat.setName (g_Other); - g_PrimitiveCategories << cat; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool primitiveLoaderBusy() -{ return g_primListerMutex; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static double radialPoint (int i, int divs, double (*func) (double)) -{ return (*func) ( (i * 2 * pi) / divs); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines) -{ for (int i = 0; i < segs; ++i) - { double x0 = radius * radialPoint (i, divs, cos), - x1 = radius * radialPoint (i + 1, divs, cos), - z0 = radius * radialPoint (i, divs, sin), - z1 = radius * radialPoint (i + 1, divs, sin); - - lines << QLineF (QPointF (x0, z0), QPointF (x1, z1)); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QList<LDObject*> makePrimitive (PrimitiveType type, int segs, int divs, int num) -{ QList<LDObject*> objs; - QList<int> condLineSegs; - QList<QLineF> circle; - - makeCircle (segs, divs, 1, circle); - - for (int i = 0; i < segs; ++i) - { double x0 = circle[i].x1(), - x1 = circle[i].x2(), - z0 = circle[i].y1(), - z1 = circle[i].y2(); - - switch (type) - { case Circle: - { vertex v0 (x0, 0.0f, z0), - v1 (x1, 0.0f, z1); - - LDLine* line = new LDLine; - line->setVertex (0, v0); - line->setVertex (1, v1); - line->setColor (edgecolor); - objs << line; - } break; - - case Cylinder: - case Ring: - case Cone: - { double x2, x3, z2, z3; - double y0, y1, y2, y3; - - if (type == Cylinder) - { x2 = x1; - x3 = x0; - z2 = z1; - z3 = z0; - - y0 = y1 = 0.0f; - y2 = y3 = 1.0f; - } - else - { x2 = x1 * (num + 1); - x3 = x0 * (num + 1); - z2 = z1 * (num + 1); - z3 = z0 * (num + 1); - - x0 *= num; - x1 *= num; - z0 *= num; - z1 *= num; - - if (type == Ring) - y0 = y1 = y2 = y3 = 0.0f; - else - { y0 = y1 = 1.0f; - y2 = y3 = 0.0f; - } - } - - vertex v0 (x0, y0, z0), - v1 (x1, y1, z1), - v2 (x2, y2, z2), - v3 (x3, y3, z3); - - LDQuad* quad = new LDQuad; - quad->setColor (maincolor); - quad->setVertex (0, v0); - quad->setVertex (1, v1); - quad->setVertex (2, v2); - quad->setVertex (3, v3); - - if (type == Cylinder) - quad->invert(); - - objs << quad; - - if (type == Cylinder || type == Cone) - condLineSegs << i; - } break; - - case Disc: - case DiscNeg: - { double x2, z2; - - if (type == Disc) - x2 = z2 = 0.0f; - else - { x2 = (x0 >= 0.0f) ? 1.0f : -1.0f; - z2 = (z0 >= 0.0f) ? 1.0f : -1.0f; - } - - vertex v0 (x0, 0.0f, z0), - v1 (x1, 0.0f, z1), - v2 (x2, 0.0f, z2); - - // Disc negatives need to go the other way around, otherwise - // they'll end up upside-down. - LDTriangle* seg = new LDTriangle; - seg->setColor (maincolor); - seg->setVertex (type == Disc ? 0 : 2, v0); - seg->setVertex (1, v1); - seg->setVertex (type == Disc ? 2 : 0, v2); - objs << seg; - } break; - } - } - - // If this is not a full circle, we need a conditional line at the other - // end, too. - if (segs < divs && condLineSegs.size() != 0) - condLineSegs << segs; - -for (int i : condLineSegs) - { vertex v0 (radialPoint (i, divs, cos), 0.0f, radialPoint (i, divs, sin)), - v1, - v2 (radialPoint (i + 1, divs, cos), 0.0f, radialPoint (i + 1, divs, sin)), - v3 (radialPoint (i - 1, divs, cos), 0.0f, radialPoint (i - 1, divs, sin)); - - if (type == Cylinder) - v1 = vertex (v0[X], 1.0f, v0[Z]); - - elif (type == Cone) - { v1 = vertex (v0[X] * (num + 1), 0.0f, v0[Z] * (num + 1)); - v0[X] *= num; - v0[Y] = 1.0f; - v0[Z] *= num; - } - - LDCndLine* line = new LDCndLine; - line->setColor (edgecolor); - line->setVertex (0, v0); - line->setVertex (1, v1); - line->setVertex (2, v2); - line->setVertex (3, v3); - objs << line; - } - - return objs; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -static str primitiveTypeName (PrimitiveType type) -{ // Not translated as primitives are in English. - return type == Circle ? "Circle" : - type == Cylinder ? "Cylinder" : - type == Disc ? "Disc" : - type == DiscNeg ? "Disc Negative" : - type == Ring ? "Ring" : "Cone"; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str radialFileName (PrimitiveType type, int segs, int divs, int num) -{ int numer = segs, - denom = divs; - - // Simplify the fractional part, but the denominator must be at least 4. - simplify (numer, denom); - - if (denom < 4) - { const int factor = 4 / denom; - numer *= factor; - denom *= factor; - } - - // Compose some general information: prefix, fraction, root, ring number - str prefix = (divs == lores) ? "" : fmt ("%1/", divs); - str frac = fmt ("%1-%2", numer, denom); - str root = g_radialNameRoots[type]; - str numstr = (type == Ring || type == Cone) ? fmt ("%1", num) : ""; - - // Truncate the root if necessary (7-16rin4.dat for instance). - // However, always keep the root at least 2 characters. - int extra = (frac.length() + numstr.length() + root.length()) - 8; - root.chop (clamp (extra, 0, 2)); - - // Stick them all together and return the result. - return prefix + frac + root + numstr + ".dat"; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile* generatePrimitive (PrimitiveType type, int segs, int divs, int num) -{ // Make the description - str frac = str::number ((float) segs / divs); - str name = radialFileName (type, segs, divs, num); - str descr; - - // Ensure that there's decimals, even if they're 0. - if (frac.indexOf (".") == -1) - frac += ".0"; - - if (type == Ring || type == Cone) - { str spacing = - (num < 10) ? " " : - (num < 100) ? " " : ""; - - descr = fmt ("%1 %2%3 x %4", primitiveTypeName (type), spacing, num, frac); - } - else - descr = fmt ("%1 %2", primitiveTypeName (type), frac); - - // Prepend "Hi-Res" if 48/ primitive. - if (divs == hires) - descr.insert (0, "Hi-Res "); - - LDFile* f = new LDFile; - f->setDefaultName (name); - - f->addObjects ( - { new LDComment (descr), - new LDComment (fmt ("Name: %1", name)), - new LDComment (fmt ("Author: LDForge")), - new LDComment (fmt ("!LDRAW_ORG Unofficial_%1Primitive", divs == hires ? "48_" : "")), - new LDComment (CALicense), - new LDEmpty, - new LDBFC (LDBFC::CertifyCCW), - new LDEmpty, - }); - - f->addObjects (makePrimitive (type, segs, divs, num)); - return f; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDFile* getPrimitive (PrimitiveType type, int segs, int divs, int num) -{ str name = radialFileName (type, segs, divs, num); - LDFile* f = getFile (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; - - LDFile* f = generatePrimitive (type, segs, divs, num); - - g_win->save (f, false); - delete f; -}
--- a/src/primitives.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/primitives.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -19,71 +19,88 @@ #ifndef LDFORGE_PRIMITIVES_H #define LDFORGE_PRIMITIVES_H -#include "common.h" +#include "main.h" #include "types.h" #include <QRegExp> #include <QDialog> -class LDFile; +class LDDocument; class Ui_MakePrimUI; class PrimitiveCategory; struct Primitive -{ str name, title; +{ + QString name, title; PrimitiveCategory* cat; }; -class PrimitiveCategory -{ PROPERTY (str, name, setName) +class PrimitiveCategory : public QObject +{ + Q_OBJECT + PROPERTY (public, QString, Name, STR_OPS, STOCK_WRITE) public: - enum Type - { Filename, - Title + enum ERegexType + { + EFilenameRegex, + ETitleRegex }; struct RegexEntry - { QRegExp regex; - Type type; + { + QRegExp regex; + ERegexType type; }; QList<RegexEntry> regexes; QList<Primitive> prims; - static QList<Primitive> uncat; + + explicit PrimitiveCategory (QString name, QObject* parent = 0); + bool isValidToInclude(); + + static void loadCategories(); + static void populateCategories(); }; // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= -// PrimitiveLister +// PrimitiveScanner // // Worker object that scans the primitives folder for primitives and // builds an index of them. // ============================================================================= -class PrimitiveLister : public QObject -{ Q_OBJECT +class PrimitiveScanner : public QObject +{ + Q_OBJECT public: - static void start(); + explicit PrimitiveScanner (QObject* parent = 0); + virtual ~PrimitiveScanner(); + static void start(); public slots: - void work(); + void work(); signals: - void starting (int num); - void workDone(); - void update (int i); + void starting (int num); + void workDone(); + void update (int i); private: - QList<Primitive> m_prims; + QList<Primitive> m_prims; + QStringList m_files; + int m_i; + int m_baselen; }; -extern QList<PrimitiveCategory> g_PrimitiveCategories; +extern QList<PrimitiveCategory*> g_PrimitiveCategories; void loadPrimitives(); -bool primitiveLoaderBusy(); +PrimitiveScanner* getActivePrimitiveScanner(); enum PrimitiveType -{ Circle, +{ + Circle, Cylinder, Disc, DiscNeg, @@ -93,7 +110,8 @@ // ============================================================================= class PrimitivePrompt : public QDialog -{ Q_OBJECT +{ + Q_OBJECT public: explicit PrimitivePrompt (QWidget* parent = null, Qt::WindowFlags f = 0); @@ -105,12 +123,12 @@ }; void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines); -LDFile* generatePrimitive (PrimitiveType type, int segs, int divs, int num); +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. -LDFile* getPrimitive (PrimitiveType type, int segs, int divs, int num); +LDDocument* getPrimitive (PrimitiveType type, int segs, int divs, int num); -str radialFileName (PrimitiveType type, int segs, int divs, int num); +QString radialFileName (PrimitiveType type, int segs, int divs, int num); #endif // LDFORGE_PRIMITIVES_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/property.h Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/types.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +#include <QObject> +#include <QStringList> +#include <QTextStream> +#include <QFile> +#include <assert.h> +#include "main.h" +#include "types.h" +#include "misc.h" +#include "ldtypes.h" +#include "document.h" + +// ============================================================================= +// ----------------------------------------------------------------------------- +QString DoFormat (QList<StringFormatArg> 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<double> vals) +{ + assert (vals.size() == 9); + memcpy (&m_vals[0], & (*vals.begin()), sizeof m_vals); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void Matrix::puts() const +{ + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + log ("%1\t", m_vals[ (i * 3) + j]); + + log ("\n"); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +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<LDSubfile*> (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); +}
--- a/src/types.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,615 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QObject> -#include <QStringList> -#include <QTextStream> -#include <qfile.h> -#include <assert.h> -#include "common.h" -#include "types.h" -#include "misc.h" -#include "ldtypes.h" -#include "file.h" - -// ============================================================================= -// ----------------------------------------------------------------------------- -str DoFormat (QList<StringFormatArg> args) -{ assert (args.size() >= 1); - str text = args[0].value(); - - for (uchar i = 1; i < args.size(); ++i) - text = text.arg (args[i].value()); - - return text; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex::vertex (double x, double y, double z) -{ m_coords[X] = x; - m_coords[Y] = y; - m_coords[Z] = z; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void vertex::move (const vertex& other) -{ for (const Axis ax : g_Axes) - m_coords[ax] += other[ax]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex vertex::midpoint (const vertex& other) -{ vertex mid; - -for (const Axis ax : g_Axes) - mid[ax] = (m_coords[ax] + other[ax]) / 2; - - return mid; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str vertex::stringRep (bool mangled) const -{ str fmtstr = "%1 %2 %3"; - - if (mangled) - fmtstr = "(%1, %2, %3)"; - - return fmt (fmtstr, coord (X), coord (Y), coord (Z)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void vertex::transform (matrix matr, vertex pos) -{ double x2 = (matr[0] * x()) + (matr[1] * y()) + (matr[2] * z()) + pos[X]; - double y2 = (matr[3] * x()) + (matr[4] * y()) + (matr[5] * z()) + pos[Y]; - double z2 = (matr[6] * x()) + (matr[7] * y()) + (matr[8] * z()) + pos[Z]; - - x() = x2; - y() = y2; - z() = z2; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex vertex::operator-() const -{ return vertex (-m_coords[X], -m_coords[Y], -m_coords[Z]); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool vertex::operator!= (const vertex& other) const -{ return !operator== (other); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -double& vertex::operator[] (const Axis ax) -{ return coord ( (int) ax); -} - -const double& vertex::operator[] (const Axis ax) const -{ return coord ( (int) ax); -} - -double& vertex::operator[] (const int ax) -{ return coord (ax); -} - -const double& vertex::operator[] (const int ax) const -{ return coord (ax); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool vertex::operator== (const vertex& other) const -{ return coord (X) == other[X] && - coord (Y) == other[Y] && - coord (Z) == other[Z]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex& vertex::operator/= (const double d) -{ for (const Axis ax : g_Axes) - m_coords[ax] /= d; - - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex vertex::operator/ (const double d) const -{ vertex other (*this); - return other /= d; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex& vertex::operator+= (const vertex& other) -{ move (other); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -vertex vertex::operator+ (const vertex& other) const -{ vertex newvert (*this); - newvert.move (other); - return newvert; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int vertex::operator< (const vertex& other) const -{ if (operator== (other)) - return false; - - if (coord (X) < other[X]) - return true; - - if (coord (X) > other[X]) - return false; - - if (coord (Y) < other[Y]) - return true; - - if (coord (Y) > other[Y]) - return false; - - return coord (Z) < other[Z]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -matrix::matrix (double vals[]) -{ for (short i = 0; i < 9; ++i) - m_vals[i] = vals[i]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -matrix::matrix (double fillval) -{ for (short i = 0; i < 9; ++i) - m_vals[i] = fillval; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -matrix::matrix (initlist<double> vals) -{ assert (vals.size() == 9); - memcpy (&m_vals[0], & (*vals.begin()), sizeof m_vals); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void matrix::puts() const -{ for (short i = 0; i < 3; ++i) - { for (short j = 0; j < 3; ++j) - log ("%1\t", m_vals[ (i * 3) + j]); - - log ("\n"); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str matrix::stringRep() const -{ str val; - - for (short i = 0; i < 9; ++i) - { if (i > 0) - val += ' '; - - val += str::number (m_vals[i]); - } - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void matrix::zero() -{ memset (&m_vals[0], 0, sizeof m_vals); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -matrix matrix::mult (matrix other) const -{ matrix val; - val.zero(); - - for (short i = 0; i < 3; ++i) - for (short j = 0; j < 3; ++j) - for (short k = 0; k < 3; ++k) - val[ (i * 3) + j] += m_vals[ (i * 3) + k] * other[ (k * 3) + j]; - - return val; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -matrix& matrix::operator= (matrix other) -{ memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -double matrix::determinant() const -{ return (val (0) * val (4) * val (8)) + - (val (1) * val (5) * val (6)) + - (val (2) * val (3) * val (7)) - - (val (2) * val (4) * val (6)) - - (val (1) * val (3) * val (8)) - - (val (0) * val (5) * val (7)); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool matrix::operator== (const matrix& other) const -{ for (int i = 0; i < 9; ++i) - if (val (i) != other[i]) - return false; - - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -StringFormatArg::StringFormatArg (const str& v) -{ m_val = v; -} - -StringFormatArg::StringFormatArg (const char& v) -{ m_val = v; -} - -StringFormatArg::StringFormatArg (const uchar& v) -{ m_val = v; -} - -StringFormatArg::StringFormatArg (const qchar& v) -{ m_val = v; -} - -StringFormatArg::StringFormatArg (const float& v) -{ m_val = str::number (v); -} - -StringFormatArg::StringFormatArg (const double& v) -{ m_val = str::number (v); -} - -StringFormatArg::StringFormatArg (const vertex& v) -{ m_val = v.stringRep (false); -} - -StringFormatArg::StringFormatArg (const matrix& v) -{ m_val = v.stringRep(); -} - -StringFormatArg::StringFormatArg (const char* v) -{ m_val = v; -} - -StringFormatArg::StringFormatArg (const StringConfig& v) -{ m_val = v.value; -} - -StringFormatArg::StringFormatArg (const IntConfig& v) -{ m_val.number (v.value); -} - -StringFormatArg::StringFormatArg (const FloatConfig& v) -{ m_val.number (v.value); -} - -StringFormatArg::StringFormatArg (const void* v) -{ m_val.sprintf ("%p", v); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File::File() -{ // Make a null file - m_file = null; - m_textstream = null; -} - -File::File (str path, OpenType rtype) -{ m_file = null; - open (path, rtype); -} - -File::File (FILE* fp, OpenType rtype) -{ m_file = null; - open (fp, rtype); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File::~File() -{ if (m_file) - { m_file->close(); - delete m_file; - - if (m_textstream) - delete m_textstream; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool File::open (FILE* fp, OpenType rtype) -{ return open ("", rtype, fp); -} - -bool File::open (str path, OpenType rtype, FILE* fp) -{ close(); - - if (!m_file) - m_file = new QFile; - - m_file->setFileName (path); - - bool result; - - QIODevice::OpenMode mode = - (rtype == Read) ? QIODevice::ReadOnly : - (rtype == Write) ? QIODevice::WriteOnly : QIODevice::Append; - - if (fp) - result = m_file->open (fp, mode); - else - result = m_file->open (mode); - - if (result) - { m_textstream = new QTextStream (m_file); - return true; - } - - delete m_file; - m_file = null; - return false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File::iterator File::begin() -{ return iterator (this); -} - -File::iterator& File::end() -{ return m_endIterator; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void File::write (str msg) -{ m_file->write (msg.toUtf8(), msg.length()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool File::readLine (str& line) -{ if (!m_textstream || m_textstream->atEnd()) - return false; - - line = m_textstream->readLine(); - return true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool File::atEnd() const -{ assert (m_textstream != null); - return m_textstream->atEnd(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool File::isNull() const -{ return m_file == null; -} - -bool File::operator!() const -{ return isNull(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void File::close() -{ if (!m_file) - return; - - delete m_file; - m_file = null; - - if (m_textstream) - { delete m_textstream; - m_textstream = null; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool File::flush() -{ return m_file->flush(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File::operator bool() const -{ return !isNull(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void File::rewind() -{ m_file->seek (0); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -File::iterator::iterator (File* f) : m_file (f) -{ operator++(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void File::iterator::operator++() -{ m_gotdata = m_file->readLine (m_text); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -str File::iterator::operator*() -{ return m_text; -} - -// ============================================================================= -// The prime contestant for the weirdest operator== 2013 award? -// ----------------------------------------------------------------------------- -bool File::iterator::operator== (File::iterator& other) -{ return (other.m_file == null && !m_gotdata); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool File::iterator::operator!= (File::iterator& other) -{ return !operator== (other); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDBoundingBox::LDBoundingBox() -{ reset(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDBoundingBox::calculate() -{ reset(); - - if (!LDFile::current()) - return; - -for (LDObject * obj : LDFile::current()->objects()) - calcObject (obj); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDBoundingBox::calcObject (LDObject* obj) -{ switch (obj->getType()) - { case LDObject::Line: - case LDObject::Triangle: - case LDObject::Quad: - case LDObject::CndLine: - - for (short i = 0; i < obj->vertices(); ++i) - calcVertex (obj->getVertex (i)); - - break; - - case LDObject::Subfile: - { LDSubfile* ref = static_cast<LDSubfile*> (obj); - QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepCacheInline); - - for (LDObject * obj : objs) - { calcObject (obj); - delete obj; - } - } - break; - - default: - break; - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDBoundingBox& LDBoundingBox::operator<< (const vertex& v) -{ calcVertex (v); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDBoundingBox& LDBoundingBox::operator<< (LDObject* obj) -{ calcObject (obj); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDBoundingBox::calcVertex (const vertex& v) -{ for (const Axis ax : g_Axes) - { if (v[ax] < m_v0[ax]) - m_v0[ax] = v[ax]; - - if (v[ax] > m_v1[ax]) - m_v1[ax] = v[ax]; - } - - m_empty = false; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void LDBoundingBox::reset() -{ m_v0[X] = m_v0[Y] = m_v0[Z] = 0x7FFFFFFF; - m_v1[X] = m_v1[Y] = m_v1[Z] = 0xFFFFFFFF; - - m_empty = true; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -double LDBoundingBox::size() const -{ double xscale = (m_v0[X] - m_v1[X]); - double yscale = (m_v0[Y] - m_v1[Y]); - double zscale = (m_v0[Z] - m_v1[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_v0[X] + m_v1[X]) / 2, - (m_v0[Y] + m_v1[Y]) / 2, - (m_v0[Z] + m_v1[Z]) / 2); -}
--- a/src/types.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/types.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -16,42 +16,45 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TYPES_H -#define TYPES_H +#ifndef LDFORGE_TYPES_H +#define LDFORGE_TYPES_H #include <QString> #include <QObject> -#include <deque> -#include "common.h" +#include <QStringList> +#include <QMetaType> +#include "property.h" class LDObject; - -typedef QChar qchar; -typedef QString str; -class StringConfig; -class IntConfig; -class FloatConfig; class QFile; class QTextStream; -typedef unsigned int uint; -typedef unsigned short ushort; -typedef unsigned long ulong; -typedef qint8 int8; -typedef qint16 int16; -typedef qint32 int32; -typedef qint64 int64; -typedef quint8 uint8; -typedef quint16 uint16; -typedef quint32 uint32; -typedef quint64 uint64; +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<class T> +using initlist = std::initializer_list<T>; -template<class T> using initlist = std::initializer_list<T>; -template<class T, class R> using pair = std::pair<T, R>; -using std::size_t; +template<class T, class R> +using pair = std::pair<T, R>; -enum Axis { X, Y, Z }; -static const Axis g_Axes[3] = { X, Y, Z }; +enum Axis +{ + X, + Y, + Z +}; + +// ============================================================================= +// +class LDObject; +using LDObjectList = QList<LDObject*>; // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @@ -60,40 +63,47 @@ // // A mathematical 3 x 3 matrix // ============================================================================= -class matrix -{ public: - matrix() {} - matrix (initlist<double> vals); - matrix (double fillval); - matrix (double vals[]); +class Matrix +{ + public: + Matrix() {} + Matrix (initlist<double> vals); + Matrix (double fillval); + Matrix (double vals[]); - double determinant () const; - matrix mult (matrix other) const; - void puts () const; - str stringRep () const; - void zero (); - matrix& operator= (matrix other); + 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]; + { + return m_vals[idx]; } inline const double& val (int idx) const - { return m_vals[idx]; + { + return m_vals[idx]; } - inline matrix operator* (matrix other) const - { return mult (other); + 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); + { + return val (idx); } - bool operator== (const matrix& other) const; + inline const double& operator[] (int idx) const + { + return val (idx); + } + + bool operator== (const Matrix& other) const; private: double m_vals[9]; @@ -102,70 +112,98 @@ // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= -// vertex +// 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); +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 midpoint (const vertex& other); - void move (const vertex& other); - str stringRep (bool mangled) const; - void transform (matrix matr, 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& coord (int n) - { return m_coords[n]; + inline double& operator[] (const Axis ax) + { + return getCoordinate ((int) ax); } - inline const double& coord (int n) const - { return m_coords[n]; + inline const double& operator[] (const Axis ax) const + { + return getCoordinate ((int) ax); } - inline double& x () - { return m_coords[X]; - } - - inline const double& x () const - { return m_coords[X]; + inline double& operator[] (const int ax) + { + return getCoordinate (ax); } - inline double& y () - { return m_coords[Y]; + inline const double& operator[] (const int ax) const + { + return getCoordinate (ax); } - inline const double& y () const - { return m_coords[Y]; + inline double& getCoordinate (int n) + { + return m_coords[n]; } - inline double& z () - { return m_coords[Z]; + inline const double& getCoordinate (int n) const + { + return m_coords[n]; + } + + inline double& x() + { + return m_coords[X]; } - inline const double& z () const - { return m_coords[Z]; + inline const double& x() const + { + return m_coords[X]; + } + + inline double& y() + { + return m_coords[Y]; } - 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; - double& operator[] (const Axis ax); - const double& operator[] (const Axis ax) const; - double& operator[] (const int ax); - const double& operator[] (const int ax) const; + 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) + // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= @@ -175,118 +213,50 @@ // Used as the argument type to the formatting functions, hence its name. // ============================================================================= class StringFormatArg -{ public: - StringFormatArg (const str& v); - StringFormatArg (const char& v); - StringFormatArg (const uchar& v); - StringFormatArg (const qchar& v); - -#define NUMERIC_FORMAT_ARG(T,C) \ - StringFormatArg (const T& v) { \ - char valstr[32]; \ - sprintf (valstr, "%" #C, v); \ - m_val = valstr; \ - } +{ + 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) {} - NUMERIC_FORMAT_ARG (int, d) - NUMERIC_FORMAT_ARG (short, d) - NUMERIC_FORMAT_ARG (long, ld) - NUMERIC_FORMAT_ARG (uint, u) - NUMERIC_FORMAT_ARG (ushort, u) - NUMERIC_FORMAT_ARG (ulong, lu) + StringFormatArg (const void* a) + { + m_val.sprintf ("%p", a); + } - StringFormatArg (const float& v); - StringFormatArg (const double& v); - StringFormatArg (const vertex& v); - StringFormatArg (const matrix& v); - StringFormatArg (const char* v); - StringFormatArg (const StringConfig& v); - StringFormatArg (const IntConfig& v); - StringFormatArg (const FloatConfig& v); - StringFormatArg (const void* v); + template<class T> StringFormatArg (const QList<T>& a) + { + m_val = "{ "; - template<class T> StringFormatArg (const QList<T>& v) - { m_val = "{ "; - - int i = 0; - - for (const T& it : v) - { if (i++) + for (const T& it : a) + { + if (&it != &a.first()) m_val += ", "; StringFormatArg arg (it); m_val += arg.value(); } - if (i) + if (!a.isEmpty()) m_val += " "; m_val += "}"; } - str value() const - { return m_val; + inline QString value() const + { + return m_val; } - private: - str m_val; -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -// File -// -// A file interface with simple interface and support for range-for-loops. -// ============================================================================= -class File -{ private: - // Iterator class to enable range-for-loop support. Rough hack.. don't use directly! - class iterator - { public: - iterator() : m_file (null) {} // end iterator has m_file == null - iterator (File* f); - void operator++(); - str operator*(); - bool operator== (iterator& other); - bool operator!= (iterator& other); - - private: - File* m_file; - str m_text; - bool m_gotdata = false; - }; - - public: - enum OpenType - { Read, - Write, - Append - }; - - File(); - File (str path, File::OpenType rtype); - File (FILE* fp, File::OpenType rtype); - ~File(); - - bool atEnd() const; - iterator begin(); - void close(); - iterator& end(); - bool flush(); - bool isNull() const; - bool readLine (str& line); - void rewind(); - bool open (FILE* fp, OpenType rtype); - bool open (str path, OpenType rtype, FILE* fp = null); - void write (str msg); - - bool operator!() const; - operator bool() const; private: - QFile* m_file; - QTextStream* m_textstream; - iterator m_endIterator; + QString m_val; }; // ============================================================================= @@ -296,9 +266,10 @@ // v0 is the minimum vertex, v1 is the maximum vertex. // ============================================================================= class LDBoundingBox -{ READ_PROPERTY (bool, empty, setEmpty) - READ_PROPERTY (vertex, v0, setV0) - READ_PROPERTY (vertex, v1, setV1) +{ + 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(); @@ -306,19 +277,19 @@ void calculate(); double size() const; void calcObject (LDObject* obj); - void calcVertex (const vertex& v); - vertex center() const; + void calcVertex (const Vertex& v); + Vertex center() const; LDBoundingBox& operator<< (LDObject* obj); - LDBoundingBox& operator<< (const vertex& v); + LDBoundingBox& operator<< (const Vertex& v); }; // Formatter function -str DoFormat (QList<StringFormatArg> args); +QString DoFormat (QList<StringFormatArg> args); // printf replacement -void doPrint (File& f, initlist<StringFormatArg> args); -void doPrint (FILE* f, initlist<StringFormatArg> args); // heh +void doPrint (QFile& f, QList<StringFormatArg> args); +void doPrint (FILE* fp, QList<StringFormatArg> 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. @@ -330,16 +301,15 @@ # define fprint(F, ...) doPrint (F, {__VA_ARGS__}) # define log(...) DoLog({ __VA_ARGS__ }) #else -str fmt (const char* fmtstr, ...); -void fprint (File& f, const char* fmtstr, ...); +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 File g_file_stdout; -extern File g_file_stderr; -extern const vertex g_origin; // Vertex at (0, 0, 0) -extern const matrix g_identity; // Identity matrix +extern const Vertex g_origin; // Vertex at (0, 0, 0) +extern const Matrix g_identity; // Identity matrix static const double pi = 3.14159265358979323846; -#endif // TYPES_H +#endif // LDFORGE_TYPES_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets.cc Mon Jan 20 15:04:26 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 <http://www.gnu.org/licenses/>. + */ + +// I still find the radio group useful... find a way to use this in Designer. +// I probably need to look into how to make Designer plugins. +// TODO: try make this usable in Designer + +#include <QBoxLayout> +#include <QRadioButton> +#include <QButtonGroup> +#include <QCheckBox> +#include <map> + +#include "widgets.h" +#include "moc_widgets.cpp" + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup::RadioGroup (const QString& title, QWidget* parent) : QGroupBox (title, parent) +{ + init (Qt::Vertical); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false) +{ + return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +bool RadioGroup::isChecked (int n) const +{ + return m_buttonGroup->checkedId() == n; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::init (Qt::Orientation orient) +{ + m_vert = orient == Qt::Vertical; + + m_buttonGroup = new QButtonGroup; + m_oldId = m_curId = 0; + m_coreLayout = null; + + m_coreLayout = new QBoxLayout ( (orient == Qt::Vertical) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom); + setLayout (m_coreLayout); + + // Init the first row with a break + rowBreak(); + + connect (m_buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int))); + connect (m_buttonGroup, SIGNAL (buttonReleased (int)), this, SLOT (slot_buttonReleased (int))); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup::RadioGroup (const QString& title, initlist<char const*> entries, int const defaultId, const Qt::Orientation orient, QWidget* parent) : + QGroupBox (title, parent), + m_defId (defaultId) +{ + init (orient); + m_oldId = m_defId; + + for (const char* entry : entries) + addButton (entry); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::rowBreak() +{ + QBoxLayout* newLayout = new QBoxLayout (m_vert ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); + m_currentLayout = newLayout; + m_layouts << newLayout; + + m_coreLayout->addLayout (newLayout); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::addButton (const char* entry) +{ + QRadioButton* button = new QRadioButton (entry); + addButton (button); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::addButton (QRadioButton* button) +{ + bool const selectThis = (m_curId == m_defId); + + m_objects << button; + m_buttonGroup->addButton (button, m_curId++); + m_currentLayout->addWidget (button); + + if (selectThis) + button->setChecked (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup& RadioGroup::operator<< (QRadioButton* button) +{ + addButton (button); + return *this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup& RadioGroup::operator<< (const char* entry) +{ + addButton (entry); + return *this; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::setCurrentRow (int row) +{ + m_currentLayout = m_layouts[row]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +int RadioGroup::value() const +{ + return m_buttonGroup->checkedId(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::setValue (int val) +{ + m_buttonGroup->button (val)->setChecked (true); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +QRadioButton* RadioGroup::operator[] (int n) const +{ + return m_objects[n]; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::slot_buttonPressed (int btn) +{ + emit buttonPressed (btn); + + m_oldId = m_buttonGroup->checkedId(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void RadioGroup::slot_buttonReleased (int btn) +{ + emit buttonReleased (btn); + int newid = m_buttonGroup->checkedId(); + + if (m_oldId != newid) + emit valueChanged (newid); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup::Iterator RadioGroup::begin() +{ + return m_objects.begin(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +RadioGroup::Iterator RadioGroup::end() +{ + return m_objects.end(); +}
--- a/src/widgets.cpp Wed Oct 23 13:14:17 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,177 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -// I still find the radio group useful... find a way to use this in Designer. -// I probably need to look into how to make Designer plugins. -// TODO: try make this usable in Designer - -#include <QBoxLayout> -#include <QRadioButton> -#include <QButtonGroup> -#include <QCheckBox> -#include <map> - -#include "widgets.h" -#include "moc_widgets.cpp" - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup::RadioGroup (const QString& title, QWidget* parent) : QGroupBox (title, parent) -{ init (Qt::Vertical); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false) -{ return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool RadioGroup::isChecked (int n) const -{ return m_buttonGroup->checkedId() == n; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::init (Qt::Orientation orient) -{ m_vert = orient == Qt::Vertical; - - m_buttonGroup = new QButtonGroup; - m_oldId = m_curId = 0; - m_coreLayout = null; - - m_coreLayout = new QBoxLayout ( (orient == Qt::Vertical) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom); - setLayout (m_coreLayout); - - // Init the first row with a break - rowBreak(); - - connect (m_buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int))); - connect (m_buttonGroup, SIGNAL (buttonReleased (int)), this, SLOT (slot_buttonReleased (int))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup::RadioGroup (const QString& title, initlist<char const*> entries, int const defaultId, const Qt::Orientation orient, QWidget* parent) : - QGroupBox (title, parent), - m_defId (defaultId) -{ init (orient); - m_oldId = m_defId; - - for (const char* entry : entries) - addButton (entry); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::rowBreak() -{ QBoxLayout* newLayout = new QBoxLayout (m_vert ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); - m_currentLayout = newLayout; - m_layouts << newLayout; - - m_coreLayout->addLayout (newLayout); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::addButton (const char* entry) -{ QRadioButton* button = new QRadioButton (entry); - addButton (button); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::addButton (QRadioButton* button) -{ bool const selectThis = (m_curId == m_defId); - - m_objects << button; - m_buttonGroup->addButton (button, m_curId++); - m_currentLayout->addWidget (button); - - if (selectThis) - button->setChecked (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup& RadioGroup::operator<< (QRadioButton* button) -{ addButton (button); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup& RadioGroup::operator<< (const char* entry) -{ addButton (entry); - return *this; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::setCurrentRow (int row) -{ m_currentLayout = m_layouts[row]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -int RadioGroup::value() const -{ return m_buttonGroup->checkedId(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::setValue (int val) -{ m_buttonGroup->button (val)->setChecked (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QRadioButton* RadioGroup::operator[] (int n) const -{ return m_objects[n]; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::slot_buttonPressed (int btn) -{ emit buttonPressed (btn); - - m_oldId = m_buttonGroup->checkedId(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void RadioGroup::slot_buttonReleased (int btn) -{ emit buttonReleased (btn); - int newid = m_buttonGroup->checkedId(); - - if (m_oldId != newid) - emit valueChanged (newid); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup::Iterator RadioGroup::begin() -{ return m_objects.begin(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -RadioGroup::Iterator RadioGroup::end() -{ return m_objects.end(); -}
--- a/src/widgets.h Wed Oct 23 13:14:17 2013 +0300 +++ b/src/widgets.h Mon Jan 20 15:04:26 2014 +0200 @@ -1,6 +1,6 @@ /* * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo + * 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 @@ -22,7 +22,7 @@ #include <QGroupBox> #include <QSpinBox> #include <map> -#include "common.h" +#include "main.h" #include "types.h" class QIcon; @@ -37,17 +37,20 @@ // Convenience widget - is a groupbox of radio buttons. // ============================================================================= class RadioGroup : public QGroupBox -{ Q_OBJECT +{ + Q_OBJECT public: typedef QList<QRadioButton*>::Iterator Iterator; explicit RadioGroup() - { init (Qt::Vertical); + { + init (Qt::Vertical); } explicit RadioGroup (QWidget* parent = null) : QGroupBox (parent) - { init (Qt::Vertical); + { + init (Qt::Vertical); } explicit RadioGroup (const QString& title, QWidget* parent = null);
--- a/ui/about.ui Wed Oct 23 13:14:17 2013 +0300 +++ b/ui/about.ui Mon Jan 20 15:04:26 2014 +0200 @@ -38,7 +38,7 @@ <string/> </property> <property name="pixmap"> - <pixmap resource="../../ldforge.qrc">:/icons/ldforge.png</pixmap> + <pixmap resource="../ldforge.qrc">:/icons/ldforge.png</pixmap> </property> <property name="scaledContents"> <bool>false</bool> @@ -64,7 +64,7 @@ <item> <widget class="QLabel" name="label_2"> <property name="text"> - <string>Copyright (C) 2013 Santeri Piippo</string> + <string>Copyright (C) 2013, 2014 Santeri Piippo</string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -116,6 +116,7 @@ </layout> </widget> <resources> + <include location="../ldforge.qrc"/> <include location="../../ldforge.qrc"/> </resources> <connections>
--- a/ui/config.ui Wed Oct 23 13:14:17 2013 +0300 +++ b/ui/config.ui Mon Jan 20 15:04:26 2014 +0200 @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>717</width> - <height>328</height> + <height>359</height> </rect> </property> <property name="windowTitle"> @@ -51,7 +51,7 @@ </item> <item> <property name="text"> - <string>Quick colors</string> + <string>Color Toolbar</string> </property> </item> <item> @@ -146,6 +146,27 @@ </widget> </item> <item row="2" column="0"> + <widget class="QLabel" name="selectedColorLabel"> + <property name="text"> + <string>Selected Color</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QPushButton" name="selColorButton"> + <property name="whatsThis"> + <string>This color is used for the main color.</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../ldforge.qrc"> + <normaloff>:/icons/colorselect.png</normaloff>:/icons/colorselect.png</iconset> + </property> + </widget> + </item> + <item row="3" column="0"> <widget class="QLabel" name="label_2"> <property name="whatsThis"> <string/> @@ -155,7 +176,7 @@ </property> </widget> </item> - <item row="2" column="1"> + <item row="3" column="1"> <widget class="QPushButton" name="backgroundColorButton"> <property name="whatsThis"> <string>This is the background color for the viewport.</string> @@ -267,6 +288,13 @@ </widget> </item> <item> + <widget class="QCheckBox" name="linelengths"> + <property name="text"> + <string>Display line lenghts when drawing</string> + </property> + </widget> + </item> + <item> <widget class="QCheckBox" name="implicitFiles"> <property name="text"> <string>List implicitly loaded files</string> @@ -458,7 +486,7 @@ Usually this contains MainColor, EdgeColor and some auxiliary colors used to group objects.</string> </property> <property name="title"> - <string>Quick colors</string> + <string>Color Toolbar</string> </property> <layout class="QHBoxLayout" name="horizontalLayout_6"> <item>
--- a/ui/ldforge.ui Wed Oct 23 13:14:17 2013 +0300 +++ b/ui/ldforge.ui Mon Jan 20 15:04:26 2014 +0200 @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>900</width> - <height>600</height> + <width>1010</width> + <height>641</height> </rect> </property> <property name="windowTitle"> @@ -69,7 +69,7 @@ <rect> <x>0</x> <y>0</y> - <width>900</width> + <width>1010</width> <height>24</height> </rect> </property> @@ -116,6 +116,7 @@ <addaction name="actionAxes"/> <addaction name="actionWireframe"/> <addaction name="actionBFCView"/> + <addaction name="actionDrawAngles"/> <addaction name="separator"/> <addaction name="actionSetOverlay"/> <addaction name="actionClearOverlay"/> @@ -365,6 +366,7 @@ <addaction name="actionAxes"/> <addaction name="actionWireframe"/> <addaction name="actionBFCView"/> + <addaction name="actionDrawAngles"/> </widget> <widget class="QToolBar" name="toolBar_7"> <property name="windowTitle"> @@ -416,6 +418,23 @@ <bool>false</bool> </attribute> </widget> + <widget class="QToolBar" name="toolBar_9"> + <property name="windowTitle"> + <string>toolBar_9</string> + </property> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionYtruder"/> + <addaction name="actionRectifier"/> + <addaction name="actionIntersector"/> + <addaction name="actionIsecalc"/> + <addaction name="actionCoverer"/> + <addaction name="actionEdger2"/> + </widget> <action name="actionNew"> <property name="icon"> <iconset resource="../ldforge.qrc"> @@ -1323,6 +1342,26 @@ <string>Reveals objects. Undoes hiding.</string> </property> </action> + <action name="actionSubfileSelection"> + <property name="text"> + <string>Subfile Selection</string> + </property> + </action> + <action name="actionDrawAngles"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="icon"> + <iconset resource="../ldforge.qrc"> + <normaloff>:/icons/mode-angle.png</normaloff>:/icons/mode-angle.png</iconset> + </property> + <property name="text"> + <string>Draw Angles</string> + </property> + <property name="toolTip"> + <string>Draw angle information when drawing lines</string> + </property> + </action> </widget> <resources> <include location="../ldforge.qrc"/>