Tue, 03 Mar 2015 16:55:36 +0200
- renamed .cc files to .cpp
--- a/CMakeLists.txt Sat Feb 21 20:23:34 2015 +0200 +++ b/CMakeLists.txt Tue Mar 03 16:55:36 2015 +0200 @@ -28,41 +28,41 @@ include_directories (${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR}) set (LDFORGE_SOURCES - src/actions.cc - src/actionsEdit.cc - src/addObjectDialog.cc - src/basics.cc - src/colors.cc - src/colorSelector.cc - src/configuration.cc - src/configDialog.cc - src/crashCatcher.cc - src/dialogs.cc - src/documentation.cc - src/editHistory.cc - src/extPrograms.cc - src/glRenderer.cc - src/glCompiler.cc - src/ldConfig.cc - src/ldDocument.cc - src/ldObject.cc - src/ldObjectMath.cpp - src/main.cc - src/mainWindow.cc - src/messageLog.cc - src/miscallenous.cc - src/partDownloader.cc - src/primitives.cc - src/radioGroup.cc - src/ringFinder.cc - src/version.cc - src/editmodes/abstractEditMode.cc - src/editmodes/circleMode.cc - src/editmodes/drawMode.cc - src/editmodes/linePathMode.cpp - src/editmodes/magicWandMode.cc - src/editmodes/rectangleMode.cc - src/editmodes/selectMode.cc + src/actions.cpp + src/actionsEdit.cpp + src/addObjectDialog.cpp + src/basics.cpp + src/colors.cpp + src/colorSelector.cpp + src/configuration.cpp + src/configDialog.cpp + src/crashCatcher.cpp + src/dialogs.cpp + src/documentation.cpp + src/editHistory.cpp + src/extPrograms.cpp + src/glRenderer.cpp + src/glCompiler.cpp + src/ldConfig.cpp + src/ldDocument.cpp + src/ldObject.cpp + src/ldObjectMath.cpp + src/main.cpp + src/mainWindow.cpp + src/messageLog.cpp + src/miscallenous.cpp + src/partDownloader.cpp + src/primitives.cpp + src/radioGroup.cpp + src/ringFinder.cpp + src/version.cpp + src/editmodes/abstractEditMode.cpp + src/editmodes/circleMode.cpp + src/editmodes/drawMode.cpp + src/editmodes/linePathMode.cpp + src/editmodes/magicWandMode.cpp + src/editmodes/rectangleMode.cpp + src/editmodes/selectMode.cpp ) set (LDFORGE_HEADERS @@ -194,4 +194,4 @@ COMMAND python "${CMAKE_SOURCE_DIR}/updaterevision.py" "${CMAKE_CURRENT_BINARY_DIR}/hginfo.h") -add_dependencies (${PROJECT_NAME} make_hginfo_h) \ No newline at end of file +add_dependencies (${PROJECT_NAME} make_hginfo_h)
--- a/src/actions.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,911 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QFileDialog> -#include <QMessageBox> -#include <QTextEdit> -#include <QBoxLayout> -#include <QDialogButtonBox> -#include <QPushButton> -#include <QInputDialog> - -#include "mainWindow.h" -#include "ldDocument.h" -#include "editHistory.h" -#include "configDialog.h" -#include "addObjectDialog.h" -#include "miscallenous.h" -#include "glRenderer.h" -#include "dialogs.h" -#include "primitives.h" -#include "radioGroup.h" -#include "colors.h" -#include "glCompiler.h" -#include "ui_newpart.h" -#include "editmodes/abstractEditMode.h" - -EXTERN_CFGENTRY (Bool, DrawWireframe) -EXTERN_CFGENTRY (Bool, BFCRedGreenView) -EXTERN_CFGENTRY (String, DefaultName) -EXTERN_CFGENTRY (String, DefaultUser) -EXTERN_CFGENTRY (Bool, UseCALicense) -EXTERN_CFGENTRY (Bool, DrawAngles) -EXTERN_CFGENTRY (Bool, RandomColors) -EXTERN_CFGENTRY (Bool, DrawSurfaces) -EXTERN_CFGENTRY (Bool, DrawEdgeLines) -EXTERN_CFGENTRY (Bool, DrawConditionalLines) -EXTERN_CFGENTRY (Bool, DrawAxes) - -// ============================================================================= -// -void MainWindow::actionNew() -{ - QDialog* dlg = new QDialog (g_win); - Ui::NewPartUI ui; - ui.setupUi (dlg); - - QString authortext = cfg::DefaultName; - - if (not cfg::DefaultUser.isEmpty()) - authortext.append (format (" [%1]", cfg::DefaultUser)); - - ui.le_author->setText (authortext); - ui.caLicense->setChecked (cfg::UseCALicense); - - if (dlg->exec() == QDialog::Rejected) - return; - - newFile(); - - BFCStatement const bfctype = ui.rb_bfc_ccw->isChecked() ? BFCStatement::CertifyCCW - : ui.rb_bfc_cw->isChecked() ? BFCStatement::CertifyCW - : BFCStatement::NoCertify; - QString const license = ui.caLicense->isChecked() ? CALicenseText : ""; - - LDObjectList objs; - objs << LDSpawn<LDComment> (ui.le_title->text()); - objs << LDSpawn<LDComment> ("Name: <untitled>.dat"); - objs << LDSpawn<LDComment> (format ("Author: %1", ui.le_author->text())); - objs << LDSpawn<LDComment> (format ("!LDRAW_ORG Unofficial_Part")); - - if (not license.isEmpty()) - objs << LDSpawn<LDComment> (license); - - objs << LDSpawn<LDEmpty>(); - objs << LDSpawn<LDBFC> (bfctype); - objs << LDSpawn<LDEmpty>(); - CurrentDocument()->addObjects (objs); - doFullRefresh(); -} - -// ============================================================================= -// -void MainWindow::actionNewFile() -{ - newFile(); -} - -// ============================================================================= -// -void MainWindow::actionOpen() -{ - QString name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)"); - - if (name.isEmpty()) - return; - - OpenMainModel (name); -} - -// ============================================================================= -// -void MainWindow::actionSave() -{ - save (CurrentDocument(), false); -} - -// ============================================================================= -// -void MainWindow::actionSaveAs() -{ - save (CurrentDocument(), true); -} - -// ============================================================================= -// -void MainWindow::actionSaveAll() -{ - for (LDDocumentPtr file : LDDocument::explicitDocuments()) - save (file, false); -} - -// ============================================================================= -// -void MainWindow::actionClose() -{ - if (not CurrentDocument()->isSafeToClose()) - return; - - CurrentDocument()->dismiss(); -} - -// ============================================================================= -// -void MainWindow::actionCloseAll() -{ - if (not IsSafeToCloseAll()) - return; - - CloseAllDocuments(); -} - -// ============================================================================= -// -void MainWindow::actionSettings() -{ - (new ConfigDialog)->exec(); -} - -// ============================================================================= -// -void MainWindow::actionSetLDrawPath() -{ - (new LDrawPathDialog (true))->exec(); -} - -// ============================================================================= -// -void MainWindow::actionExit() -{ - Exit(); -} - -// ============================================================================= -// -void MainWindow::actionNewSubfile() -{ - AddObjectDialog::staticDialog (OBJ_Subfile, LDObjectPtr()); -} - -// ============================================================================= -// -void MainWindow::actionNewLine() -{ - AddObjectDialog::staticDialog (OBJ_Line, LDObjectPtr()); -} - -// ============================================================================= -// -void MainWindow::actionNewTriangle() -{ - AddObjectDialog::staticDialog (OBJ_Triangle, LDObjectPtr()); -} - -// ============================================================================= -// -void MainWindow::actionNewQuad() -{ - AddObjectDialog::staticDialog (OBJ_Quad, LDObjectPtr()); -} - -// ============================================================================= -// -void MainWindow::actionNewCLine() -{ - AddObjectDialog::staticDialog (OBJ_CondLine, LDObjectPtr()); -} - -// ============================================================================= -// -void MainWindow::actionNewComment() -{ - AddObjectDialog::staticDialog (OBJ_Comment, LDObjectPtr()); -} - -// ============================================================================= -// -void MainWindow::actionNewBFC() -{ - AddObjectDialog::staticDialog (OBJ_BFC, LDObjectPtr()); -} - -// ============================================================================= -// -void MainWindow::actionEdit() -{ - if (Selection().size() != 1) - return; - - LDObjectPtr obj = Selection() [0]; - AddObjectDialog::staticDialog (obj->type(), obj); -} - -// ============================================================================= -// -void MainWindow::actionHelp() -{ -} - -// ============================================================================= -// -void MainWindow::actionAbout() -{ - AboutDialog().exec(); -} - -// ============================================================================= -// -void MainWindow::actionAboutQt() -{ - QMessageBox::aboutQt (g_win); -} - -// ============================================================================= -// -void MainWindow::actionSelectAll() -{ - for (LDObjectPtr obj : CurrentDocument()->objects()) - obj->select(); -} - -// ============================================================================= -// -void MainWindow::actionSelectByColor() -{ - if (Selection().isEmpty()) - return; - - QList<LDColor> colors; - - for (LDObjectPtr obj : Selection()) - { - if (obj->isColored()) - colors << obj->color(); - } - - RemoveDuplicates (colors); - CurrentDocument()->clearSelection(); - - for (LDObjectPtr obj : CurrentDocument()->objects()) - { - if (colors.contains (obj->color())) - obj->select(); - } -} - -// ============================================================================= -// -void MainWindow::actionSelectByType() -{ - if (Selection().isEmpty()) - return; - - QList<LDObjectType> types; - QStringList subfilenames; - - for (LDObjectPtr obj : Selection()) - { - types << obj->type(); - - if (types.last() == OBJ_Subfile) - subfilenames << obj.staticCast<LDSubfile>()->fileInfo()->name(); - } - - RemoveDuplicates (types); - RemoveDuplicates (subfilenames); - CurrentDocument()->clearSelection(); - - for (LDObjectPtr obj : CurrentDocument()->objects()) - { - LDObjectType type = obj->type(); - - if (not types.contains (type)) - continue; - - // For subfiles, type check is not enough, we check the name of the document as well. - if (type == OBJ_Subfile and not subfilenames.contains (obj.staticCast<LDSubfile>()->fileInfo()->name())) - continue; - - obj->select(); - } -} - -// ============================================================================= -// -void MainWindow::actionGridCoarse() -{ - cfg::Grid = Grid::Coarse; - updateGridToolBar(); -} - -void MainWindow::actionGridMedium() -{ - cfg::Grid = Grid::Medium; - updateGridToolBar(); -} - -void MainWindow::actionGridFine() -{ - cfg::Grid = Grid::Fine; - updateGridToolBar(); -} - -// ============================================================================= -// -void MainWindow::actionResetView() -{ - R()->resetAngles(); - R()->update(); -} - -// ============================================================================= -// -void MainWindow::actionInsertFrom() -{ - QString fname = QFileDialog::getOpenFileName(); - int idx = getInsertionPoint(); - - if (not fname.length()) - return; - - QFile f (fname); - - if (not f.open (QIODevice::ReadOnly)) - { - Critical (format ("Couldn't open %1 (%2)", fname, f.errorString())); - return; - } - - LDObjectList objs = LoadFileContents (&f, null); - - CurrentDocument()->clearSelection(); - - for (LDObjectPtr obj : objs) - { - CurrentDocument()->insertObj (idx, obj); - obj->select(); - R()->compileObject (obj); - - idx++; - } - - refresh(); - scrollToSelection(); -} - -// ============================================================================= -// -void MainWindow::actionExportTo() -{ - if (Selection().isEmpty()) - return; - - QString fname = QFileDialog::getSaveFileName(); - - if (fname.length() == 0) - return; - - QFile file (fname); - - if (not file.open (QIODevice::WriteOnly | QIODevice::Text)) - { - Critical (format ("Unable to open %1 for writing (%2)", fname, file.errorString())); - return; - } - - for (LDObjectPtr obj : Selection()) - { - QString contents = obj->asText(); - QByteArray data = contents.toUtf8(); - file.write (data, data.size()); - file.write ("\r\n", 2); - } -} - -// ============================================================================= -// -void MainWindow::actionInsertRaw() -{ - 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() == QDialog::Rejected) - return; - - CurrentDocument()->clearSelection(); - - for (QString line : QString (te_edit->toPlainText()).split ("\n")) - { - LDObjectPtr obj = ParseLine (line); - - CurrentDocument()->insertObj (idx, obj); - obj->select(); - idx++; - } - - refresh(); - scrollToSelection(); -} - -// ============================================================================= -// -void MainWindow::actionScreenshot() -{ - setlocale (LC_ALL, "C"); - - int w, h; - uchar* imgdata = R()->getScreencap (w, h); - QImage img = GetImageFromScreencap (imgdata, w, h); - - QString root = Basename (CurrentDocument()->name()); - - if (root.right (4) == ".dat") - root.chop (4); - - QString defaultname = (root.length() > 0) ? format ("%1.png", root) : ""; - QString fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname, - "PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)"); - - if (not fname.isEmpty() and not img.save (fname)) - Critical (format ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno))); - - delete[] imgdata; -} - -// ============================================================================= -// -void MainWindow::actionAxes() -{ - cfg::DrawAxes = not cfg::DrawAxes; - updateActions(); - R()->update(); -} - -// ============================================================================= -// -void MainWindow::actionVisibilityToggle() -{ - for (LDObjectPtr obj : Selection()) - obj->setHidden (not obj->isHidden()); - - refresh(); -} - -// ============================================================================= -// -void MainWindow::actionVisibilityHide() -{ - for (LDObjectPtr obj : Selection()) - obj->setHidden (true); - - refresh(); -} - -// ============================================================================= -// -void MainWindow::actionVisibilityReveal() -{ - for (LDObjectPtr obj : Selection()) - obj->setHidden (false); - refresh(); -} - -// ============================================================================= -// -void MainWindow::actionWireframe() -{ - cfg::DrawWireframe = not cfg::DrawWireframe; - R()->refresh(); -} - -// ============================================================================= -// -void MainWindow::actionSetOverlay() -{ - OverlayDialog dlg; - - if (not dlg.exec()) - return; - - R()->setupOverlay ((ECamera) dlg.camera(), dlg.fpath(), dlg.ofsx(), - dlg.ofsy(), dlg.lwidth(), dlg.lheight()); -} - -// ============================================================================= -// -void MainWindow::actionClearOverlay() -{ - R()->clearOverlay(); -} - -// ============================================================================= -// -void MainWindow::actionModeSelect() -{ - R()->setEditMode (EditModeType::Select); -} - -// ============================================================================= -// -void MainWindow::actionModeDraw() -{ - R()->setEditMode (EditModeType::Draw); -} - -// ============================================================================= -// -void MainWindow::actionModeRectangle() -{ - R()->setEditMode (EditModeType::Rectangle); -} - -// ============================================================================= -// -void MainWindow::actionModeCircle() -{ - R()->setEditMode (EditModeType::Circle); -} - -// ============================================================================= -// -void MainWindow::actionModeMagicWand() -{ - R()->setEditMode (EditModeType::MagicWand); -} - -void MainWindow::actionModeLinePath() -{ - R()->setEditMode (EditModeType::LinePath); -} - -// ============================================================================= -// -void MainWindow::actionDrawAngles() -{ - cfg::DrawAngles = not cfg::DrawAngles; - R()->refresh(); -} - -// ============================================================================= -// -void MainWindow::actionSetDrawDepth() -{ - if (R()->camera() == EFreeCamera) - return; - - bool ok; - double depth = QInputDialog::getDouble (g_win, "Set Draw Depth", - format ("Depth value for %1 Camera:", R()->getCameraName()), - R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok); - - if (ok) - R()->setDepthValue (depth); -} - -#if 0 -// This is a test to draw a dummy axle. Meant to be used as a primitive gallery, -// but I can't figure how to generate these pictures properly. Multi-threading -// these is an immense pain. -void MainWindow::actiontestpic() -{ - LDDocumentPtr file = getFile ("axle.dat"); - setlocale (LC_ALL, "C"); - - if (not 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 - -// ============================================================================= -// -void MainWindow::actionScanPrimitives() -{ - PrimitiveScanner::start(); -} - -// ============================================================================= -// -void MainWindow::actionBFCView() -{ - cfg::BFCRedGreenView = not cfg::BFCRedGreenView; - - if (cfg::BFCRedGreenView) - cfg::RandomColors = false; - - updateActions(); - R()->refresh(); -} - -// ============================================================================= -// -void MainWindow::actionJumpTo() -{ - bool ok; - int defval = 0; - LDObjectPtr obj; - - if (Selection().size() == 1) - defval = Selection()[0]->lineNumber(); - - int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval, - 1, CurrentDocument()->getObjectCount(), 1, &ok); - - if (not ok or (obj = CurrentDocument()->getObject (idx - 1)) == null) - return; - - CurrentDocument()->clearSelection(); - obj->select(); - updateSelection(); -} - -// ============================================================================= -// -void MainWindow::actionSubfileSelection() -{ - if (Selection().size() == 0) - return; - - QString parentpath (CurrentDocument()->fullPath()); - - // BFC type of the new subfile - it shall inherit the BFC type of the parent document - BFCStatement bfctype (BFCStatement::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 - LDCommentPtr titleobj (CurrentDocument()->getObject (0).dynamicCast<LDComment>()); - - // License text for the subfile - QString license (PreferredLicenseText()); - - // LDraw code body of the new subfile (i.e. code of the selection) - QStringList code; - - // Full path of the subfile to be - QString fullsubname; - - // Where to insert the subfile reference? - int refidx (Selection()[0]->lineNumber()); - - // Determine title of subfile - if (titleobj != null) - subtitle = "~" + titleobj->text(); - else - subtitle = "~subfile"; - - // Remove duplicate tildes - while (subtitle.startsWith ("~~")) - 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 (CurrentDocument()->fullPath())); - - if (topdirname != "s") - { - QString desiredPath = subdirname + "/s"; - QString title = tr ("Create subfile directory?"); - QString text = format (tr ("The directory <b>%1</b> is suggested for " - "subfiles. This directory does not exist, create it?"), desiredPath); - - if (QDir (desiredPath).exists() or Confirm (title, text)) - { - subdirname = desiredPath; - QDir().mkpath (subdirname); - } - else - return; - } - - // Determine the body of the name of the subfile - if (not parentpath.isEmpty()) - { - // Chop existing '.dat' suffix - 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; - - // Now find the appropriate filename. Increase the number of the subfile - // until we find a name which isn't already taken. - 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 or QFile (fullsubname).exists()); - } - - // Determine the BFC winding type used in the main document - it is to - // be carried over to the subfile. - LDIterate<LDBFC> (CurrentDocument()->objects(), [&] (LDBFCPtr const& bfc) - { - if (Eq (bfc->statement(), BFCStatement::CertifyCCW, BFCStatement::CertifyCW, - BFCStatement::NoCertify)) - { - bfctype = bfc->statement(); - Break(); - } - }); - - // Get the body of the document in LDraw code - for (LDObjectPtr obj : Selection()) - code << obj->asText(); - - // Create the new subfile document - LDDocumentPtr doc = LDDocument::createNew(); - doc->setImplicit (false); - doc->setFullPath (fullsubname); - doc->setName (LDDocument::shortenName (fullsubname)); - - LDObjectList objs; - objs << LDSpawn<LDComment> (subtitle); - objs << LDSpawn<LDComment> ("Name: "); // This gets filled in when the subfile is saved - objs << LDSpawn<LDComment> (format ("Author: %1 [%2]", cfg::DefaultName, cfg::DefaultUser)); - objs << LDSpawn<LDComment> ("!LDRAW_ORG Unofficial_Subpart"); - - if (not license.isEmpty()) - objs << LDSpawn<LDComment> (license); - - objs << LDSpawn<LDEmpty>(); - objs << LDSpawn<LDBFC> (bfctype); - objs << LDSpawn<LDEmpty>(); - - doc->addObjects (objs); - - // Add the actual subfile code to the new document - for (QString line : code) - { - LDObjectPtr obj = ParseLine (line); - doc->addObject (obj); - } - - // Try save it - if (save (doc, true)) - { - // Save was successful. Delete the original selection now from the - // main document. - for (LDObjectPtr obj : Selection()) - obj->destroy(); - - // Add a reference to the new subfile to where the selection was - LDSubfilePtr ref (LDSpawn<LDSubfile>()); - ref->setColor (MainColor()); - ref->setFileInfo (doc); - ref->setPosition (Origin); - ref->setTransform (IdentityMatrix); - CurrentDocument()->insertObj (refidx, ref); - - // Refresh stuff - updateDocumentList(); - doFullRefresh(); - } - else - { - // Failed to save. - doc->dismiss(); - } -} - -void MainWindow::actionRandomColors() -{ - cfg::RandomColors = not cfg::RandomColors; - - if (cfg::RandomColors) - cfg::BFCRedGreenView = false; - - updateActions(); - R()->refresh(); -} - -void MainWindow::actionOpenSubfiles() -{ - for (LDObjectPtr obj : Selection()) - { - LDSubfilePtr ref = obj.dynamicCast<LDSubfile>(); - - if (ref == null or not ref->fileInfo()->isImplicit()) - continue; - - ref->fileInfo()->setImplicit (false); - } -} - -void MainWindow::actionDrawSurfaces() -{ - cfg::DrawSurfaces = not cfg::DrawSurfaces; - updateActions(); - update(); -} - -void MainWindow::actionDrawEdgeLines() -{ - cfg::DrawEdgeLines = not cfg::DrawEdgeLines; - updateActions(); - update(); -} - -void MainWindow::actionDrawConditionalLines() -{ - cfg::DrawConditionalLines = not cfg::DrawConditionalLines; - updateActions(); - update(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/actions.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,911 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QFileDialog> +#include <QMessageBox> +#include <QTextEdit> +#include <QBoxLayout> +#include <QDialogButtonBox> +#include <QPushButton> +#include <QInputDialog> + +#include "mainWindow.h" +#include "ldDocument.h" +#include "editHistory.h" +#include "configDialog.h" +#include "addObjectDialog.h" +#include "miscallenous.h" +#include "glRenderer.h" +#include "dialogs.h" +#include "primitives.h" +#include "radioGroup.h" +#include "colors.h" +#include "glCompiler.h" +#include "ui_newpart.h" +#include "editmodes/abstractEditMode.h" + +EXTERN_CFGENTRY (Bool, DrawWireframe) +EXTERN_CFGENTRY (Bool, BFCRedGreenView) +EXTERN_CFGENTRY (String, DefaultName) +EXTERN_CFGENTRY (String, DefaultUser) +EXTERN_CFGENTRY (Bool, UseCALicense) +EXTERN_CFGENTRY (Bool, DrawAngles) +EXTERN_CFGENTRY (Bool, RandomColors) +EXTERN_CFGENTRY (Bool, DrawSurfaces) +EXTERN_CFGENTRY (Bool, DrawEdgeLines) +EXTERN_CFGENTRY (Bool, DrawConditionalLines) +EXTERN_CFGENTRY (Bool, DrawAxes) + +// ============================================================================= +// +void MainWindow::actionNew() +{ + QDialog* dlg = new QDialog (g_win); + Ui::NewPartUI ui; + ui.setupUi (dlg); + + QString authortext = cfg::DefaultName; + + if (not cfg::DefaultUser.isEmpty()) + authortext.append (format (" [%1]", cfg::DefaultUser)); + + ui.le_author->setText (authortext); + ui.caLicense->setChecked (cfg::UseCALicense); + + if (dlg->exec() == QDialog::Rejected) + return; + + newFile(); + + BFCStatement const bfctype = ui.rb_bfc_ccw->isChecked() ? BFCStatement::CertifyCCW + : ui.rb_bfc_cw->isChecked() ? BFCStatement::CertifyCW + : BFCStatement::NoCertify; + QString const license = ui.caLicense->isChecked() ? CALicenseText : ""; + + LDObjectList objs; + objs << LDSpawn<LDComment> (ui.le_title->text()); + objs << LDSpawn<LDComment> ("Name: <untitled>.dat"); + objs << LDSpawn<LDComment> (format ("Author: %1", ui.le_author->text())); + objs << LDSpawn<LDComment> (format ("!LDRAW_ORG Unofficial_Part")); + + if (not license.isEmpty()) + objs << LDSpawn<LDComment> (license); + + objs << LDSpawn<LDEmpty>(); + objs << LDSpawn<LDBFC> (bfctype); + objs << LDSpawn<LDEmpty>(); + CurrentDocument()->addObjects (objs); + doFullRefresh(); +} + +// ============================================================================= +// +void MainWindow::actionNewFile() +{ + newFile(); +} + +// ============================================================================= +// +void MainWindow::actionOpen() +{ + QString name = QFileDialog::getOpenFileName (g_win, "Open File", "", "LDraw files (*.dat *.ldr)"); + + if (name.isEmpty()) + return; + + OpenMainModel (name); +} + +// ============================================================================= +// +void MainWindow::actionSave() +{ + save (CurrentDocument(), false); +} + +// ============================================================================= +// +void MainWindow::actionSaveAs() +{ + save (CurrentDocument(), true); +} + +// ============================================================================= +// +void MainWindow::actionSaveAll() +{ + for (LDDocumentPtr file : LDDocument::explicitDocuments()) + save (file, false); +} + +// ============================================================================= +// +void MainWindow::actionClose() +{ + if (not CurrentDocument()->isSafeToClose()) + return; + + CurrentDocument()->dismiss(); +} + +// ============================================================================= +// +void MainWindow::actionCloseAll() +{ + if (not IsSafeToCloseAll()) + return; + + CloseAllDocuments(); +} + +// ============================================================================= +// +void MainWindow::actionSettings() +{ + (new ConfigDialog)->exec(); +} + +// ============================================================================= +// +void MainWindow::actionSetLDrawPath() +{ + (new LDrawPathDialog (true))->exec(); +} + +// ============================================================================= +// +void MainWindow::actionExit() +{ + Exit(); +} + +// ============================================================================= +// +void MainWindow::actionNewSubfile() +{ + AddObjectDialog::staticDialog (OBJ_Subfile, LDObjectPtr()); +} + +// ============================================================================= +// +void MainWindow::actionNewLine() +{ + AddObjectDialog::staticDialog (OBJ_Line, LDObjectPtr()); +} + +// ============================================================================= +// +void MainWindow::actionNewTriangle() +{ + AddObjectDialog::staticDialog (OBJ_Triangle, LDObjectPtr()); +} + +// ============================================================================= +// +void MainWindow::actionNewQuad() +{ + AddObjectDialog::staticDialog (OBJ_Quad, LDObjectPtr()); +} + +// ============================================================================= +// +void MainWindow::actionNewCLine() +{ + AddObjectDialog::staticDialog (OBJ_CondLine, LDObjectPtr()); +} + +// ============================================================================= +// +void MainWindow::actionNewComment() +{ + AddObjectDialog::staticDialog (OBJ_Comment, LDObjectPtr()); +} + +// ============================================================================= +// +void MainWindow::actionNewBFC() +{ + AddObjectDialog::staticDialog (OBJ_BFC, LDObjectPtr()); +} + +// ============================================================================= +// +void MainWindow::actionEdit() +{ + if (Selection().size() != 1) + return; + + LDObjectPtr obj = Selection() [0]; + AddObjectDialog::staticDialog (obj->type(), obj); +} + +// ============================================================================= +// +void MainWindow::actionHelp() +{ +} + +// ============================================================================= +// +void MainWindow::actionAbout() +{ + AboutDialog().exec(); +} + +// ============================================================================= +// +void MainWindow::actionAboutQt() +{ + QMessageBox::aboutQt (g_win); +} + +// ============================================================================= +// +void MainWindow::actionSelectAll() +{ + for (LDObjectPtr obj : CurrentDocument()->objects()) + obj->select(); +} + +// ============================================================================= +// +void MainWindow::actionSelectByColor() +{ + if (Selection().isEmpty()) + return; + + QList<LDColor> colors; + + for (LDObjectPtr obj : Selection()) + { + if (obj->isColored()) + colors << obj->color(); + } + + RemoveDuplicates (colors); + CurrentDocument()->clearSelection(); + + for (LDObjectPtr obj : CurrentDocument()->objects()) + { + if (colors.contains (obj->color())) + obj->select(); + } +} + +// ============================================================================= +// +void MainWindow::actionSelectByType() +{ + if (Selection().isEmpty()) + return; + + QList<LDObjectType> types; + QStringList subfilenames; + + for (LDObjectPtr obj : Selection()) + { + types << obj->type(); + + if (types.last() == OBJ_Subfile) + subfilenames << obj.staticCast<LDSubfile>()->fileInfo()->name(); + } + + RemoveDuplicates (types); + RemoveDuplicates (subfilenames); + CurrentDocument()->clearSelection(); + + for (LDObjectPtr obj : CurrentDocument()->objects()) + { + LDObjectType type = obj->type(); + + if (not types.contains (type)) + continue; + + // For subfiles, type check is not enough, we check the name of the document as well. + if (type == OBJ_Subfile and not subfilenames.contains (obj.staticCast<LDSubfile>()->fileInfo()->name())) + continue; + + obj->select(); + } +} + +// ============================================================================= +// +void MainWindow::actionGridCoarse() +{ + cfg::Grid = Grid::Coarse; + updateGridToolBar(); +} + +void MainWindow::actionGridMedium() +{ + cfg::Grid = Grid::Medium; + updateGridToolBar(); +} + +void MainWindow::actionGridFine() +{ + cfg::Grid = Grid::Fine; + updateGridToolBar(); +} + +// ============================================================================= +// +void MainWindow::actionResetView() +{ + R()->resetAngles(); + R()->update(); +} + +// ============================================================================= +// +void MainWindow::actionInsertFrom() +{ + QString fname = QFileDialog::getOpenFileName(); + int idx = getInsertionPoint(); + + if (not fname.length()) + return; + + QFile f (fname); + + if (not f.open (QIODevice::ReadOnly)) + { + Critical (format ("Couldn't open %1 (%2)", fname, f.errorString())); + return; + } + + LDObjectList objs = LoadFileContents (&f, null); + + CurrentDocument()->clearSelection(); + + for (LDObjectPtr obj : objs) + { + CurrentDocument()->insertObj (idx, obj); + obj->select(); + R()->compileObject (obj); + + idx++; + } + + refresh(); + scrollToSelection(); +} + +// ============================================================================= +// +void MainWindow::actionExportTo() +{ + if (Selection().isEmpty()) + return; + + QString fname = QFileDialog::getSaveFileName(); + + if (fname.length() == 0) + return; + + QFile file (fname); + + if (not file.open (QIODevice::WriteOnly | QIODevice::Text)) + { + Critical (format ("Unable to open %1 for writing (%2)", fname, file.errorString())); + return; + } + + for (LDObjectPtr obj : Selection()) + { + QString contents = obj->asText(); + QByteArray data = contents.toUtf8(); + file.write (data, data.size()); + file.write ("\r\n", 2); + } +} + +// ============================================================================= +// +void MainWindow::actionInsertRaw() +{ + 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() == QDialog::Rejected) + return; + + CurrentDocument()->clearSelection(); + + for (QString line : QString (te_edit->toPlainText()).split ("\n")) + { + LDObjectPtr obj = ParseLine (line); + + CurrentDocument()->insertObj (idx, obj); + obj->select(); + idx++; + } + + refresh(); + scrollToSelection(); +} + +// ============================================================================= +// +void MainWindow::actionScreenshot() +{ + setlocale (LC_ALL, "C"); + + int w, h; + uchar* imgdata = R()->getScreencap (w, h); + QImage img = GetImageFromScreencap (imgdata, w, h); + + QString root = Basename (CurrentDocument()->name()); + + if (root.right (4) == ".dat") + root.chop (4); + + QString defaultname = (root.length() > 0) ? format ("%1.png", root) : ""; + QString fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname, + "PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)"); + + if (not fname.isEmpty() and not img.save (fname)) + Critical (format ("Couldn't open %1 for writing to save screencap: %2", fname, strerror (errno))); + + delete[] imgdata; +} + +// ============================================================================= +// +void MainWindow::actionAxes() +{ + cfg::DrawAxes = not cfg::DrawAxes; + updateActions(); + R()->update(); +} + +// ============================================================================= +// +void MainWindow::actionVisibilityToggle() +{ + for (LDObjectPtr obj : Selection()) + obj->setHidden (not obj->isHidden()); + + refresh(); +} + +// ============================================================================= +// +void MainWindow::actionVisibilityHide() +{ + for (LDObjectPtr obj : Selection()) + obj->setHidden (true); + + refresh(); +} + +// ============================================================================= +// +void MainWindow::actionVisibilityReveal() +{ + for (LDObjectPtr obj : Selection()) + obj->setHidden (false); + refresh(); +} + +// ============================================================================= +// +void MainWindow::actionWireframe() +{ + cfg::DrawWireframe = not cfg::DrawWireframe; + R()->refresh(); +} + +// ============================================================================= +// +void MainWindow::actionSetOverlay() +{ + OverlayDialog dlg; + + if (not dlg.exec()) + return; + + R()->setupOverlay ((ECamera) dlg.camera(), dlg.fpath(), dlg.ofsx(), + dlg.ofsy(), dlg.lwidth(), dlg.lheight()); +} + +// ============================================================================= +// +void MainWindow::actionClearOverlay() +{ + R()->clearOverlay(); +} + +// ============================================================================= +// +void MainWindow::actionModeSelect() +{ + R()->setEditMode (EditModeType::Select); +} + +// ============================================================================= +// +void MainWindow::actionModeDraw() +{ + R()->setEditMode (EditModeType::Draw); +} + +// ============================================================================= +// +void MainWindow::actionModeRectangle() +{ + R()->setEditMode (EditModeType::Rectangle); +} + +// ============================================================================= +// +void MainWindow::actionModeCircle() +{ + R()->setEditMode (EditModeType::Circle); +} + +// ============================================================================= +// +void MainWindow::actionModeMagicWand() +{ + R()->setEditMode (EditModeType::MagicWand); +} + +void MainWindow::actionModeLinePath() +{ + R()->setEditMode (EditModeType::LinePath); +} + +// ============================================================================= +// +void MainWindow::actionDrawAngles() +{ + cfg::DrawAngles = not cfg::DrawAngles; + R()->refresh(); +} + +// ============================================================================= +// +void MainWindow::actionSetDrawDepth() +{ + if (R()->camera() == EFreeCamera) + return; + + bool ok; + double depth = QInputDialog::getDouble (g_win, "Set Draw Depth", + format ("Depth value for %1 Camera:", R()->getCameraName()), + R()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok); + + if (ok) + R()->setDepthValue (depth); +} + +#if 0 +// This is a test to draw a dummy axle. Meant to be used as a primitive gallery, +// but I can't figure how to generate these pictures properly. Multi-threading +// these is an immense pain. +void MainWindow::actiontestpic() +{ + LDDocumentPtr file = getFile ("axle.dat"); + setlocale (LC_ALL, "C"); + + if (not 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 + +// ============================================================================= +// +void MainWindow::actionScanPrimitives() +{ + PrimitiveScanner::start(); +} + +// ============================================================================= +// +void MainWindow::actionBFCView() +{ + cfg::BFCRedGreenView = not cfg::BFCRedGreenView; + + if (cfg::BFCRedGreenView) + cfg::RandomColors = false; + + updateActions(); + R()->refresh(); +} + +// ============================================================================= +// +void MainWindow::actionJumpTo() +{ + bool ok; + int defval = 0; + LDObjectPtr obj; + + if (Selection().size() == 1) + defval = Selection()[0]->lineNumber(); + + int idx = QInputDialog::getInt (null, "Go to line", "Go to line:", defval, + 1, CurrentDocument()->getObjectCount(), 1, &ok); + + if (not ok or (obj = CurrentDocument()->getObject (idx - 1)) == null) + return; + + CurrentDocument()->clearSelection(); + obj->select(); + updateSelection(); +} + +// ============================================================================= +// +void MainWindow::actionSubfileSelection() +{ + if (Selection().size() == 0) + return; + + QString parentpath (CurrentDocument()->fullPath()); + + // BFC type of the new subfile - it shall inherit the BFC type of the parent document + BFCStatement bfctype (BFCStatement::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 + LDCommentPtr titleobj (CurrentDocument()->getObject (0).dynamicCast<LDComment>()); + + // License text for the subfile + QString license (PreferredLicenseText()); + + // LDraw code body of the new subfile (i.e. code of the selection) + QStringList code; + + // Full path of the subfile to be + QString fullsubname; + + // Where to insert the subfile reference? + int refidx (Selection()[0]->lineNumber()); + + // Determine title of subfile + if (titleobj != null) + subtitle = "~" + titleobj->text(); + else + subtitle = "~subfile"; + + // Remove duplicate tildes + while (subtitle.startsWith ("~~")) + 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 (CurrentDocument()->fullPath())); + + if (topdirname != "s") + { + QString desiredPath = subdirname + "/s"; + QString title = tr ("Create subfile directory?"); + QString text = format (tr ("The directory <b>%1</b> is suggested for " + "subfiles. This directory does not exist, create it?"), desiredPath); + + if (QDir (desiredPath).exists() or Confirm (title, text)) + { + subdirname = desiredPath; + QDir().mkpath (subdirname); + } + else + return; + } + + // Determine the body of the name of the subfile + if (not parentpath.isEmpty()) + { + // Chop existing '.dat' suffix + 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; + + // Now find the appropriate filename. Increase the number of the subfile + // until we find a name which isn't already taken. + 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 or QFile (fullsubname).exists()); + } + + // Determine the BFC winding type used in the main document - it is to + // be carried over to the subfile. + LDIterate<LDBFC> (CurrentDocument()->objects(), [&] (LDBFCPtr const& bfc) + { + if (Eq (bfc->statement(), BFCStatement::CertifyCCW, BFCStatement::CertifyCW, + BFCStatement::NoCertify)) + { + bfctype = bfc->statement(); + Break(); + } + }); + + // Get the body of the document in LDraw code + for (LDObjectPtr obj : Selection()) + code << obj->asText(); + + // Create the new subfile document + LDDocumentPtr doc = LDDocument::createNew(); + doc->setImplicit (false); + doc->setFullPath (fullsubname); + doc->setName (LDDocument::shortenName (fullsubname)); + + LDObjectList objs; + objs << LDSpawn<LDComment> (subtitle); + objs << LDSpawn<LDComment> ("Name: "); // This gets filled in when the subfile is saved + objs << LDSpawn<LDComment> (format ("Author: %1 [%2]", cfg::DefaultName, cfg::DefaultUser)); + objs << LDSpawn<LDComment> ("!LDRAW_ORG Unofficial_Subpart"); + + if (not license.isEmpty()) + objs << LDSpawn<LDComment> (license); + + objs << LDSpawn<LDEmpty>(); + objs << LDSpawn<LDBFC> (bfctype); + objs << LDSpawn<LDEmpty>(); + + doc->addObjects (objs); + + // Add the actual subfile code to the new document + for (QString line : code) + { + LDObjectPtr obj = ParseLine (line); + doc->addObject (obj); + } + + // Try save it + if (save (doc, true)) + { + // Save was successful. Delete the original selection now from the + // main document. + for (LDObjectPtr obj : Selection()) + obj->destroy(); + + // Add a reference to the new subfile to where the selection was + LDSubfilePtr ref (LDSpawn<LDSubfile>()); + ref->setColor (MainColor()); + ref->setFileInfo (doc); + ref->setPosition (Origin); + ref->setTransform (IdentityMatrix); + CurrentDocument()->insertObj (refidx, ref); + + // Refresh stuff + updateDocumentList(); + doFullRefresh(); + } + else + { + // Failed to save. + doc->dismiss(); + } +} + +void MainWindow::actionRandomColors() +{ + cfg::RandomColors = not cfg::RandomColors; + + if (cfg::RandomColors) + cfg::BFCRedGreenView = false; + + updateActions(); + R()->refresh(); +} + +void MainWindow::actionOpenSubfiles() +{ + for (LDObjectPtr obj : Selection()) + { + LDSubfilePtr ref = obj.dynamicCast<LDSubfile>(); + + if (ref == null or not ref->fileInfo()->isImplicit()) + continue; + + ref->fileInfo()->setImplicit (false); + } +} + +void MainWindow::actionDrawSurfaces() +{ + cfg::DrawSurfaces = not cfg::DrawSurfaces; + updateActions(); + update(); +} + +void MainWindow::actionDrawEdgeLines() +{ + cfg::DrawEdgeLines = not cfg::DrawEdgeLines; + updateActions(); + update(); +} + +void MainWindow::actionDrawConditionalLines() +{ + cfg::DrawConditionalLines = not cfg::DrawConditionalLines; + updateActions(); + update(); +}
--- a/src/actionsEdit.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,738 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <limits> -#include <QSpinBox> -#include <QCheckBox> -#include <QBoxLayout> -#include <QClipboard> -#include <QInputDialog> -#include "mainWindow.h" -#include "main.h" -#include "ldDocument.h" -#include "colorSelector.h" -#include "miscallenous.h" -#include "radioGroup.h" -#include "glRenderer.h" -#include "dialogs.h" -#include "colors.h" -#include "ldObjectMath.h" -#include "ui_replcoords.h" -#include "ui_editraw.h" -#include "ui_flip.h" -#include "ui_addhistoryline.h" - -EXTERN_CFGENTRY (String, DefaultUser) - -CFGENTRY (Int, RoundPosition, 3) -CFGENTRY (Int, RoundMatrix, 4) -CFGENTRY (Int, SplitLinesSegments, 5) - -// ============================================================================= -// -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 (LDObjectPtr obj : objs) - { - if (not data.isEmpty()) - data += "\n"; - - data += obj->asText(); - ++num; - } - - qApp->clipboard()->setText (data); - return num; -} - -// ============================================================================= -// -void MainWindow::actionCut() -{ - int num = CopyToClipboard(); - deleteSelection(); - print (tr ("%1 objects cut"), num); -} - -// ============================================================================= -// -void MainWindow::actionCopy() -{ - int num = CopyToClipboard(); - print (tr ("%1 objects copied"), num); -} - -// ============================================================================= -// -void MainWindow::actionPaste() -{ - const QString clipboardText = qApp->clipboard()->text(); - int idx = getInsertionPoint(); - CurrentDocument()->clearSelection(); - int num = 0; - - for (QString line : clipboardText.split ("\n")) - { - LDObjectPtr pasted = ParseLine (line); - CurrentDocument()->insertObj (idx++, pasted); - pasted->select(); - ++num; - } - - print (tr ("%1 objects pasted"), num); - refresh(); - scrollToSelection(); -} - -// ============================================================================= -// -void MainWindow::actionDelete() -{ - int num = deleteSelection(); - print (tr ("%1 objects deleted"), num); -} - -// ============================================================================= -// -static void DoInline (bool deep) -{ - LDObjectList sel = Selection(); - - LDIterate<LDSubfile> (Selection(), [&](LDSubfilePtr const& ref) - { - // Get the index of the subfile so we know where to insert the - // inlined contents. - long idx = ref->lineNumber(); - - assert (idx != -1); - LDObjectList objs = ref->inlineContents (deep, false); - - // Merge in the inlined objects - for (LDObjectPtr inlineobj : objs) - { - QString line = inlineobj->asText(); - inlineobj->destroy(); - LDObjectPtr newobj = ParseLine (line); - CurrentDocument()->insertObj (idx++, newobj); - newobj->select(); - } - - // Delete the subfile now as it's been inlined. - ref->destroy(); - }); - - g_win->refresh(); -} - -void MainWindow::actionInline() -{ - DoInline (false); -} - -void MainWindow::actionInlineDeep() -{ - DoInline (true); -} - -// ============================================================================= -// -void MainWindow::actionSplitQuads() -{ - int num = 0; - - LDIterate<LDQuad> (Selection(), [&](LDQuadPtr const& quad) - { - // Find the index of this quad - long index = quad->lineNumber(); - - if (index == -1) - return; - - QList<LDTrianglePtr> triangles = quad->splitToTriangles(); - - // Replace the quad with the first triangle and add the second triangle - // after the first one. - CurrentDocument()->setObject (index, triangles[0]); - CurrentDocument()->insertObj (index + 1, triangles[1]); - num++; - }); - - print ("%1 quadrilaterals split", num); - refresh(); -} - -// ============================================================================= -// -void MainWindow::actionEditRaw() -{ - if (Selection().size() != 1) - return; - - LDObjectPtr obj = Selection()[0]; - QDialog* dlg = new QDialog; - Ui::EditRawUI ui; - - ui.setupUi (dlg); - ui.code->setText (obj->asText()); - - if (obj->type() == OBJ_Error) - ui.errorDescription->setText (obj.staticCast<LDError>()->reason()); - else - { - ui.errorDescription->hide(); - ui.errorIcon->hide(); - } - - if (dlg->exec() == QDialog::Rejected) - return; - - // Reinterpret it from the text of the input field - LDObjectPtr newobj = ParseLine (ui.code->text()); - obj->replace (newobj); - refresh(); -} - -// ============================================================================= -// -void MainWindow::actionSetColor() -{ - if (Selection().isEmpty()) - return; - - LDObjectList objs = Selection(); - - // If all selected objects have the same color, said color is our default - // value to the color selection dialog. - LDColor color; - LDColor defaultcol = getSelectedColor(); - - // Show the dialog to the user now and ask for a color. - if (ColorSelector::selectColor (color, defaultcol, g_win)) - { - for (LDObjectPtr obj : objs) - { - if (obj->isColored()) - obj->setColor (color); - } - - refresh(); - } -} - -// ============================================================================= -// -void MainWindow::actionBorders() -{ - LDObjectList objs = Selection(); - int num = 0; - - for (LDObjectPtr obj : objs) - { - const LDObjectType type = obj->type(); - - if (type != OBJ_Quad and type != OBJ_Triangle) - continue; - - LDLinePtr lines[4]; - - if (type == OBJ_Quad) - { - LDQuadPtr quad = obj.staticCast<LDQuad>(); - lines[0] = LDSpawn<LDLine> (quad->vertex (0), quad->vertex (1)); - lines[1] = LDSpawn<LDLine> (quad->vertex (1), quad->vertex (2)); - lines[2] = LDSpawn<LDLine> (quad->vertex (2), quad->vertex (3)); - lines[3] = LDSpawn<LDLine> (quad->vertex (3), quad->vertex (0)); - } - else - { - LDTrianglePtr tri = obj.staticCast<LDTriangle>(); - lines[0] = LDSpawn<LDLine> (tri->vertex (0), tri->vertex (1)); - lines[1] = LDSpawn<LDLine> (tri->vertex (1), tri->vertex (2)); - lines[2] = LDSpawn<LDLine> (tri->vertex (2), tri->vertex (0)); - } - - for (int i = 0; i < countof (lines); ++i) - { - if (lines[i] == null) - continue; - - long idx = obj->lineNumber() + i + 1; - CurrentDocument()->insertObj (idx, lines[i]); - } - - num += countof (lines); - } - - print (tr ("Added %1 border lines"), num); - refresh(); -} - -// ============================================================================= -// -static void MoveSelection (const bool up) -{ - LDObjectList objs = Selection(); - LDObject::moveObjects (objs, up); - g_win->buildObjList(); -} - -// ============================================================================= -// -void MainWindow::actionMoveUp() -{ - MoveSelection (true); -} - -void MainWindow::actionMoveDown() -{ - MoveSelection (false); -} - -// ============================================================================= -// -void MainWindow::actionUndo() -{ - CurrentDocument()->undo(); -} - -void MainWindow::actionRedo() -{ - CurrentDocument()->redo(); -} - -// ============================================================================= -// -static void MoveObjects (Vertex vect) -{ - // Apply the grid values - vect *= *CurrentGrid().coordinateSnap; - - for (LDObjectPtr obj : Selection()) - obj->move (vect); - - g_win->refresh(); -} - -// ============================================================================= -// -void MainWindow::actionMoveXNeg() -{ - MoveObjects ({-1, 0, 0}); -} - -void MainWindow::actionMoveYNeg() -{ - MoveObjects ({0, -1, 0}); -} - -void MainWindow::actionMoveZNeg() -{ - MoveObjects ({0, 0, -1}); -} - -void MainWindow::actionMoveXPos() -{ - MoveObjects ({1, 0, 0}); -} - -void MainWindow::actionMoveYPos() -{ - MoveObjects ({0, 1, 0}); -} - -void MainWindow::actionMoveZPos() -{ - MoveObjects ({0, 0, 1}); -} - -// ============================================================================= -// -void MainWindow::actionInvert() -{ - for (LDObjectPtr obj : Selection()) - obj->invert(); - - refresh(); -} - -// ============================================================================= -// -static double GetRotateActionAngle() -{ - return (Pi * *CurrentGrid().angleSnap) / 180; -} - -void MainWindow::actionRotateXPos() -{ - RotateObjects (1, 0, 0, GetRotateActionAngle(), Selection()); -} -void MainWindow::actionRotateYPos() -{ - RotateObjects (0, 1, 0, GetRotateActionAngle(), Selection()); -} -void MainWindow::actionRotateZPos() -{ - RotateObjects (0, 0, 1, GetRotateActionAngle(), Selection()); -} -void MainWindow::actionRotateXNeg() -{ - RotateObjects (-1, 0, 0, GetRotateActionAngle(), Selection()); -} -void MainWindow::actionRotateYNeg() -{ - RotateObjects (0, -1, 0, GetRotateActionAngle(), Selection()); -} -void MainWindow::actionRotateZNeg() -{ - RotateObjects (0, 0, -1, GetRotateActionAngle(), Selection()); -} - -void MainWindow::actionRotationPoint() -{ - ConfigureRotationPoint(); -} - -// ============================================================================= -// -void MainWindow::actionRoundCoordinates() -{ - setlocale (LC_ALL, "C"); - int num = 0; - - for (LDObjectPtr obj : Selection()) - { - LDMatrixObjectPtr mo = obj.dynamicCast<LDMatrixObject>(); - - if (mo != null) - { - Vertex v = mo->position(); - Matrix t = mo->transform(); - - // Note: matrix values are to be rounded to 4 decimals. - v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); }); - ApplyToMatrix (t, [](int, double& a) { RoundToDecimals (a, cfg::RoundMatrix); }); - - mo->setPosition (v); - mo->setTransform (t); - num += 12; - } - else - { - for (int i = 0; i < obj->numVertices(); ++i) - { - Vertex v = obj->vertex (i); - v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); }); - obj->setVertex (i, v); - num += 3; - } - } - } - - print (tr ("Rounded %1 values"), num); - refreshObjectList(); - refresh(); -} - -// ============================================================================= -// -void MainWindow::actionUncolor() -{ - int num = 0; - - for (LDObjectPtr obj : Selection()) - { - if (not obj->isColored()) - continue; - - obj->setColor (obj->defaultColor()); - num++; - } - - print (tr ("%1 objects uncolored"), num); - refresh(); -} - -// ============================================================================= -// -void MainWindow::actionReplaceCoords() -{ - QDialog* dlg = new QDialog (g_win); - Ui::ReplaceCoordsUI ui; - ui.setupUi (dlg); - - if (not 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 (LDObjectPtr obj : Selection()) - { - for (int i = 0; i < obj->numVertices(); ++i) - { - Vertex v = obj->vertex (i); - - v.apply ([&](Axis ax, double& coord) - { - if (not sel.contains (ax) or - (not any and coord != search)) - { - return; - } - - if (not rel) - coord = 0; - - coord += replacement; - num++; - }); - - obj->setVertex (i, v); - } - } - - print (tr ("Altered %1 values"), num); - refresh(); -} - -// ============================================================================= -// -void MainWindow::actionFlip() -{ - QDialog* dlg = new QDialog; - Ui::FlipUI ui; - ui.setupUi (dlg); - - if (not 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 (LDObjectPtr obj : Selection()) - { - for (int i = 0; i < obj->numVertices(); ++i) - { - Vertex v = obj->vertex (i); - - v.apply ([&](Axis ax, double& a) - { - if (sel.contains (ax)) - a = -a; - }); - - obj->setVertex (i, v); - } - } - - refresh(); -} - -// ============================================================================= -// -void MainWindow::actionDemote() -{ - int num = 0; - - LDIterate<LDCondLine> (Selection(), [&](LDCondLinePtr const& cnd) - { - cnd->toEdgeLine(); - ++num; - }); - - print (tr ("Demoted %1 conditional lines"), num); - refresh(); -} - -// ============================================================================= -// -static bool IsColorUsed (LDColor color) -{ - for (LDObjectPtr obj : CurrentDocument()->objects()) - { - if (obj->isColored() and obj->color() == color) - return true; - } - - return false; -} - -// ============================================================================= -// -void MainWindow::actionAutocolor() -{ - int colnum = 0; - LDColor color; - - for (colnum = 0; - colnum < CountLDConfigColors() and - ((color = LDColor::fromIndex (colnum)) == null or - IsColorUsed (color)); - colnum++) {} - - if (colnum >= CountLDConfigColors()) - { - print (tr ("Cannot auto-color: all colors are in use!")); - return; - } - - for (LDObjectPtr obj : Selection()) - { - if (not obj->isColored()) - continue; - - obj->setColor (color); - } - - print (tr ("Auto-colored: new color is [%1] %2"), colnum, color.name()); - refresh(); -} - -// ============================================================================= -// -void MainWindow::actionAddHistoryLine() -{ - LDObjectPtr obj; - bool ishistory = false, - prevIsHistory = false; - - QDialog* dlg = new QDialog; - Ui_AddHistoryLine* ui = new Ui_AddHistoryLine; - ui->setupUi (dlg); - ui->m_username->setText (cfg::DefaultUser); - ui->m_date->setDate (QDate::currentDate()); - ui->m_comment->setFocus(); - - if (not dlg->exec()) - return; - - // Create the comment object based on input - QString commentText = format ("!HISTORY %1 [%2] %3", - ui->m_date->date().toString ("yyyy-MM-dd"), - ui->m_username->text(), - ui->m_comment->text()); - - LDCommentPtr comm (LDSpawn<LDComment> (commentText)); - - // Find a spot to place the new comment - for (obj = CurrentDocument()->getObject (0); - obj != null and obj->next() != null and not obj->next()->isScemantic(); - obj = obj->next()) - { - LDCommentPtr comm = obj.dynamicCast<LDComment>(); - - if (comm != null and comm->text().startsWith ("!HISTORY ")) - ishistory = true; - - if (prevIsHistory and not ishistory) - { - // Last line was history, this isn't, thus insert the new history - // line here. - break; - } - - prevIsHistory = ishistory; - } - - int idx = obj ? obj->lineNumber() : 0; - CurrentDocument()->insertObj (idx++, comm); - - // If we're adding a history line right before a scemantic object, pad it - // an empty line - if (obj and obj->next() and obj->next()->isScemantic()) - CurrentDocument()->insertObj (idx, LDEmptyPtr (LDSpawn<LDEmpty>())); - - buildObjList(); - delete ui; -} - -void MainWindow::actionSplitLines() -{ - bool ok; - int segments = QInputDialog::getInt (g_win, APPNAME, "Amount of segments:", cfg::SplitLinesSegments, 0, - std::numeric_limits<int>::max(), 1, &ok); - - if (not ok) - return; - - cfg::SplitLinesSegments = segments; - - for (LDObjectPtr obj : Selection()) - { - if (not Eq (obj->type(), OBJ_Line, OBJ_CondLine)) - continue; - - QVector<LDObjectPtr> newsegs; - - for (int i = 0; i < segments; ++i) - { - LDObjectPtr segment; - Vertex v0, v1; - - v0.apply ([&](Axis ax, double& a) - { - double len = obj->vertex (1)[ax] - obj->vertex (0)[ax]; - a = (obj->vertex (0)[ax] + ((len * i) / segments)); - }); - - v1.apply ([&](Axis ax, double& a) - { - double len = obj->vertex (1)[ax] - obj->vertex (0)[ax]; - a = (obj->vertex (0)[ax] + ((len * (i + 1)) / segments)); - }); - - if (obj->type() == OBJ_Line) - segment = LDSpawn<LDLine> (v0, v1); - else - segment = LDSpawn<LDCondLine> (v0, v1, obj->vertex (2), obj->vertex (3)); - - newsegs << segment; - } - - int ln = obj->lineNumber(); - - for (LDObjectPtr seg : newsegs) - CurrentDocument()->insertObj (ln++, seg); - - obj->destroy(); - } - - buildObjList(); - g_win->refresh(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/actionsEdit.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,738 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <limits> +#include <QSpinBox> +#include <QCheckBox> +#include <QBoxLayout> +#include <QClipboard> +#include <QInputDialog> +#include "mainWindow.h" +#include "main.h" +#include "ldDocument.h" +#include "colorSelector.h" +#include "miscallenous.h" +#include "radioGroup.h" +#include "glRenderer.h" +#include "dialogs.h" +#include "colors.h" +#include "ldObjectMath.h" +#include "ui_replcoords.h" +#include "ui_editraw.h" +#include "ui_flip.h" +#include "ui_addhistoryline.h" + +EXTERN_CFGENTRY (String, DefaultUser) + +CFGENTRY (Int, RoundPosition, 3) +CFGENTRY (Int, RoundMatrix, 4) +CFGENTRY (Int, SplitLinesSegments, 5) + +// ============================================================================= +// +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 (LDObjectPtr obj : objs) + { + if (not data.isEmpty()) + data += "\n"; + + data += obj->asText(); + ++num; + } + + qApp->clipboard()->setText (data); + return num; +} + +// ============================================================================= +// +void MainWindow::actionCut() +{ + int num = CopyToClipboard(); + deleteSelection(); + print (tr ("%1 objects cut"), num); +} + +// ============================================================================= +// +void MainWindow::actionCopy() +{ + int num = CopyToClipboard(); + print (tr ("%1 objects copied"), num); +} + +// ============================================================================= +// +void MainWindow::actionPaste() +{ + const QString clipboardText = qApp->clipboard()->text(); + int idx = getInsertionPoint(); + CurrentDocument()->clearSelection(); + int num = 0; + + for (QString line : clipboardText.split ("\n")) + { + LDObjectPtr pasted = ParseLine (line); + CurrentDocument()->insertObj (idx++, pasted); + pasted->select(); + ++num; + } + + print (tr ("%1 objects pasted"), num); + refresh(); + scrollToSelection(); +} + +// ============================================================================= +// +void MainWindow::actionDelete() +{ + int num = deleteSelection(); + print (tr ("%1 objects deleted"), num); +} + +// ============================================================================= +// +static void DoInline (bool deep) +{ + LDObjectList sel = Selection(); + + LDIterate<LDSubfile> (Selection(), [&](LDSubfilePtr const& ref) + { + // Get the index of the subfile so we know where to insert the + // inlined contents. + long idx = ref->lineNumber(); + + assert (idx != -1); + LDObjectList objs = ref->inlineContents (deep, false); + + // Merge in the inlined objects + for (LDObjectPtr inlineobj : objs) + { + QString line = inlineobj->asText(); + inlineobj->destroy(); + LDObjectPtr newobj = ParseLine (line); + CurrentDocument()->insertObj (idx++, newobj); + newobj->select(); + } + + // Delete the subfile now as it's been inlined. + ref->destroy(); + }); + + g_win->refresh(); +} + +void MainWindow::actionInline() +{ + DoInline (false); +} + +void MainWindow::actionInlineDeep() +{ + DoInline (true); +} + +// ============================================================================= +// +void MainWindow::actionSplitQuads() +{ + int num = 0; + + LDIterate<LDQuad> (Selection(), [&](LDQuadPtr const& quad) + { + // Find the index of this quad + long index = quad->lineNumber(); + + if (index == -1) + return; + + QList<LDTrianglePtr> triangles = quad->splitToTriangles(); + + // Replace the quad with the first triangle and add the second triangle + // after the first one. + CurrentDocument()->setObject (index, triangles[0]); + CurrentDocument()->insertObj (index + 1, triangles[1]); + num++; + }); + + print ("%1 quadrilaterals split", num); + refresh(); +} + +// ============================================================================= +// +void MainWindow::actionEditRaw() +{ + if (Selection().size() != 1) + return; + + LDObjectPtr obj = Selection()[0]; + QDialog* dlg = new QDialog; + Ui::EditRawUI ui; + + ui.setupUi (dlg); + ui.code->setText (obj->asText()); + + if (obj->type() == OBJ_Error) + ui.errorDescription->setText (obj.staticCast<LDError>()->reason()); + else + { + ui.errorDescription->hide(); + ui.errorIcon->hide(); + } + + if (dlg->exec() == QDialog::Rejected) + return; + + // Reinterpret it from the text of the input field + LDObjectPtr newobj = ParseLine (ui.code->text()); + obj->replace (newobj); + refresh(); +} + +// ============================================================================= +// +void MainWindow::actionSetColor() +{ + if (Selection().isEmpty()) + return; + + LDObjectList objs = Selection(); + + // If all selected objects have the same color, said color is our default + // value to the color selection dialog. + LDColor color; + LDColor defaultcol = getSelectedColor(); + + // Show the dialog to the user now and ask for a color. + if (ColorSelector::selectColor (color, defaultcol, g_win)) + { + for (LDObjectPtr obj : objs) + { + if (obj->isColored()) + obj->setColor (color); + } + + refresh(); + } +} + +// ============================================================================= +// +void MainWindow::actionBorders() +{ + LDObjectList objs = Selection(); + int num = 0; + + for (LDObjectPtr obj : objs) + { + const LDObjectType type = obj->type(); + + if (type != OBJ_Quad and type != OBJ_Triangle) + continue; + + LDLinePtr lines[4]; + + if (type == OBJ_Quad) + { + LDQuadPtr quad = obj.staticCast<LDQuad>(); + lines[0] = LDSpawn<LDLine> (quad->vertex (0), quad->vertex (1)); + lines[1] = LDSpawn<LDLine> (quad->vertex (1), quad->vertex (2)); + lines[2] = LDSpawn<LDLine> (quad->vertex (2), quad->vertex (3)); + lines[3] = LDSpawn<LDLine> (quad->vertex (3), quad->vertex (0)); + } + else + { + LDTrianglePtr tri = obj.staticCast<LDTriangle>(); + lines[0] = LDSpawn<LDLine> (tri->vertex (0), tri->vertex (1)); + lines[1] = LDSpawn<LDLine> (tri->vertex (1), tri->vertex (2)); + lines[2] = LDSpawn<LDLine> (tri->vertex (2), tri->vertex (0)); + } + + for (int i = 0; i < countof (lines); ++i) + { + if (lines[i] == null) + continue; + + long idx = obj->lineNumber() + i + 1; + CurrentDocument()->insertObj (idx, lines[i]); + } + + num += countof (lines); + } + + print (tr ("Added %1 border lines"), num); + refresh(); +} + +// ============================================================================= +// +static void MoveSelection (const bool up) +{ + LDObjectList objs = Selection(); + LDObject::moveObjects (objs, up); + g_win->buildObjList(); +} + +// ============================================================================= +// +void MainWindow::actionMoveUp() +{ + MoveSelection (true); +} + +void MainWindow::actionMoveDown() +{ + MoveSelection (false); +} + +// ============================================================================= +// +void MainWindow::actionUndo() +{ + CurrentDocument()->undo(); +} + +void MainWindow::actionRedo() +{ + CurrentDocument()->redo(); +} + +// ============================================================================= +// +static void MoveObjects (Vertex vect) +{ + // Apply the grid values + vect *= *CurrentGrid().coordinateSnap; + + for (LDObjectPtr obj : Selection()) + obj->move (vect); + + g_win->refresh(); +} + +// ============================================================================= +// +void MainWindow::actionMoveXNeg() +{ + MoveObjects ({-1, 0, 0}); +} + +void MainWindow::actionMoveYNeg() +{ + MoveObjects ({0, -1, 0}); +} + +void MainWindow::actionMoveZNeg() +{ + MoveObjects ({0, 0, -1}); +} + +void MainWindow::actionMoveXPos() +{ + MoveObjects ({1, 0, 0}); +} + +void MainWindow::actionMoveYPos() +{ + MoveObjects ({0, 1, 0}); +} + +void MainWindow::actionMoveZPos() +{ + MoveObjects ({0, 0, 1}); +} + +// ============================================================================= +// +void MainWindow::actionInvert() +{ + for (LDObjectPtr obj : Selection()) + obj->invert(); + + refresh(); +} + +// ============================================================================= +// +static double GetRotateActionAngle() +{ + return (Pi * *CurrentGrid().angleSnap) / 180; +} + +void MainWindow::actionRotateXPos() +{ + RotateObjects (1, 0, 0, GetRotateActionAngle(), Selection()); +} +void MainWindow::actionRotateYPos() +{ + RotateObjects (0, 1, 0, GetRotateActionAngle(), Selection()); +} +void MainWindow::actionRotateZPos() +{ + RotateObjects (0, 0, 1, GetRotateActionAngle(), Selection()); +} +void MainWindow::actionRotateXNeg() +{ + RotateObjects (-1, 0, 0, GetRotateActionAngle(), Selection()); +} +void MainWindow::actionRotateYNeg() +{ + RotateObjects (0, -1, 0, GetRotateActionAngle(), Selection()); +} +void MainWindow::actionRotateZNeg() +{ + RotateObjects (0, 0, -1, GetRotateActionAngle(), Selection()); +} + +void MainWindow::actionRotationPoint() +{ + ConfigureRotationPoint(); +} + +// ============================================================================= +// +void MainWindow::actionRoundCoordinates() +{ + setlocale (LC_ALL, "C"); + int num = 0; + + for (LDObjectPtr obj : Selection()) + { + LDMatrixObjectPtr mo = obj.dynamicCast<LDMatrixObject>(); + + if (mo != null) + { + Vertex v = mo->position(); + Matrix t = mo->transform(); + + // Note: matrix values are to be rounded to 4 decimals. + v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); }); + ApplyToMatrix (t, [](int, double& a) { RoundToDecimals (a, cfg::RoundMatrix); }); + + mo->setPosition (v); + mo->setTransform (t); + num += 12; + } + else + { + for (int i = 0; i < obj->numVertices(); ++i) + { + Vertex v = obj->vertex (i); + v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); }); + obj->setVertex (i, v); + num += 3; + } + } + } + + print (tr ("Rounded %1 values"), num); + refreshObjectList(); + refresh(); +} + +// ============================================================================= +// +void MainWindow::actionUncolor() +{ + int num = 0; + + for (LDObjectPtr obj : Selection()) + { + if (not obj->isColored()) + continue; + + obj->setColor (obj->defaultColor()); + num++; + } + + print (tr ("%1 objects uncolored"), num); + refresh(); +} + +// ============================================================================= +// +void MainWindow::actionReplaceCoords() +{ + QDialog* dlg = new QDialog (g_win); + Ui::ReplaceCoordsUI ui; + ui.setupUi (dlg); + + if (not 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 (LDObjectPtr obj : Selection()) + { + for (int i = 0; i < obj->numVertices(); ++i) + { + Vertex v = obj->vertex (i); + + v.apply ([&](Axis ax, double& coord) + { + if (not sel.contains (ax) or + (not any and coord != search)) + { + return; + } + + if (not rel) + coord = 0; + + coord += replacement; + num++; + }); + + obj->setVertex (i, v); + } + } + + print (tr ("Altered %1 values"), num); + refresh(); +} + +// ============================================================================= +// +void MainWindow::actionFlip() +{ + QDialog* dlg = new QDialog; + Ui::FlipUI ui; + ui.setupUi (dlg); + + if (not 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 (LDObjectPtr obj : Selection()) + { + for (int i = 0; i < obj->numVertices(); ++i) + { + Vertex v = obj->vertex (i); + + v.apply ([&](Axis ax, double& a) + { + if (sel.contains (ax)) + a = -a; + }); + + obj->setVertex (i, v); + } + } + + refresh(); +} + +// ============================================================================= +// +void MainWindow::actionDemote() +{ + int num = 0; + + LDIterate<LDCondLine> (Selection(), [&](LDCondLinePtr const& cnd) + { + cnd->toEdgeLine(); + ++num; + }); + + print (tr ("Demoted %1 conditional lines"), num); + refresh(); +} + +// ============================================================================= +// +static bool IsColorUsed (LDColor color) +{ + for (LDObjectPtr obj : CurrentDocument()->objects()) + { + if (obj->isColored() and obj->color() == color) + return true; + } + + return false; +} + +// ============================================================================= +// +void MainWindow::actionAutocolor() +{ + int colnum = 0; + LDColor color; + + for (colnum = 0; + colnum < CountLDConfigColors() and + ((color = LDColor::fromIndex (colnum)) == null or + IsColorUsed (color)); + colnum++) {} + + if (colnum >= CountLDConfigColors()) + { + print (tr ("Cannot auto-color: all colors are in use!")); + return; + } + + for (LDObjectPtr obj : Selection()) + { + if (not obj->isColored()) + continue; + + obj->setColor (color); + } + + print (tr ("Auto-colored: new color is [%1] %2"), colnum, color.name()); + refresh(); +} + +// ============================================================================= +// +void MainWindow::actionAddHistoryLine() +{ + LDObjectPtr obj; + bool ishistory = false, + prevIsHistory = false; + + QDialog* dlg = new QDialog; + Ui_AddHistoryLine* ui = new Ui_AddHistoryLine; + ui->setupUi (dlg); + ui->m_username->setText (cfg::DefaultUser); + ui->m_date->setDate (QDate::currentDate()); + ui->m_comment->setFocus(); + + if (not dlg->exec()) + return; + + // Create the comment object based on input + QString commentText = format ("!HISTORY %1 [%2] %3", + ui->m_date->date().toString ("yyyy-MM-dd"), + ui->m_username->text(), + ui->m_comment->text()); + + LDCommentPtr comm (LDSpawn<LDComment> (commentText)); + + // Find a spot to place the new comment + for (obj = CurrentDocument()->getObject (0); + obj != null and obj->next() != null and not obj->next()->isScemantic(); + obj = obj->next()) + { + LDCommentPtr comm = obj.dynamicCast<LDComment>(); + + if (comm != null and comm->text().startsWith ("!HISTORY ")) + ishistory = true; + + if (prevIsHistory and not ishistory) + { + // Last line was history, this isn't, thus insert the new history + // line here. + break; + } + + prevIsHistory = ishistory; + } + + int idx = obj ? obj->lineNumber() : 0; + CurrentDocument()->insertObj (idx++, comm); + + // If we're adding a history line right before a scemantic object, pad it + // an empty line + if (obj and obj->next() and obj->next()->isScemantic()) + CurrentDocument()->insertObj (idx, LDEmptyPtr (LDSpawn<LDEmpty>())); + + buildObjList(); + delete ui; +} + +void MainWindow::actionSplitLines() +{ + bool ok; + int segments = QInputDialog::getInt (g_win, APPNAME, "Amount of segments:", cfg::SplitLinesSegments, 0, + std::numeric_limits<int>::max(), 1, &ok); + + if (not ok) + return; + + cfg::SplitLinesSegments = segments; + + for (LDObjectPtr obj : Selection()) + { + if (not Eq (obj->type(), OBJ_Line, OBJ_CondLine)) + continue; + + QVector<LDObjectPtr> newsegs; + + for (int i = 0; i < segments; ++i) + { + LDObjectPtr segment; + Vertex v0, v1; + + v0.apply ([&](Axis ax, double& a) + { + double len = obj->vertex (1)[ax] - obj->vertex (0)[ax]; + a = (obj->vertex (0)[ax] + ((len * i) / segments)); + }); + + v1.apply ([&](Axis ax, double& a) + { + double len = obj->vertex (1)[ax] - obj->vertex (0)[ax]; + a = (obj->vertex (0)[ax] + ((len * (i + 1)) / segments)); + }); + + if (obj->type() == OBJ_Line) + segment = LDSpawn<LDLine> (v0, v1); + else + segment = LDSpawn<LDCondLine> (v0, v1, obj->vertex (2), obj->vertex (3)); + + newsegs << segment; + } + + int ln = obj->lineNumber(); + + for (LDObjectPtr seg : newsegs) + CurrentDocument()->insertObj (ln++, seg); + + obj->destroy(); + } + + buildObjList(); + g_win->refresh(); +}
--- a/src/addObjectDialog.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,396 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QGridLayout> -#include <QCheckBox> -#include <QDialogButtonBox> -#include <QSpinBox> -#include <QLabel> -#include <QListWidget> -#include <QTreeWidget> -#include <QLineEdit> -#include <QPushButton> -#include "mainWindow.h" -#include "addObjectDialog.h" -#include "ldDocument.h" -#include "colors.h" -#include "colorSelector.h" -#include "editHistory.h" -#include "radioGroup.h" -#include "miscallenous.h" -#include "primitives.h" - -// ============================================================================= -// -AddObjectDialog::AddObjectDialog (const LDObjectType type, LDObjectPtr obj, QWidget* parent) : - QDialog (parent) -{ - setlocale (LC_ALL, "C"); - - int coordCount = 0; - QString typeName = LDObject::typeName (type); - - switch (type) - { - case OBJ_Comment: - { - le_comment = new QLineEdit; - - if (obj) - le_comment->setText (obj.staticCast<LDComment>()->text()); - - le_comment->setMinimumWidth (384); - } break; - - case OBJ_Line: - { - coordCount = 6; - } break; - - case OBJ_Triangle: - { - coordCount = 9; - } break; - - case OBJ_Quad: - case OBJ_CondLine: - { - coordCount = 12; - } break; - - case OBJ_BFC: - { - rb_bfcType = new RadioGroup ("Statement", {}, 0, Qt::Vertical); - - for_enum (BFCStatement, i) - { - // Separate these in two columns - if (int (i) == int (BFCStatement::NumValues) / 2) - rb_bfcType->rowBreak(); - - rb_bfcType->addButton (LDBFC::StatementStrings[int (i)]); - } - - if (obj) - rb_bfcType->setValue ((int) obj.staticCast<LDBFC>()->statement()); - } break; - - case OBJ_Subfile: - { - coordCount = 3; - tw_subfileList = new QTreeWidget(); - tw_subfileList->setHeaderLabel (tr ("Primitives")); - PopulatePrimitives (tw_subfileList, (obj != null ? obj.staticCast<LDSubfile>()->fileInfo()->name() : "")); - - connect (tw_subfileList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_subfileTypeChanged())); - lb_subfileName = new QLabel ("File:"); - le_subfileName = new QLineEdit; - le_subfileName->setFocus(); - - if (obj) - { - LDSubfilePtr ref = obj.staticCast<LDSubfile>(); - le_subfileName->setText (ref->fileInfo()->name()); - } - } break; - - default: - { - Critical (format ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName)); - } return; - } - - QPixmap icon = GetIcon (format ("add-%1", typeName)); - LDObjectPtr 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) - m_color = obj->color(); - else - m_color = (type == OBJ_CondLine or type == OBJ_Line) ? EdgeColor() : MainColor(); - - pb_color = new QPushButton; - setButtonBackground (pb_color, m_color); - 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 OBJ_Line: - case OBJ_CondLine: - case OBJ_Triangle: - case OBJ_Quad: - // Apply coordinates - if (obj != null) - { - for (int i = 0; i < coordCount / 3; ++i) - { - obj->vertex (i).apply ([&](Axis ax, double value) - { - dsb_coords[(i * 3) + ax]->setValue (value); - }); - } - } - break; - - case OBJ_Comment: - layout->addWidget (le_comment, 0, 1); - break; - - case OBJ_BFC: - layout->addWidget (rb_bfcType, 0, 1); - break; - - case OBJ_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()) - { - LDMatrixObjectPtr mo = obj.dynamicCast<LDMatrixObject>(); - QLabel* lb_matrix = new QLabel ("Matrix:"); - le_matrix = new QLineEdit; - // le_matrix->setValidator (new QDoubleValidator); - Matrix defaultMatrix = IdentityMatrix; - - if (mo != null) - { - mo->position().apply ([&](Axis ax, double value) - { - dsb_coords[ax]->setValue (value); - }); - - defaultMatrix = mo->transform(); - } - - le_matrix->setText (defaultMatrix.toString()); - layout->addWidget (lb_matrix, 4, 1); - layout->addWidget (le_matrix, 4, 2, 1, 3); - } - - if (defaults->isColored()) - layout->addWidget (pb_color, 1, 0); - - if (coordCount > 0) - { - QGridLayout* const qCoordLayout = new QGridLayout; - - for (int i = 0; i < coordCount; ++i) - qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3)); - - layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3); - } - - QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - QWidget::connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept())); - QWidget::connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); - layout->addWidget (bbx_buttons, 5, 0, 1, 4); - setLayout (layout); - setWindowTitle (format (tr ("Edit %1"), typeName)); - setWindowIcon (icon); -} - -// ============================================================================= -// ============================================================================= -void AddObjectDialog::setButtonBackground (QPushButton* button, LDColor color) -{ - button->setIcon (GetIcon ("palette")); - button->setAutoFillBackground (true); - - if (color != null) - button->setStyleSheet (format ("background-color: %1", color.hexcode())); -} - -// ============================================================================= -// ============================================================================= -QString AddObjectDialog::currentSubfileName() -{ - SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem()); - - if (item->primitive() == null) - return ""; // selected a heading - - return item->primitive()->name; -} - -// ============================================================================= -// ============================================================================= -void AddObjectDialog::slot_colorButtonClicked() -{ - ColorSelector::selectColor (m_color, m_color, this); - setButtonBackground (pb_color, m_color); -} - -// ============================================================================= -// ============================================================================= -void AddObjectDialog::slot_subfileTypeChanged() -{ - QString name = currentSubfileName(); - - if (name.length() > 0) - le_subfileName->setText (name); -} - -// ============================================================================= -// ============================================================================= -template<typename T> -static QSharedPointer<T> InitObject (LDObjectPtr& obj) -{ - if (obj == null) - obj = LDSpawn<T>(); - - return obj.staticCast<T>(); -} - -// ============================================================================= -// ============================================================================= -void AddObjectDialog::staticDialog (const LDObjectType type, LDObjectPtr obj) -{ - setlocale (LC_ALL, "C"); - - // FIXME: Redirect to Edit Raw - if (obj and obj->type() == OBJ_Error) - return; - - if (type == OBJ_Empty) - return; // Nothing to edit with empties - - const bool newObject = (obj == null); - Matrix transform = IdentityMatrix; - AddObjectDialog dlg (type, obj); - - assert (obj == null or obj->type() == type); - - if (dlg.exec() == QDialog::Rejected) - return; - - if (type == OBJ_Subfile) - { - 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 OBJ_Comment: - { - LDCommentPtr comm = InitObject<LDComment> (obj); - comm->setText (dlg.le_comment->text()); - } - break; - - case OBJ_Line: - case OBJ_Triangle: - case OBJ_Quad: - case OBJ_CondLine: - { - if (not obj) - obj = LDObject::getDefault (type); - - for (int i = 0; i < obj->numVertices(); ++i) - { - Vertex v; - - v.apply ([&](Axis ax, double& value) - { - value = dlg.dsb_coords[(i * 3) + ax]->value(); - }); - - obj->setVertex (i, v); - } - } break; - - case OBJ_BFC: - { - LDBFCPtr bfc = InitObject<LDBFC> (obj); - assert (IsWithin (dlg.rb_bfcType->value(), 0, int (BFCStatement::NumValues) - 1)); - bfc->setStatement (BFCStatement (dlg.rb_bfcType->value())); - } break; - - case OBJ_Subfile: - { - QString name = dlg.le_subfileName->text(); - - if (name.length() == 0) - return; // no subfile filename - - LDDocumentPtr file = GetDocument (name); - - if (not file) - { - Critical (format ("Couldn't open `%1': %2", name, strerror (errno))); - return; - } - - LDSubfilePtr ref = InitObject<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.m_color); - - if (newObject) - { - int idx = g_win->getInsertionPoint(); - CurrentDocument()->insertObj (idx, obj); - } - - g_win->refresh(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/addObjectDialog.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,396 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QGridLayout> +#include <QCheckBox> +#include <QDialogButtonBox> +#include <QSpinBox> +#include <QLabel> +#include <QListWidget> +#include <QTreeWidget> +#include <QLineEdit> +#include <QPushButton> +#include "mainWindow.h" +#include "addObjectDialog.h" +#include "ldDocument.h" +#include "colors.h" +#include "colorSelector.h" +#include "editHistory.h" +#include "radioGroup.h" +#include "miscallenous.h" +#include "primitives.h" + +// ============================================================================= +// +AddObjectDialog::AddObjectDialog (const LDObjectType type, LDObjectPtr obj, QWidget* parent) : + QDialog (parent) +{ + setlocale (LC_ALL, "C"); + + int coordCount = 0; + QString typeName = LDObject::typeName (type); + + switch (type) + { + case OBJ_Comment: + { + le_comment = new QLineEdit; + + if (obj) + le_comment->setText (obj.staticCast<LDComment>()->text()); + + le_comment->setMinimumWidth (384); + } break; + + case OBJ_Line: + { + coordCount = 6; + } break; + + case OBJ_Triangle: + { + coordCount = 9; + } break; + + case OBJ_Quad: + case OBJ_CondLine: + { + coordCount = 12; + } break; + + case OBJ_BFC: + { + rb_bfcType = new RadioGroup ("Statement", {}, 0, Qt::Vertical); + + for_enum (BFCStatement, i) + { + // Separate these in two columns + if (int (i) == int (BFCStatement::NumValues) / 2) + rb_bfcType->rowBreak(); + + rb_bfcType->addButton (LDBFC::StatementStrings[int (i)]); + } + + if (obj) + rb_bfcType->setValue ((int) obj.staticCast<LDBFC>()->statement()); + } break; + + case OBJ_Subfile: + { + coordCount = 3; + tw_subfileList = new QTreeWidget(); + tw_subfileList->setHeaderLabel (tr ("Primitives")); + PopulatePrimitives (tw_subfileList, (obj != null ? obj.staticCast<LDSubfile>()->fileInfo()->name() : "")); + + connect (tw_subfileList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_subfileTypeChanged())); + lb_subfileName = new QLabel ("File:"); + le_subfileName = new QLineEdit; + le_subfileName->setFocus(); + + if (obj) + { + LDSubfilePtr ref = obj.staticCast<LDSubfile>(); + le_subfileName->setText (ref->fileInfo()->name()); + } + } break; + + default: + { + Critical (format ("Unhandled LDObject type %1 (%2) in AddObjectDialog", (int) type, typeName)); + } return; + } + + QPixmap icon = GetIcon (format ("add-%1", typeName)); + LDObjectPtr 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) + m_color = obj->color(); + else + m_color = (type == OBJ_CondLine or type == OBJ_Line) ? EdgeColor() : MainColor(); + + pb_color = new QPushButton; + setButtonBackground (pb_color, m_color); + 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 OBJ_Line: + case OBJ_CondLine: + case OBJ_Triangle: + case OBJ_Quad: + // Apply coordinates + if (obj != null) + { + for (int i = 0; i < coordCount / 3; ++i) + { + obj->vertex (i).apply ([&](Axis ax, double value) + { + dsb_coords[(i * 3) + ax]->setValue (value); + }); + } + } + break; + + case OBJ_Comment: + layout->addWidget (le_comment, 0, 1); + break; + + case OBJ_BFC: + layout->addWidget (rb_bfcType, 0, 1); + break; + + case OBJ_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()) + { + LDMatrixObjectPtr mo = obj.dynamicCast<LDMatrixObject>(); + QLabel* lb_matrix = new QLabel ("Matrix:"); + le_matrix = new QLineEdit; + // le_matrix->setValidator (new QDoubleValidator); + Matrix defaultMatrix = IdentityMatrix; + + if (mo != null) + { + mo->position().apply ([&](Axis ax, double value) + { + dsb_coords[ax]->setValue (value); + }); + + defaultMatrix = mo->transform(); + } + + le_matrix->setText (defaultMatrix.toString()); + layout->addWidget (lb_matrix, 4, 1); + layout->addWidget (le_matrix, 4, 2, 1, 3); + } + + if (defaults->isColored()) + layout->addWidget (pb_color, 1, 0); + + if (coordCount > 0) + { + QGridLayout* const qCoordLayout = new QGridLayout; + + for (int i = 0; i < coordCount; ++i) + qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3)); + + layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3); + } + + QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + QWidget::connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept())); + QWidget::connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); + layout->addWidget (bbx_buttons, 5, 0, 1, 4); + setLayout (layout); + setWindowTitle (format (tr ("Edit %1"), typeName)); + setWindowIcon (icon); +} + +// ============================================================================= +// ============================================================================= +void AddObjectDialog::setButtonBackground (QPushButton* button, LDColor color) +{ + button->setIcon (GetIcon ("palette")); + button->setAutoFillBackground (true); + + if (color != null) + button->setStyleSheet (format ("background-color: %1", color.hexcode())); +} + +// ============================================================================= +// ============================================================================= +QString AddObjectDialog::currentSubfileName() +{ + SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem()); + + if (item->primitive() == null) + return ""; // selected a heading + + return item->primitive()->name; +} + +// ============================================================================= +// ============================================================================= +void AddObjectDialog::slot_colorButtonClicked() +{ + ColorSelector::selectColor (m_color, m_color, this); + setButtonBackground (pb_color, m_color); +} + +// ============================================================================= +// ============================================================================= +void AddObjectDialog::slot_subfileTypeChanged() +{ + QString name = currentSubfileName(); + + if (name.length() > 0) + le_subfileName->setText (name); +} + +// ============================================================================= +// ============================================================================= +template<typename T> +static QSharedPointer<T> InitObject (LDObjectPtr& obj) +{ + if (obj == null) + obj = LDSpawn<T>(); + + return obj.staticCast<T>(); +} + +// ============================================================================= +// ============================================================================= +void AddObjectDialog::staticDialog (const LDObjectType type, LDObjectPtr obj) +{ + setlocale (LC_ALL, "C"); + + // FIXME: Redirect to Edit Raw + if (obj and obj->type() == OBJ_Error) + return; + + if (type == OBJ_Empty) + return; // Nothing to edit with empties + + const bool newObject = (obj == null); + Matrix transform = IdentityMatrix; + AddObjectDialog dlg (type, obj); + + assert (obj == null or obj->type() == type); + + if (dlg.exec() == QDialog::Rejected) + return; + + if (type == OBJ_Subfile) + { + 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 OBJ_Comment: + { + LDCommentPtr comm = InitObject<LDComment> (obj); + comm->setText (dlg.le_comment->text()); + } + break; + + case OBJ_Line: + case OBJ_Triangle: + case OBJ_Quad: + case OBJ_CondLine: + { + if (not obj) + obj = LDObject::getDefault (type); + + for (int i = 0; i < obj->numVertices(); ++i) + { + Vertex v; + + v.apply ([&](Axis ax, double& value) + { + value = dlg.dsb_coords[(i * 3) + ax]->value(); + }); + + obj->setVertex (i, v); + } + } break; + + case OBJ_BFC: + { + LDBFCPtr bfc = InitObject<LDBFC> (obj); + assert (IsWithin (dlg.rb_bfcType->value(), 0, int (BFCStatement::NumValues) - 1)); + bfc->setStatement (BFCStatement (dlg.rb_bfcType->value())); + } break; + + case OBJ_Subfile: + { + QString name = dlg.le_subfileName->text(); + + if (name.length() == 0) + return; // no subfile filename + + LDDocumentPtr file = GetDocument (name); + + if (not file) + { + Critical (format ("Couldn't open `%1': %2", name, strerror (errno))); + return; + } + + LDSubfilePtr ref = InitObject<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.m_color); + + if (newObject) + { + int idx = g_win->getInsertionPoint(); + CurrentDocument()->insertObj (idx, obj); + } + + g_win->refresh(); +}
--- a/src/basics.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,336 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QObject> -#include <QStringList> -#include <QTextStream> -#include <QFile> -#include <assert.h> -#include "main.h" -#include "basics.h" -#include "miscallenous.h" -#include "ldObject.h" -#include "ldDocument.h" - -Vertex::Vertex() : - QVector3D() {} - -Vertex::Vertex (const QVector3D& a) : - QVector3D (a) {} - -Vertex::Vertex (qreal xpos, qreal ypos, qreal zpos) : - QVector3D(xpos, ypos, zpos) {} - - -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(); - setX (x2); - setY (y2); - setZ (z2); -} - -void Vertex::apply (ApplyFunction func) -{ - double newX = x(), newY = y(), newZ = z(); - func (X, newX); - func (Y, newY); - func (Z, newZ); - *this = Vertex (newX, newY, newZ); -} - -void Vertex::apply (ApplyConstFunction func) const -{ - func (X, x()); - func (Y, y()); - func (Z, z()); -} - -double Vertex::operator[] (Axis ax) const -{ - switch (ax) - { - case X: return x(); - case Y: return y(); - case Z: return z(); - } - - assert (false); - return 0.0; -} - -void Vertex::setCoordinate (Axis ax, qreal value) -{ - switch (ax) - { - case X: setX (value); break; - case Y: setY (value); break; - case Z: setZ (value); break; - } -} - -QString Vertex::toString (bool mangled) const -{ - if (mangled) - return format ("(%1, %2, %3)", x(), y(), z()); - - return format ("%1 %2 %3", x(), y(), z()); -} - -bool Vertex::operator< (const Vertex& other) const -{ - if (x() != other.x()) return x() < other.x(); - if (y() != other.y()) return y() < other.y(); - if (z() != other.z()) return z() < other.z(); - return false; -} - -// ============================================================================= -// -Matrix::Matrix (double vals[]) -{ - for (int i = 0; i < 9; ++i) - m_vals[i] = vals[i]; -} - -// ============================================================================= -// -Matrix::Matrix (double fillval) -{ - for (int i = 0; i < 9; ++i) - m_vals[i] = fillval; -} - -// ============================================================================= -// -Matrix::Matrix (const std::initializer_list<double>& vals) -{ - assert (vals.size() == 9); - memcpy (&m_vals[0], vals.begin(), sizeof m_vals); -} - -// ============================================================================= -// -void Matrix::dump() const -{ - for (int i = 0; i < 3; ++i) - { - for (int j = 0; j < 3; ++j) - print ("%1\t", m_vals[i * 3 + j]); - - print ("\n"); - } -} - -// ============================================================================= -// -QString Matrix::toString() const -{ - QString val; - - for (int i = 0; i < 9; ++i) - { - if (i > 0) - val += ' '; - - val += QString::number (m_vals[i]); - } - - return val; -} - -// ============================================================================= -// -void Matrix::zero() -{ - memset (&m_vals[0], 0, sizeof m_vals); -} - -// ============================================================================= -// -Matrix Matrix::mult (const Matrix& other) const -{ - Matrix val; - val.zero(); - - for (int i = 0; i < 3; ++i) - for (int j = 0; j < 3; ++j) - for (int k = 0; k < 3; ++k) - val[(i * 3) + j] += m_vals[(i * 3) + k] * other[(k * 3) + j]; - - return val; -} - -// ============================================================================= -// -Matrix& Matrix::operator= (const Matrix& other) -{ - memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals); - return *this; -} - -// ============================================================================= -// -double Matrix::getDeterminant() const -{ - return (value (0) * value (4) * value (8)) + - (value (1) * value (5) * value (6)) + - (value (2) * value (3) * value (7)) - - (value (2) * value (4) * value (6)) - - (value (1) * value (3) * value (8)) - - (value (0) * value (5) * value (7)); -} - -// ============================================================================= -// -bool Matrix::operator== (const Matrix& other) const -{ - for (int i = 0; i < 9; ++i) - { - if (value (i) != other[i]) - return false; - } - - return true; -} - -// ============================================================================= -// -LDBoundingBox::LDBoundingBox() -{ - reset(); -} - -// ============================================================================= -// -void LDBoundingBox::calculateFromCurrentDocument() -{ - reset(); - - if (CurrentDocument() == null) - return; - - for (LDObjectPtr obj : CurrentDocument()->objects()) - calcObject (obj); -} - -// ============================================================================= -// -void LDBoundingBox::calcObject (LDObjectPtr obj) -{ - switch (obj->type()) - { - case OBJ_Line: - case OBJ_Triangle: - case OBJ_Quad: - case OBJ_CondLine: - { - for (int i = 0; i < obj->numVertices(); ++i) - calcVertex (obj->vertex (i)); - } break; - - case OBJ_Subfile: - { - LDSubfilePtr ref = obj.staticCast<LDSubfile>(); - LDObjectList objs = ref->inlineContents (true, false); - - for (LDObjectPtr obj : objs) - { - calcObject (obj); - obj->destroy(); - } - } - break; - - default: - break; - } -} - -// ============================================================================= -// -LDBoundingBox& LDBoundingBox::operator<< (const Vertex& v) -{ - calcVertex (v); - return *this; -} - -// ============================================================================= -// -LDBoundingBox& LDBoundingBox::operator<< (LDObjectPtr obj) -{ - calcObject (obj); - return *this; -} - -// ============================================================================= -// -void LDBoundingBox::calcVertex (const Vertex& vertex) -{ - m_vertex0.setX (Min (vertex.x(), m_vertex0.x())); - m_vertex0.setY (Min (vertex.y(), m_vertex0.y())); - m_vertex0.setZ (Min (vertex.z(), m_vertex0.z())); - m_vertex1.setX (Max (vertex.x(), m_vertex1.x())); - m_vertex1.setY (Max (vertex.y(), m_vertex1.y())); - m_vertex1.setZ (Max (vertex.z(), m_vertex1.z())); - setEmpty (false); -} - -// ============================================================================= -// -void LDBoundingBox::reset() -{ - m_vertex0 = Vertex (10000.0, 10000.0, 10000.0); - m_vertex1 = Vertex (-10000.0, -10000.0, -10000.0); - setEmpty (true); -} - -// ============================================================================= -// -double LDBoundingBox::longestMeasurement() const -{ - double xscale = (m_vertex0.x() - m_vertex1.x()); - double yscale = (m_vertex0.y() - m_vertex1.y()); - double zscale = (m_vertex0.z() - m_vertex1.z()); - double size = zscale; - - if (xscale > yscale) - { - if (xscale > zscale) - size = xscale; - } - elif (yscale > zscale) - size = yscale; - - if (Abs (size) >= 2.0) - return Abs (size / 2); - - return 1.0; -} - -// ============================================================================= -// -Vertex LDBoundingBox::center() const -{ - return Vertex ( - (m_vertex0.x() + m_vertex1.x()) / 2, - (m_vertex0.y() + m_vertex1.y()) / 2, - (m_vertex0.z() + m_vertex1.z()) / 2); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/basics.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,336 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QObject> +#include <QStringList> +#include <QTextStream> +#include <QFile> +#include <assert.h> +#include "main.h" +#include "basics.h" +#include "miscallenous.h" +#include "ldObject.h" +#include "ldDocument.h" + +Vertex::Vertex() : + QVector3D() {} + +Vertex::Vertex (const QVector3D& a) : + QVector3D (a) {} + +Vertex::Vertex (qreal xpos, qreal ypos, qreal zpos) : + QVector3D(xpos, ypos, zpos) {} + + +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(); + setX (x2); + setY (y2); + setZ (z2); +} + +void Vertex::apply (ApplyFunction func) +{ + double newX = x(), newY = y(), newZ = z(); + func (X, newX); + func (Y, newY); + func (Z, newZ); + *this = Vertex (newX, newY, newZ); +} + +void Vertex::apply (ApplyConstFunction func) const +{ + func (X, x()); + func (Y, y()); + func (Z, z()); +} + +double Vertex::operator[] (Axis ax) const +{ + switch (ax) + { + case X: return x(); + case Y: return y(); + case Z: return z(); + } + + assert (false); + return 0.0; +} + +void Vertex::setCoordinate (Axis ax, qreal value) +{ + switch (ax) + { + case X: setX (value); break; + case Y: setY (value); break; + case Z: setZ (value); break; + } +} + +QString Vertex::toString (bool mangled) const +{ + if (mangled) + return format ("(%1, %2, %3)", x(), y(), z()); + + return format ("%1 %2 %3", x(), y(), z()); +} + +bool Vertex::operator< (const Vertex& other) const +{ + if (x() != other.x()) return x() < other.x(); + if (y() != other.y()) return y() < other.y(); + if (z() != other.z()) return z() < other.z(); + return false; +} + +// ============================================================================= +// +Matrix::Matrix (double vals[]) +{ + for (int i = 0; i < 9; ++i) + m_vals[i] = vals[i]; +} + +// ============================================================================= +// +Matrix::Matrix (double fillval) +{ + for (int i = 0; i < 9; ++i) + m_vals[i] = fillval; +} + +// ============================================================================= +// +Matrix::Matrix (const std::initializer_list<double>& vals) +{ + assert (vals.size() == 9); + memcpy (&m_vals[0], vals.begin(), sizeof m_vals); +} + +// ============================================================================= +// +void Matrix::dump() const +{ + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + print ("%1\t", m_vals[i * 3 + j]); + + print ("\n"); + } +} + +// ============================================================================= +// +QString Matrix::toString() const +{ + QString val; + + for (int i = 0; i < 9; ++i) + { + if (i > 0) + val += ' '; + + val += QString::number (m_vals[i]); + } + + return val; +} + +// ============================================================================= +// +void Matrix::zero() +{ + memset (&m_vals[0], 0, sizeof m_vals); +} + +// ============================================================================= +// +Matrix Matrix::mult (const Matrix& other) const +{ + Matrix val; + val.zero(); + + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + for (int k = 0; k < 3; ++k) + val[(i * 3) + j] += m_vals[(i * 3) + k] * other[(k * 3) + j]; + + return val; +} + +// ============================================================================= +// +Matrix& Matrix::operator= (const Matrix& other) +{ + memcpy (&m_vals[0], &other.m_vals[0], sizeof m_vals); + return *this; +} + +// ============================================================================= +// +double Matrix::getDeterminant() const +{ + return (value (0) * value (4) * value (8)) + + (value (1) * value (5) * value (6)) + + (value (2) * value (3) * value (7)) - + (value (2) * value (4) * value (6)) - + (value (1) * value (3) * value (8)) - + (value (0) * value (5) * value (7)); +} + +// ============================================================================= +// +bool Matrix::operator== (const Matrix& other) const +{ + for (int i = 0; i < 9; ++i) + { + if (value (i) != other[i]) + return false; + } + + return true; +} + +// ============================================================================= +// +LDBoundingBox::LDBoundingBox() +{ + reset(); +} + +// ============================================================================= +// +void LDBoundingBox::calculateFromCurrentDocument() +{ + reset(); + + if (CurrentDocument() == null) + return; + + for (LDObjectPtr obj : CurrentDocument()->objects()) + calcObject (obj); +} + +// ============================================================================= +// +void LDBoundingBox::calcObject (LDObjectPtr obj) +{ + switch (obj->type()) + { + case OBJ_Line: + case OBJ_Triangle: + case OBJ_Quad: + case OBJ_CondLine: + { + for (int i = 0; i < obj->numVertices(); ++i) + calcVertex (obj->vertex (i)); + } break; + + case OBJ_Subfile: + { + LDSubfilePtr ref = obj.staticCast<LDSubfile>(); + LDObjectList objs = ref->inlineContents (true, false); + + for (LDObjectPtr obj : objs) + { + calcObject (obj); + obj->destroy(); + } + } + break; + + default: + break; + } +} + +// ============================================================================= +// +LDBoundingBox& LDBoundingBox::operator<< (const Vertex& v) +{ + calcVertex (v); + return *this; +} + +// ============================================================================= +// +LDBoundingBox& LDBoundingBox::operator<< (LDObjectPtr obj) +{ + calcObject (obj); + return *this; +} + +// ============================================================================= +// +void LDBoundingBox::calcVertex (const Vertex& vertex) +{ + m_vertex0.setX (Min (vertex.x(), m_vertex0.x())); + m_vertex0.setY (Min (vertex.y(), m_vertex0.y())); + m_vertex0.setZ (Min (vertex.z(), m_vertex0.z())); + m_vertex1.setX (Max (vertex.x(), m_vertex1.x())); + m_vertex1.setY (Max (vertex.y(), m_vertex1.y())); + m_vertex1.setZ (Max (vertex.z(), m_vertex1.z())); + setEmpty (false); +} + +// ============================================================================= +// +void LDBoundingBox::reset() +{ + m_vertex0 = Vertex (10000.0, 10000.0, 10000.0); + m_vertex1 = Vertex (-10000.0, -10000.0, -10000.0); + setEmpty (true); +} + +// ============================================================================= +// +double LDBoundingBox::longestMeasurement() const +{ + double xscale = (m_vertex0.x() - m_vertex1.x()); + double yscale = (m_vertex0.y() - m_vertex1.y()); + double zscale = (m_vertex0.z() - m_vertex1.z()); + double size = zscale; + + if (xscale > yscale) + { + if (xscale > zscale) + size = xscale; + } + elif (yscale > zscale) + size = yscale; + + if (Abs (size) >= 2.0) + return Abs (size / 2); + + return 1.0; +} + +// ============================================================================= +// +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/colorSelector.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,215 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * ===================================================================== - * - * colorSelectDialog.cxx: Color selector box. - */ - -#include <QGraphicsScene> -#include <QGraphicsItem> -#include <QMouseEvent> -#include <QScrollBar> -#include <QColorDialog> -#include "main.h" -#include "mainWindow.h" -#include "colorSelector.h" -#include "colors.h" -#include "configuration.h" -#include "miscallenous.h" -#include "ui_colorsel.h" - -static const int g_numColumns = 16; - -EXTERN_CFGENTRY (String, MainColor) -EXTERN_CFGENTRY (Float, MainColorAlpha) - -// ============================================================================= -// -ColorSelector::ColorSelector (LDColor defaultvalue, QWidget* parent) : - QDialog (parent) -{ - m_firstResize = true; - ui = new Ui_ColorSelUI; - ui->setupUi (this); - setSelection (defaultvalue); - - QGridLayout* layout = new QGridLayout (this); - - // Spawn color selector buttons - for (int i = 0; i < CountLDConfigColors(); ++i) - { - LDColor ldcolor = LDColor::fromIndex (i); - QPushButton* button = new QPushButton (this); - button->setMinimumSize (QSize (32, 32)); - button->setMaximumSize (button->minimumSize()); - - if (ldcolor != null) - { - QString colorname; - QColor color (ldcolor.faceColor()); - - if (i == MainColorIndex) - { - color = QColor (cfg::MainColor); - color.setAlphaF (cfg::MainColorAlpha); - } - - QString color2name (Luma (color) < 80 ? "white" : "black"); - button->setAutoFillBackground (true); - button->setStyleSheet (format ("background-color: rgba(%1, %2, %3, %4); color: %5", - color.red(), color.green(), color.blue(), color.alpha(), color2name)); - button->setCheckable (true); - button->setText (QString::number (ldcolor.index())); - button->setToolTip (format ("%1: %2", ldcolor.index(), ldcolor.name())); - m_buttons[i] = button; - m_buttonsReversed[button] = i; - connect (button, SIGNAL (clicked(bool)), this, SLOT (colorButtonClicked())); - - if (ldcolor == selection()) - button->setChecked (true); - } - else - { - button->setEnabled (false); - } - - layout->addWidget (button, i / g_numColumns, i % g_numColumns); - } - - QWidget* widget = new QWidget(); - widget->setLayout (layout); - ui->definedColors->setWidget (widget); - connect (ui->directColor, SIGNAL (clicked (bool)), this, SLOT (chooseDirectColor())); - - ui->definedColors->setMinimumWidth (ui->definedColors->widget()->width() + 16); - -#ifdef TRANSPARENT_DIRECT_COLORS - connect (ui->transparentDirectColor, SIGNAL (clicked (bool)), this, SLOT (transparentCheckboxClicked())); -#else - ui->transparentDirectColor->hide(); -#endif - - drawColorInfo(); -} - -// ============================================================================= -// -ColorSelector::~ColorSelector() -{ - delete ui; -} - -// ============================================================================= -// -void ColorSelector::colorButtonClicked() -{ - QPushButton* button = qobject_cast<QPushButton*> (sender()); - auto it = m_buttonsReversed.find (button); - LDColor color; - - if (Q_UNLIKELY (button == null or it == m_buttonsReversed.end() - or (color = LDColor::fromIndex (*it)) == null)) - { - print ("colorButtonClicked() called with invalid sender"); - return; - } - - if (selection() != null) - { - auto it2 = m_buttons.find (selection().index()); - - if (it2 != m_buttons.end()) - (*it2)->setChecked (false); - } - - setSelection (color); - button->setChecked (true); - drawColorInfo(); -} - -// ============================================================================= -// -void ColorSelector::drawColorInfo() -{ - if (selection() == null) - { - ui->colorLabel->setText ("---"); - ui->iconLabel->setPixmap (QPixmap()); - ui->transparentDirectColor->setChecked (false); - return; - } - - ui->colorLabel->setText (format ("%1 - %2", selection().indexString(), - (selection().isDirect() ? "<direct color>" : selection().name()))); - ui->iconLabel->setPixmap (MakeColorIcon (selection(), 16).pixmap (16, 16)); - -#ifdef TRANSPARENT_DIRECT_COLORS - ui->transparentDirectColor->setEnabled (selection().isDirect()); - ui->transparentDirectColor->setChecked (selection().isDirect() and selection().faceColor().alphaF() < 1.0); -#else - ui->transparentDirectColor->setChecked (false); - ui->transparentDirectColor->setEnabled (false); -#endif -} - -// ============================================================================= -// -void ColorSelector::selectDirectColor (QColor col) -{ - int32 idx = (ui->transparentDirectColor->isChecked() ? 0x03000000 : 0x02000000); - idx |= (col.red() << 16) | (col.green() << 8) | (col.blue()); - setSelection (LDColor::fromIndex (idx)); - drawColorInfo(); -} - -// ============================================================================= -// -void ColorSelector::chooseDirectColor() -{ - QColor defcolor = selection() != null ? selection().faceColor() : Qt::white; - QColor newcolor = QColorDialog::getColor (defcolor); - - if (not newcolor.isValid()) - return; // canceled - - selectDirectColor (newcolor); -} - -// ============================================================================= -// -void ColorSelector::transparentCheckboxClicked() -{ - if (selection() == null or not selection().isDirect()) - return; - - selectDirectColor (selection().faceColor()); -} - -// ============================================================================= -// -bool ColorSelector::selectColor (LDColor& val, LDColor defval, QWidget* parent) -{ - ColorSelector dlg (defval, parent); - - if (dlg.exec() and dlg.selection() != null) - { - val = dlg.selection(); - return true; - } - - return false; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colorSelector.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,215 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * ===================================================================== + * + * colorSelectDialog.cxx: Color selector box. + */ + +#include <QGraphicsScene> +#include <QGraphicsItem> +#include <QMouseEvent> +#include <QScrollBar> +#include <QColorDialog> +#include "main.h" +#include "mainWindow.h" +#include "colorSelector.h" +#include "colors.h" +#include "configuration.h" +#include "miscallenous.h" +#include "ui_colorsel.h" + +static const int g_numColumns = 16; + +EXTERN_CFGENTRY (String, MainColor) +EXTERN_CFGENTRY (Float, MainColorAlpha) + +// ============================================================================= +// +ColorSelector::ColorSelector (LDColor defaultvalue, QWidget* parent) : + QDialog (parent) +{ + m_firstResize = true; + ui = new Ui_ColorSelUI; + ui->setupUi (this); + setSelection (defaultvalue); + + QGridLayout* layout = new QGridLayout (this); + + // Spawn color selector buttons + for (int i = 0; i < CountLDConfigColors(); ++i) + { + LDColor ldcolor = LDColor::fromIndex (i); + QPushButton* button = new QPushButton (this); + button->setMinimumSize (QSize (32, 32)); + button->setMaximumSize (button->minimumSize()); + + if (ldcolor != null) + { + QString colorname; + QColor color (ldcolor.faceColor()); + + if (i == MainColorIndex) + { + color = QColor (cfg::MainColor); + color.setAlphaF (cfg::MainColorAlpha); + } + + QString color2name (Luma (color) < 80 ? "white" : "black"); + button->setAutoFillBackground (true); + button->setStyleSheet (format ("background-color: rgba(%1, %2, %3, %4); color: %5", + color.red(), color.green(), color.blue(), color.alpha(), color2name)); + button->setCheckable (true); + button->setText (QString::number (ldcolor.index())); + button->setToolTip (format ("%1: %2", ldcolor.index(), ldcolor.name())); + m_buttons[i] = button; + m_buttonsReversed[button] = i; + connect (button, SIGNAL (clicked(bool)), this, SLOT (colorButtonClicked())); + + if (ldcolor == selection()) + button->setChecked (true); + } + else + { + button->setEnabled (false); + } + + layout->addWidget (button, i / g_numColumns, i % g_numColumns); + } + + QWidget* widget = new QWidget(); + widget->setLayout (layout); + ui->definedColors->setWidget (widget); + connect (ui->directColor, SIGNAL (clicked (bool)), this, SLOT (chooseDirectColor())); + + ui->definedColors->setMinimumWidth (ui->definedColors->widget()->width() + 16); + +#ifdef TRANSPARENT_DIRECT_COLORS + connect (ui->transparentDirectColor, SIGNAL (clicked (bool)), this, SLOT (transparentCheckboxClicked())); +#else + ui->transparentDirectColor->hide(); +#endif + + drawColorInfo(); +} + +// ============================================================================= +// +ColorSelector::~ColorSelector() +{ + delete ui; +} + +// ============================================================================= +// +void ColorSelector::colorButtonClicked() +{ + QPushButton* button = qobject_cast<QPushButton*> (sender()); + auto it = m_buttonsReversed.find (button); + LDColor color; + + if (Q_UNLIKELY (button == null or it == m_buttonsReversed.end() + or (color = LDColor::fromIndex (*it)) == null)) + { + print ("colorButtonClicked() called with invalid sender"); + return; + } + + if (selection() != null) + { + auto it2 = m_buttons.find (selection().index()); + + if (it2 != m_buttons.end()) + (*it2)->setChecked (false); + } + + setSelection (color); + button->setChecked (true); + drawColorInfo(); +} + +// ============================================================================= +// +void ColorSelector::drawColorInfo() +{ + if (selection() == null) + { + ui->colorLabel->setText ("---"); + ui->iconLabel->setPixmap (QPixmap()); + ui->transparentDirectColor->setChecked (false); + return; + } + + ui->colorLabel->setText (format ("%1 - %2", selection().indexString(), + (selection().isDirect() ? "<direct color>" : selection().name()))); + ui->iconLabel->setPixmap (MakeColorIcon (selection(), 16).pixmap (16, 16)); + +#ifdef TRANSPARENT_DIRECT_COLORS + ui->transparentDirectColor->setEnabled (selection().isDirect()); + ui->transparentDirectColor->setChecked (selection().isDirect() and selection().faceColor().alphaF() < 1.0); +#else + ui->transparentDirectColor->setChecked (false); + ui->transparentDirectColor->setEnabled (false); +#endif +} + +// ============================================================================= +// +void ColorSelector::selectDirectColor (QColor col) +{ + int32 idx = (ui->transparentDirectColor->isChecked() ? 0x03000000 : 0x02000000); + idx |= (col.red() << 16) | (col.green() << 8) | (col.blue()); + setSelection (LDColor::fromIndex (idx)); + drawColorInfo(); +} + +// ============================================================================= +// +void ColorSelector::chooseDirectColor() +{ + QColor defcolor = selection() != null ? selection().faceColor() : Qt::white; + QColor newcolor = QColorDialog::getColor (defcolor); + + if (not newcolor.isValid()) + return; // canceled + + selectDirectColor (newcolor); +} + +// ============================================================================= +// +void ColorSelector::transparentCheckboxClicked() +{ + if (selection() == null or not selection().isDirect()) + return; + + selectDirectColor (selection().faceColor()); +} + +// ============================================================================= +// +bool ColorSelector::selectColor (LDColor& val, LDColor defval, QWidget* parent) +{ + ColorSelector dlg (defval, parent); + + if (dlg.exec() and dlg.selection() != null) + { + val = dlg.selection(); + return true; + } + + return false; +}
--- a/src/colors.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "main.h" -#include "colors.h" -#include "ldDocument.h" -#include "miscallenous.h" -#include "mainWindow.h" -#include "ldConfig.h" -#include <QColor> - -static LDColor g_LDConfigColors[512]; - -void InitColors() -{ - LDColorData* col; - print ("Initializing color information.\n"); - - // Always make sure there's 16 and 24 available. They're special like that. - col = new LDColorData; - col->m_faceColor = - col->m_hexcode = "#AAAAAA"; - col->m_edgeColor = Qt::black; - g_LDConfigColors[16] = col; - - col = new LDColorData; - col->m_faceColor = - col->m_edgeColor = - col->m_hexcode = "#000000"; - g_LDConfigColors[24] = col; - - LDConfigParser::parseLDConfig(); -} - -LDColor MainColor() -{ - return g_LDConfigColors[MainColorIndex]; -} - -LDColor EdgeColor() -{ - return g_LDConfigColors[EdgeColorIndex]; -} - -void LDColor::addLDConfigColor (qint32 index, LDColor color) -{ - assert (index >= 0 and index < countof (g_LDConfigColors)); - g_LDConfigColors[index] = color; -} - -LDColor LDColor::fromIndex (qint32 index) -{ - if (index < countof (g_LDConfigColors) and g_LDConfigColors[index] != null) - return g_LDConfigColors[index]; - - if (index >= 0x2000000) - { - // Direct color - QColor col; - col.setRed ((index & 0x0FF0000) >> 16); - col.setGreen ((index & 0x000FF00) >> 8); - col.setBlue (index & 0x00000FF); - - if (index >= 0x3000000) - col.setAlpha (128); - - LDColorData* color = new LDColorData; - color->m_name = "0x" + QString::number (index, 16).toUpper(); - color->m_faceColor = col; - color->m_edgeColor = Luma (col) < 48 ? Qt::white : Qt::black; - color->m_hexcode = col.name(); - color->m_index = index; - return LDColor (color); - } - - return null; -} - -QString LDColor::indexString() const -{ - if (isDirect()) - return "0x" + QString::number (index(), 16).toUpper(); - - return QString::number (index()); -} - -bool LDColor::isDirect() const -{ - return index() >= 0x02000000; -} - -bool LDColor::operator== (LDColor const& other) -{ - if ((data() == nullptr) ^ (other == nullptr)) - return false; - - if (data() != nullptr) - return index() == other.index(); - - // both are null - return true; -} - -int Luma (const QColor& col) -{ - return (0.2126f * col.red()) + - (0.7152f * col.green()) + - (0.0722f * col.blue()); -} - -int CountLDConfigColors() -{ - return countof (g_LDConfigColors); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colors.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,129 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "main.h" +#include "colors.h" +#include "ldDocument.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "ldConfig.h" +#include <QColor> + +static LDColor g_LDConfigColors[512]; + +void InitColors() +{ + LDColorData* col; + print ("Initializing color information.\n"); + + // Always make sure there's 16 and 24 available. They're special like that. + col = new LDColorData; + col->m_faceColor = + col->m_hexcode = "#AAAAAA"; + col->m_edgeColor = Qt::black; + g_LDConfigColors[16] = col; + + col = new LDColorData; + col->m_faceColor = + col->m_edgeColor = + col->m_hexcode = "#000000"; + g_LDConfigColors[24] = col; + + LDConfigParser::parseLDConfig(); +} + +LDColor MainColor() +{ + return g_LDConfigColors[MainColorIndex]; +} + +LDColor EdgeColor() +{ + return g_LDConfigColors[EdgeColorIndex]; +} + +void LDColor::addLDConfigColor (qint32 index, LDColor color) +{ + assert (index >= 0 and index < countof (g_LDConfigColors)); + g_LDConfigColors[index] = color; +} + +LDColor LDColor::fromIndex (qint32 index) +{ + if (index < countof (g_LDConfigColors) and g_LDConfigColors[index] != null) + return g_LDConfigColors[index]; + + if (index >= 0x2000000) + { + // Direct color + QColor col; + col.setRed ((index & 0x0FF0000) >> 16); + col.setGreen ((index & 0x000FF00) >> 8); + col.setBlue (index & 0x00000FF); + + if (index >= 0x3000000) + col.setAlpha (128); + + LDColorData* color = new LDColorData; + color->m_name = "0x" + QString::number (index, 16).toUpper(); + color->m_faceColor = col; + color->m_edgeColor = Luma (col) < 48 ? Qt::white : Qt::black; + color->m_hexcode = col.name(); + color->m_index = index; + return LDColor (color); + } + + return null; +} + +QString LDColor::indexString() const +{ + if (isDirect()) + return "0x" + QString::number (index(), 16).toUpper(); + + return QString::number (index()); +} + +bool LDColor::isDirect() const +{ + return index() >= 0x02000000; +} + +bool LDColor::operator== (LDColor const& other) +{ + if ((data() == nullptr) ^ (other == nullptr)) + return false; + + if (data() != nullptr) + return index() == other.index(); + + // both are null + return true; +} + +int Luma (const QColor& col) +{ + return (0.2126f * col.red()) + + (0.7152f * col.green()) + + (0.0722f * col.blue()); +} + +int CountLDConfigColors() +{ + return countof (g_LDConfigColors); +}
--- a/src/configDialog.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,735 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * ===================================================================== - * - * configDialog.cxx: Settings dialog and everything related to it. - * Actual configuration core is in config.cxx. - */ - -#include <QGridLayout> -#include <QFileDialog> -#include <QColorDialog> -#include <QBoxLayout> -#include <QKeyEvent> -#include <QGroupBox> -#include <QDoubleSpinBox> -#include <QLineEdit> -#include <QCheckBox> -#include "main.h" -#include "configDialog.h" -#include "ldDocument.h" -#include "configuration.h" -#include "miscallenous.h" -#include "colors.h" -#include "colorSelector.h" -#include "glRenderer.h" -#include "ui_config.h" - -EXTERN_CFGENTRY (String, YtruderPath) -EXTERN_CFGENTRY (String, RectifierPath) -EXTERN_CFGENTRY (String, IntersectorPath) -EXTERN_CFGENTRY (String, CovererPath) -EXTERN_CFGENTRY (String, IsecalcPath) -EXTERN_CFGENTRY (String, Edger2Path) -EXTERN_CFGENTRY (Bool, YtruderUsesWine) -EXTERN_CFGENTRY (Bool, RectifierUsesWine) -EXTERN_CFGENTRY (Bool, IntersectorUsesWine) -EXTERN_CFGENTRY (Bool, CovererUsesWine) -EXTERN_CFGENTRY (Bool, IsecalcUsesWine) -EXTERN_CFGENTRY (Bool, Edger2UsesWine) -EXTERN_CFGENTRY (String, QuickColorToolbar) - -const char* g_extProgPathFilter = -#ifdef _WIN32 - "Applications (*.exe)(*.exe);;" -#endif - "All files (*.*)(*.*)"; - -// -// -static struct LDExtProgInfo -{ - QString const name; - QString const iconname; - QString* const path; - QLineEdit* input; - QPushButton* setPathButton; - bool* const wine; - QCheckBox* wineBox; -} g_LDExtProgInfo[] = -{ -#ifndef _WIN32 -# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &cfg::NAME##Path, null, null, \ - &cfg::NAME##UsesWine, null }, -#else -# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &cfg::NAME##Path, null, null, null, null }, -#endif - EXTPROG (Ytruder, ytruder) - EXTPROG (Rectifier, rectifier) - EXTPROG (Intersector, intersector) - EXTPROG (Isecalc, isecalc) - EXTPROG (Coverer, coverer) - EXTPROG (Edger2, edger2) -#undef EXTPROG -}; - -// -// -ConfigDialog::ConfigDialog (ConfigDialog::Tab deftab, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f) -{ - assert (g_win != null); - ui = new Ui_ConfigUI; - ui->setupUi (this); - - // Set defaults - m_applyToWidgetOptions ([&](QWidget* wdg, AbstractConfigEntry* conf) - { - QVariant value (conf->toVariant()); - QLineEdit* le; - QSpinBox* spinbox; - QDoubleSpinBox* doublespinbox; - QSlider* slider; - QCheckBox* checkbox; - QPushButton* button; - - if ((le = qobject_cast<QLineEdit*> (wdg)) != null) - { - le->setText (value.toString()); - } - elif ((spinbox = qobject_cast<QSpinBox*> (wdg)) != null) - { - spinbox->setValue (value.toInt()); - } - elif ((doublespinbox = qobject_cast<QDoubleSpinBox*> (wdg)) != null) - { - doublespinbox->setValue (value.toDouble()); - } - elif ((slider = qobject_cast<QSlider*> (wdg)) != null) - { - slider->setValue (value.toInt()); - } - elif ((checkbox = qobject_cast<QCheckBox*> (wdg)) != null) - { - checkbox->setChecked (value.toBool()); - } - elif ((button = qobject_cast<QPushButton*> (wdg)) != null) - { - setButtonBackground (button, value.toString()); - connect (button, SIGNAL (clicked()), this, SLOT (setButtonColor())); - } - else - { - print ("Unknown widget of type %1\n", wdg->metaObject()->className()); - } - }); - - g_win->applyToActions ([&](QAction* act) - { - addShortcut (act); - }); - - ui->shortcutsList->setSortingEnabled (true); - ui->shortcutsList->sortItems(); - quickColors = LoadQuickColorList(); - updateQuickColorList(); - initExtProgs(); - selectPage (deftab); - 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())); - 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())); - connect (ui->findDownloadPath, SIGNAL (clicked (bool)), this, SLOT (slot_findDownloadFolder())); - 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 (QAction* act) -{ - ShortcutListItem* item = new ShortcutListItem; - item->setIcon (act->icon()); - item->setAction (act); - item->setSequence (act->shortcut()); - 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 (ui->shortcutsList->count(), item); -} - -// -// 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); - - if (info.wine != null) - { - QCheckBox* wineBox = new QCheckBox ("Wine"); - wineBox->setChecked (*info.wine); - info.wineBox = wineBox; - pathsLayout->addWidget (wineBox, row, 4); - } - - ++row; - } - - ui->extProgs->setLayout (pathsLayout); -} - -void ConfigDialog::m_applyToWidgetOptions (std::function<void (QWidget*, AbstractConfigEntry*)> func) -{ - // Apply configuration - for (QWidget* widget : findChildren<QWidget*>()) - { - if (not widget->objectName().startsWith ("config")) - continue; - - QString confname (widget->objectName().mid (strlen ("config"))); - AbstractConfigEntry* conf (Config::FindByName (confname)); - - if (conf == null) - { - print ("Couldn't find configuration entry named %1", confname); - continue; - } - - func (widget, conf); - } -} - -// -// Set the settings based on widget data. -// -void ConfigDialog::applySettings() -{ - m_applyToWidgetOptions ([&](QWidget* widget, AbstractConfigEntry* conf) - { - QVariant value (conf->toVariant()); - QLineEdit* le; - QSpinBox* spinbox; - QDoubleSpinBox* doublespinbox; - QSlider* slider; - QCheckBox* checkbox; - QPushButton* button; - - if ((le = qobject_cast<QLineEdit*> (widget)) != null) - value = le->text(); - elif ((spinbox = qobject_cast<QSpinBox*> (widget)) != null) - value = spinbox->value(); - elif ((doublespinbox = qobject_cast<QDoubleSpinBox*> (widget)) != null) - value = doublespinbox->value(); - elif ((slider = qobject_cast<QSlider*> (widget)) != null) - value = slider->value(); - elif ((checkbox = qobject_cast<QCheckBox*> (widget)) != null) - value = checkbox->isChecked(); - elif ((button = qobject_cast<QPushButton*> (widget)) != null) - value = m_buttonColors[button]; - else - print ("Unknown widget of type %1\n", widget->metaObject()->className()); - - conf->loadFromVariant (value); - }); - - // Rebuild the quick color toolbar - g_win->setQuickColors (quickColors); - cfg::QuickColorToolbar = quickColorString(); - - // Ext program settings - for (const LDExtProgInfo& info : g_LDExtProgInfo) - { - *info.path = info.input->text(); - - if (info.wine != null) - *info.wine = info.wineBox->isChecked(); - } - - // Apply shortcuts - for (int i = 0; i < ui->shortcutsList->count(); ++i) - { - auto item = static_cast<ShortcutListItem*> (ui->shortcutsList->item (i)); - item->action()->setShortcut (item->sequence()); - } - - Config::Save(); - LDDocument::current()->reloadAllSubfiles(); - LoadLogoStuds(); - g_win->R()->setBackground(); - g_win->doFullRefresh(); - g_win->updateDocumentList(); -} - -// -// A dialog button was clicked -// -void ConfigDialog::buttonClicked (QAbstractButton* button) -{ - QDialogButtonBox* dbb = ui->buttonBox; - - if (button == dbb->button (QDialogButtonBox::Ok)) - { - applySettings(); - accept(); - } - elif (button == dbb->button (QDialogButtonBox::Apply)) - { - applySettings(); - } - elif (button == dbb->button (QDialogButtonBox::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 ("<hr />"); - 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 and &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 (not isNew) - { - item = getSelectedQuickColor(); - - if (not item) - return; - - int i = getItemRow (item, quickColorItems); - entry = &quickColors[i]; - - if (entry->isSeparator() == true) - return; // don't color separators - } - - LDColor defval = entry ? entry->color() : null; - LDColor val; - - if (not ColorSelector::selectColor (val, defval, this)) - return; - - if (entry != null) - { - entry->setColor (val); - } - else - { - LDQuickColor entry (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 or dest >= quickColorItems.size()) - return; // destination out of bounds - - qSwap (quickColors[dest], quickColors[idx]); - 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(); -} - -// -// -void ConfigDialog::setButtonColor() -{ - QPushButton* button = qobject_cast<QPushButton*> (sender()); - - if (button == null) - { - print ("setButtonColor: null sender!\n"); - return; - } - - QColor color = QColorDialog::getColor (m_buttonColors[button]); - - if (color.isValid()) - { - QString colorname; - colorname.sprintf ("#%.2X%.2X%.2X", color.red(), color.green(), color.blue()); - setButtonBackground (button, colorname); - } -} - -// -// Sets background color of a given button. -// -void ConfigDialog::setButtonBackground (QPushButton* button, QString value) -{ - button->setIcon (GetIcon ("colorselect")); - button->setAutoFillBackground (true); - button->setStyleSheet (format ("background-color: %1", value)); - m_buttonColors[button] = QColor (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, this)) - setShortcutText (item); -} - -// -// Reset a shortcut to defaults -// -void ConfigDialog::slot_resetShortcut() -{ - QList<ShortcutListItem*> sel = getShortcutSelection(); - - for (ShortcutListItem* item : sel) - { - item->setSequence (MainWindow::defaultShortcut (item->action())); - setShortcutText (item); - } -} - -// -// Remove the shortcut of an action. -// -void ConfigDialog::slot_clearShortcut() -{ - QList<ShortcutListItem*> sel = getShortcutSelection(); - - for (ShortcutListItem* item : sel) - { - item->setSequence (QKeySequence()); - setShortcutText (item); - } -} - -// -// Set the path of an external program -// -void ConfigDialog::slot_setExtProgPath() -{ - const LDExtProgInfo* info = null; - - for (const LDExtProgInfo& it : g_LDExtProgInfo) - { - if (it.setPathButton == sender()) - { - info = ⁢ - break; - } - } - - assert (info != null); - QString fpath = QFileDialog::getOpenFileName (this, format ("Path to %1", info->name), *info->path, g_extProgPathFilter); - - if (fpath.isEmpty()) - return; - - info->input->setText (fpath); -} - -// -// '...' button pressed for the download path -// -void ConfigDialog::slot_findDownloadFolder() -{ - QString dpath = QFileDialog::getExistingDirectory(); - - if (not dpath.isEmpty()) - ui->configDownloadFilePath->setText (dpath); -} - -// -// -// Updates the text string for a given shortcut list item -// -void ConfigDialog::setShortcutText (ShortcutListItem* item) -{ - QAction* act = item->action(); - QString label = act->iconText(); - QString keybind = item->sequence().toString(); - item->setText (format ("%1 (%2)", label, keybind)); -} - -// -// Gets the configuration string of the quick color toolbar -// -QString ConfigDialog::quickColorString() -{ - QString val; - - for (const LDQuickColor& entry : quickColors) - { - if (val.length() > 0) - val += ':'; - - if (entry.isSeparator()) - val += '|'; - else - val += format ("%1", entry.color().index()); - } - - return val; -} - -// -// -KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f), seq (seq) -{ - lb_output = new QLabel; - IMPLEMENT_DIALOG_BUTTONS - - setWhatsThis (tr ("Into this dialog you can input a key sequence for use as a " - "shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to " - "dismiss.")); - - QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (lb_output); - layout->addWidget (bbx_buttons); - setLayout (layout); - - updateOutput(); -} - -// -// -bool KeySequenceDialog::staticDialog (ShortcutListItem* item, QWidget* parent) -{ - KeySequenceDialog dlg (item->sequence(), parent); - - if (dlg.exec() == QDialog::Rejected) - return false; - - item->setSequence (dlg.seq); - return true; -} - -// -// -void KeySequenceDialog::updateOutput() -{ - QString shortcut = seq.toString(); - - if (seq == QKeySequence()) - shortcut = "<empty>"; - - QString text = format ("<center><b>%1</b></center>", shortcut); - lb_output->setText (text); -} - -// -// -void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) -{ - seq = ev->key() + ev->modifiers(); - updateOutput(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/configDialog.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,735 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * ===================================================================== + * + * configDialog.cxx: Settings dialog and everything related to it. + * Actual configuration core is in config.cxx. + */ + +#include <QGridLayout> +#include <QFileDialog> +#include <QColorDialog> +#include <QBoxLayout> +#include <QKeyEvent> +#include <QGroupBox> +#include <QDoubleSpinBox> +#include <QLineEdit> +#include <QCheckBox> +#include "main.h" +#include "configDialog.h" +#include "ldDocument.h" +#include "configuration.h" +#include "miscallenous.h" +#include "colors.h" +#include "colorSelector.h" +#include "glRenderer.h" +#include "ui_config.h" + +EXTERN_CFGENTRY (String, YtruderPath) +EXTERN_CFGENTRY (String, RectifierPath) +EXTERN_CFGENTRY (String, IntersectorPath) +EXTERN_CFGENTRY (String, CovererPath) +EXTERN_CFGENTRY (String, IsecalcPath) +EXTERN_CFGENTRY (String, Edger2Path) +EXTERN_CFGENTRY (Bool, YtruderUsesWine) +EXTERN_CFGENTRY (Bool, RectifierUsesWine) +EXTERN_CFGENTRY (Bool, IntersectorUsesWine) +EXTERN_CFGENTRY (Bool, CovererUsesWine) +EXTERN_CFGENTRY (Bool, IsecalcUsesWine) +EXTERN_CFGENTRY (Bool, Edger2UsesWine) +EXTERN_CFGENTRY (String, QuickColorToolbar) + +const char* g_extProgPathFilter = +#ifdef _WIN32 + "Applications (*.exe)(*.exe);;" +#endif + "All files (*.*)(*.*)"; + +// +// +static struct LDExtProgInfo +{ + QString const name; + QString const iconname; + QString* const path; + QLineEdit* input; + QPushButton* setPathButton; + bool* const wine; + QCheckBox* wineBox; +} g_LDExtProgInfo[] = +{ +#ifndef _WIN32 +# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &cfg::NAME##Path, null, null, \ + &cfg::NAME##UsesWine, null }, +#else +# define EXTPROG(NAME, LOWNAME) { #NAME, #LOWNAME, &cfg::NAME##Path, null, null, null, null }, +#endif + EXTPROG (Ytruder, ytruder) + EXTPROG (Rectifier, rectifier) + EXTPROG (Intersector, intersector) + EXTPROG (Isecalc, isecalc) + EXTPROG (Coverer, coverer) + EXTPROG (Edger2, edger2) +#undef EXTPROG +}; + +// +// +ConfigDialog::ConfigDialog (ConfigDialog::Tab deftab, QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f) +{ + assert (g_win != null); + ui = new Ui_ConfigUI; + ui->setupUi (this); + + // Set defaults + m_applyToWidgetOptions ([&](QWidget* wdg, AbstractConfigEntry* conf) + { + QVariant value (conf->toVariant()); + QLineEdit* le; + QSpinBox* spinbox; + QDoubleSpinBox* doublespinbox; + QSlider* slider; + QCheckBox* checkbox; + QPushButton* button; + + if ((le = qobject_cast<QLineEdit*> (wdg)) != null) + { + le->setText (value.toString()); + } + elif ((spinbox = qobject_cast<QSpinBox*> (wdg)) != null) + { + spinbox->setValue (value.toInt()); + } + elif ((doublespinbox = qobject_cast<QDoubleSpinBox*> (wdg)) != null) + { + doublespinbox->setValue (value.toDouble()); + } + elif ((slider = qobject_cast<QSlider*> (wdg)) != null) + { + slider->setValue (value.toInt()); + } + elif ((checkbox = qobject_cast<QCheckBox*> (wdg)) != null) + { + checkbox->setChecked (value.toBool()); + } + elif ((button = qobject_cast<QPushButton*> (wdg)) != null) + { + setButtonBackground (button, value.toString()); + connect (button, SIGNAL (clicked()), this, SLOT (setButtonColor())); + } + else + { + print ("Unknown widget of type %1\n", wdg->metaObject()->className()); + } + }); + + g_win->applyToActions ([&](QAction* act) + { + addShortcut (act); + }); + + ui->shortcutsList->setSortingEnabled (true); + ui->shortcutsList->sortItems(); + quickColors = LoadQuickColorList(); + updateQuickColorList(); + initExtProgs(); + selectPage (deftab); + 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())); + 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())); + connect (ui->findDownloadPath, SIGNAL (clicked (bool)), this, SLOT (slot_findDownloadFolder())); + 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 (QAction* act) +{ + ShortcutListItem* item = new ShortcutListItem; + item->setIcon (act->icon()); + item->setAction (act); + item->setSequence (act->shortcut()); + 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 (ui->shortcutsList->count(), item); +} + +// +// 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); + + if (info.wine != null) + { + QCheckBox* wineBox = new QCheckBox ("Wine"); + wineBox->setChecked (*info.wine); + info.wineBox = wineBox; + pathsLayout->addWidget (wineBox, row, 4); + } + + ++row; + } + + ui->extProgs->setLayout (pathsLayout); +} + +void ConfigDialog::m_applyToWidgetOptions (std::function<void (QWidget*, AbstractConfigEntry*)> func) +{ + // Apply configuration + for (QWidget* widget : findChildren<QWidget*>()) + { + if (not widget->objectName().startsWith ("config")) + continue; + + QString confname (widget->objectName().mid (strlen ("config"))); + AbstractConfigEntry* conf (Config::FindByName (confname)); + + if (conf == null) + { + print ("Couldn't find configuration entry named %1", confname); + continue; + } + + func (widget, conf); + } +} + +// +// Set the settings based on widget data. +// +void ConfigDialog::applySettings() +{ + m_applyToWidgetOptions ([&](QWidget* widget, AbstractConfigEntry* conf) + { + QVariant value (conf->toVariant()); + QLineEdit* le; + QSpinBox* spinbox; + QDoubleSpinBox* doublespinbox; + QSlider* slider; + QCheckBox* checkbox; + QPushButton* button; + + if ((le = qobject_cast<QLineEdit*> (widget)) != null) + value = le->text(); + elif ((spinbox = qobject_cast<QSpinBox*> (widget)) != null) + value = spinbox->value(); + elif ((doublespinbox = qobject_cast<QDoubleSpinBox*> (widget)) != null) + value = doublespinbox->value(); + elif ((slider = qobject_cast<QSlider*> (widget)) != null) + value = slider->value(); + elif ((checkbox = qobject_cast<QCheckBox*> (widget)) != null) + value = checkbox->isChecked(); + elif ((button = qobject_cast<QPushButton*> (widget)) != null) + value = m_buttonColors[button]; + else + print ("Unknown widget of type %1\n", widget->metaObject()->className()); + + conf->loadFromVariant (value); + }); + + // Rebuild the quick color toolbar + g_win->setQuickColors (quickColors); + cfg::QuickColorToolbar = quickColorString(); + + // Ext program settings + for (const LDExtProgInfo& info : g_LDExtProgInfo) + { + *info.path = info.input->text(); + + if (info.wine != null) + *info.wine = info.wineBox->isChecked(); + } + + // Apply shortcuts + for (int i = 0; i < ui->shortcutsList->count(); ++i) + { + auto item = static_cast<ShortcutListItem*> (ui->shortcutsList->item (i)); + item->action()->setShortcut (item->sequence()); + } + + Config::Save(); + LDDocument::current()->reloadAllSubfiles(); + LoadLogoStuds(); + g_win->R()->setBackground(); + g_win->doFullRefresh(); + g_win->updateDocumentList(); +} + +// +// A dialog button was clicked +// +void ConfigDialog::buttonClicked (QAbstractButton* button) +{ + QDialogButtonBox* dbb = ui->buttonBox; + + if (button == dbb->button (QDialogButtonBox::Ok)) + { + applySettings(); + accept(); + } + elif (button == dbb->button (QDialogButtonBox::Apply)) + { + applySettings(); + } + elif (button == dbb->button (QDialogButtonBox::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 ("<hr />"); + 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 and &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 (not isNew) + { + item = getSelectedQuickColor(); + + if (not item) + return; + + int i = getItemRow (item, quickColorItems); + entry = &quickColors[i]; + + if (entry->isSeparator() == true) + return; // don't color separators + } + + LDColor defval = entry ? entry->color() : null; + LDColor val; + + if (not ColorSelector::selectColor (val, defval, this)) + return; + + if (entry != null) + { + entry->setColor (val); + } + else + { + LDQuickColor entry (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 or dest >= quickColorItems.size()) + return; // destination out of bounds + + qSwap (quickColors[dest], quickColors[idx]); + 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(); +} + +// +// +void ConfigDialog::setButtonColor() +{ + QPushButton* button = qobject_cast<QPushButton*> (sender()); + + if (button == null) + { + print ("setButtonColor: null sender!\n"); + return; + } + + QColor color = QColorDialog::getColor (m_buttonColors[button]); + + if (color.isValid()) + { + QString colorname; + colorname.sprintf ("#%.2X%.2X%.2X", color.red(), color.green(), color.blue()); + setButtonBackground (button, colorname); + } +} + +// +// Sets background color of a given button. +// +void ConfigDialog::setButtonBackground (QPushButton* button, QString value) +{ + button->setIcon (GetIcon ("colorselect")); + button->setAutoFillBackground (true); + button->setStyleSheet (format ("background-color: %1", value)); + m_buttonColors[button] = QColor (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, this)) + setShortcutText (item); +} + +// +// Reset a shortcut to defaults +// +void ConfigDialog::slot_resetShortcut() +{ + QList<ShortcutListItem*> sel = getShortcutSelection(); + + for (ShortcutListItem* item : sel) + { + item->setSequence (MainWindow::defaultShortcut (item->action())); + setShortcutText (item); + } +} + +// +// Remove the shortcut of an action. +// +void ConfigDialog::slot_clearShortcut() +{ + QList<ShortcutListItem*> sel = getShortcutSelection(); + + for (ShortcutListItem* item : sel) + { + item->setSequence (QKeySequence()); + setShortcutText (item); + } +} + +// +// Set the path of an external program +// +void ConfigDialog::slot_setExtProgPath() +{ + const LDExtProgInfo* info = null; + + for (const LDExtProgInfo& it : g_LDExtProgInfo) + { + if (it.setPathButton == sender()) + { + info = ⁢ + break; + } + } + + assert (info != null); + QString fpath = QFileDialog::getOpenFileName (this, format ("Path to %1", info->name), *info->path, g_extProgPathFilter); + + if (fpath.isEmpty()) + return; + + info->input->setText (fpath); +} + +// +// '...' button pressed for the download path +// +void ConfigDialog::slot_findDownloadFolder() +{ + QString dpath = QFileDialog::getExistingDirectory(); + + if (not dpath.isEmpty()) + ui->configDownloadFilePath->setText (dpath); +} + +// +// +// Updates the text string for a given shortcut list item +// +void ConfigDialog::setShortcutText (ShortcutListItem* item) +{ + QAction* act = item->action(); + QString label = act->iconText(); + QString keybind = item->sequence().toString(); + item->setText (format ("%1 (%2)", label, keybind)); +} + +// +// Gets the configuration string of the quick color toolbar +// +QString ConfigDialog::quickColorString() +{ + QString val; + + for (const LDQuickColor& entry : quickColors) + { + if (val.length() > 0) + val += ':'; + + if (entry.isSeparator()) + val += '|'; + else + val += format ("%1", entry.color().index()); + } + + return val; +} + +// +// +KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f), seq (seq) +{ + lb_output = new QLabel; + IMPLEMENT_DIALOG_BUTTONS + + setWhatsThis (tr ("Into this dialog you can input a key sequence for use as a " + "shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to " + "dismiss.")); + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget (lb_output); + layout->addWidget (bbx_buttons); + setLayout (layout); + + updateOutput(); +} + +// +// +bool KeySequenceDialog::staticDialog (ShortcutListItem* item, QWidget* parent) +{ + KeySequenceDialog dlg (item->sequence(), parent); + + if (dlg.exec() == QDialog::Rejected) + return false; + + item->setSequence (dlg.seq); + return true; +} + +// +// +void KeySequenceDialog::updateOutput() +{ + QString shortcut = seq.toString(); + + if (seq == QKeySequence()) + shortcut = "<empty>"; + + QString text = format ("<center><b>%1</b></center>", shortcut); + lb_output->setText (text); +} + +// +// +void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) +{ + seq = ev->key() + ev->modifiers(); + updateOutput(); +}
--- a/src/configuration.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,230 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * ===================================================================== - * - * config.cxx: Configuration management. I don't like how unsafe QSettings - * is so this implements a type-safer and identifer-safer wrapping system of - * configuration variables. QSettings is used underlyingly, this is a matter - * of interface. - */ - -#include <errno.h> -#include <QDir> -#include <QTextStream> -#include <QSettings> -#include "main.h" -#include "configuration.h" -#include "miscallenous.h" -#include "mainWindow.h" -#include "ldDocument.h" -#include "glRenderer.h" -#include "configuration.inc" - -#ifdef _WIN32 -# define EXTENSION ".ini" -#else -# define EXTENSION ".cfg" -#endif // _WIN32 - -#define MAX_CONFIG 512 - -static QMap<QString, AbstractConfigEntry*> EntriesByName; -static QList<AbstractConfigEntry*> ConfigurationEntries; - -AbstractConfigEntry::AbstractConfigEntry (QString name) : - m_name (name) {} - -void Config::Initialize() -{ - SetupConfigurationLists(); - print ("Configuration initialized with %1 entries\n", ConfigurationEntries.size()); -} - -static void InitConfigurationEntry (AbstractConfigEntry* entry) -{ - ConfigurationEntries << entry; - EntriesByName[entry->name()] = entry; -} - -// -// Load the configuration from file -// -bool Config::Load() -{ - QSettings* settings = SettingsObject(); - print ("Loading configuration file from %1\n", settings->fileName()); - - for (AbstractConfigEntry* cfg : ConfigurationEntries) - { - if (cfg == null) - break; - - QVariant val = settings->value (cfg->name(), cfg->getDefaultAsVariant()); - cfg->loadFromVariant (val); - } - - if (g_win != null) - g_win->loadShortcuts (settings); - - delete settings; - return true; -} - -// -// Save the configuration to disk -// -bool Config::Save() -{ - QSettings* settings = SettingsObject(); - - for (AbstractConfigEntry* cfg : ConfigurationEntries) - { - if (not cfg->isDefault()) - settings->setValue (cfg->name(), cfg->toVariant()); - else - settings->remove (cfg->name()); - } - - if (g_win != null) - g_win->saveShortcuts (settings); - - settings->sync(); - print ("Configuration saved to %1.\n", settings->fileName()); - delete settings; - return true; -} - -// -// Reset configuration to defaults. -// -void Config::ResetToDefaults() -{ - for (AbstractConfigEntry* cfg : ConfigurationEntries) - cfg->resetValue(); -} - -// -// Where is the configuration file located at? -// -QString Config::FilePath (QString file) -{ - return Config::DirectoryPath() + DIRSLASH + file; -} - -// -// Directory of the configuration file. -// -QString Config::DirectoryPath() -{ - QSettings* settings = SettingsObject(); - QString result = Dirname (settings->fileName()); - delete settings; - return result; -} - -// -// Accessor to the settings object -// -QSettings* Config::SettingsObject() -{ - QString path = qApp->applicationDirPath() + "/" UNIXNAME EXTENSION; - return new QSettings (path, QSettings::IniFormat); -} - -// -// Accessor to entry list -// -QList<AbstractConfigEntry*> const& Config::AllConfigEntries() -{ - return ConfigurationEntries; -} - -AbstractConfigEntry* Config::FindByName (QString const& name) -{ - auto it = EntriesByName.find (name); - return (it != EntriesByName.end()) ? *it : null; -} - -template<typename T> -static T* GetConfigByName (QString name, AbstractConfigEntry::Type type) -{ - auto it = EntriesByName.find (name); - - if (it == EntriesByName.end()) - return null; - - AbstractConfigEntry* 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##ConfigEntry* NAME##ConfigEntry::getByName (QString name) \ - { \ - return GetConfigByName<NAME##ConfigEntry> (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) - -void IntConfigEntry::loadFromVariant (const QVariant& val) -{ - *m_valueptr = val.toInt(); -} - -void StringConfigEntry::loadFromVariant (const QVariant& val) -{ - *m_valueptr = val.toString(); -} - -void BoolConfigEntry::loadFromVariant (const QVariant& val) -{ - *m_valueptr = val.toBool(); -} - -void ListConfigEntry::loadFromVariant (const QVariant& val) -{ - *m_valueptr = val.toList(); -} - -void KeySequenceConfigEntry::loadFromVariant (const QVariant& val) -{ - *m_valueptr = val.toString(); -} - -void FloatConfigEntry::loadFromVariant (const QVariant& val) -{ - *m_valueptr = val.toDouble(); -} - -void VertexConfigEntry::loadFromVariant (const QVariant& val) -{ - *m_valueptr = val.value<Vertex>(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/configuration.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,230 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * ===================================================================== + * + * config.cxx: Configuration management. I don't like how unsafe QSettings + * is so this implements a type-safer and identifer-safer wrapping system of + * configuration variables. QSettings is used underlyingly, this is a matter + * of interface. + */ + +#include <errno.h> +#include <QDir> +#include <QTextStream> +#include <QSettings> +#include "main.h" +#include "configuration.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "ldDocument.h" +#include "glRenderer.h" +#include "configuration.inc" + +#ifdef _WIN32 +# define EXTENSION ".ini" +#else +# define EXTENSION ".cfg" +#endif // _WIN32 + +#define MAX_CONFIG 512 + +static QMap<QString, AbstractConfigEntry*> EntriesByName; +static QList<AbstractConfigEntry*> ConfigurationEntries; + +AbstractConfigEntry::AbstractConfigEntry (QString name) : + m_name (name) {} + +void Config::Initialize() +{ + SetupConfigurationLists(); + print ("Configuration initialized with %1 entries\n", ConfigurationEntries.size()); +} + +static void InitConfigurationEntry (AbstractConfigEntry* entry) +{ + ConfigurationEntries << entry; + EntriesByName[entry->name()] = entry; +} + +// +// Load the configuration from file +// +bool Config::Load() +{ + QSettings* settings = SettingsObject(); + print ("Loading configuration file from %1\n", settings->fileName()); + + for (AbstractConfigEntry* cfg : ConfigurationEntries) + { + if (cfg == null) + break; + + QVariant val = settings->value (cfg->name(), cfg->getDefaultAsVariant()); + cfg->loadFromVariant (val); + } + + if (g_win != null) + g_win->loadShortcuts (settings); + + delete settings; + return true; +} + +// +// Save the configuration to disk +// +bool Config::Save() +{ + QSettings* settings = SettingsObject(); + + for (AbstractConfigEntry* cfg : ConfigurationEntries) + { + if (not cfg->isDefault()) + settings->setValue (cfg->name(), cfg->toVariant()); + else + settings->remove (cfg->name()); + } + + if (g_win != null) + g_win->saveShortcuts (settings); + + settings->sync(); + print ("Configuration saved to %1.\n", settings->fileName()); + delete settings; + return true; +} + +// +// Reset configuration to defaults. +// +void Config::ResetToDefaults() +{ + for (AbstractConfigEntry* cfg : ConfigurationEntries) + cfg->resetValue(); +} + +// +// Where is the configuration file located at? +// +QString Config::FilePath (QString file) +{ + return Config::DirectoryPath() + DIRSLASH + file; +} + +// +// Directory of the configuration file. +// +QString Config::DirectoryPath() +{ + QSettings* settings = SettingsObject(); + QString result = Dirname (settings->fileName()); + delete settings; + return result; +} + +// +// Accessor to the settings object +// +QSettings* Config::SettingsObject() +{ + QString path = qApp->applicationDirPath() + "/" UNIXNAME EXTENSION; + return new QSettings (path, QSettings::IniFormat); +} + +// +// Accessor to entry list +// +QList<AbstractConfigEntry*> const& Config::AllConfigEntries() +{ + return ConfigurationEntries; +} + +AbstractConfigEntry* Config::FindByName (QString const& name) +{ + auto it = EntriesByName.find (name); + return (it != EntriesByName.end()) ? *it : null; +} + +template<typename T> +static T* GetConfigByName (QString name, AbstractConfigEntry::Type type) +{ + auto it = EntriesByName.find (name); + + if (it == EntriesByName.end()) + return null; + + AbstractConfigEntry* 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##ConfigEntry* NAME##ConfigEntry::getByName (QString name) \ + { \ + return GetConfigByName<NAME##ConfigEntry> (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) + +void IntConfigEntry::loadFromVariant (const QVariant& val) +{ + *m_valueptr = val.toInt(); +} + +void StringConfigEntry::loadFromVariant (const QVariant& val) +{ + *m_valueptr = val.toString(); +} + +void BoolConfigEntry::loadFromVariant (const QVariant& val) +{ + *m_valueptr = val.toBool(); +} + +void ListConfigEntry::loadFromVariant (const QVariant& val) +{ + *m_valueptr = val.toList(); +} + +void KeySequenceConfigEntry::loadFromVariant (const QVariant& val) +{ + *m_valueptr = val.toString(); +} + +void FloatConfigEntry::loadFromVariant (const QVariant& val) +{ + *m_valueptr = val.toDouble(); +} + +void VertexConfigEntry::loadFromVariant (const QVariant& val) +{ + *m_valueptr = val.value<Vertex>(); +}
--- a/src/crashCatcher.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,161 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QProcess> -#include <QTemporaryFile> -#include <unistd.h> -#include <signal.h> -#include "crashCatcher.h" -#include "dialogs.h" - -#ifdef __unix__ -# ifdef Q_OS_LINUX -# include <sys/prctl.h> -# endif - -// Is the crash catcher active now? -static bool IsCrashCatcherActive = false; - -// If an assertion failed, what was it? -static QString AssertionFailureText; - -// List of signals to catch and crash on -static QList<int> SignalsToCatch ({ - SIGSEGV, // segmentation fault - SIGABRT, // abort() calls - SIGFPE, // floating point exceptions (e.g. division by zero) - SIGILL, // illegal instructions -}); - -// ------------------------------------------------------------------------------------------------- -// -// Removes the signal handler from SIGABRT and then aborts. -// -static void FinalAbort() -{ - struct sigaction sighandler; - sighandler.sa_handler = SIG_DFL; - sighandler.sa_flags = 0; - sigaction (SIGABRT, &sighandler, 0); - abort(); -} - -// ------------------------------------------------------------------------------------------------- -// -static void HandleCrash (int sig) -{ - printf ("!! Caught signal %d, launching gdb\n", sig); - - if (IsCrashCatcherActive) - { - printf ("Caught signal while crash catcher is active! Execution cannot continue.\n"); - FinalAbort(); - } - - pid_t const pid (getpid()); - QProcess proc; - QTemporaryFile commandsFile; - - IsCrashCatcherActive = true; - - if (commandsFile.open()) - { - commandsFile.write (format ("attach %1\n", pid).toLocal8Bit()); - commandsFile.write (QString ("backtrace full\n").toLocal8Bit()); - commandsFile.write (QString ("detach\n").toLocal8Bit()); - commandsFile.write (QString ("quit").toLocal8Bit()); - commandsFile.close(); - } - - proc.start ("gdb", {"-x", commandsFile.fileName()}); - - // Linux doesn't allow ptrace to be used on anything but direct child processes - // so we need to use prctl to register an exception to this to allow GDB attach to us. - // We need to do this now and no earlier because only now we actually know GDB's PID. -#ifdef Q_OS_LINUX - prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0); -#endif - - proc.waitForFinished (1000); - QString output (proc.readAllStandardOutput()); - QString err (proc.readAllStandardError()); - QFile f (UNIXNAME "-crash.log"); - - if (f.open (QIODevice::WriteOnly)) - { - fprint (f, format ("=== Program crashed with signal %1 ===\n\n%2" - "GDB stdout:\n%3\nGDB stderr:\n%4\n", sig, - (not AssertionFailureText.isEmpty()) ? AssertionFailureText + "\n\n" : "", - output, err)); - f.close(); - } - - if (not AssertionFailureText.isEmpty()) - printf ("Assertion failed: \"%s\".\n", qPrintable (AssertionFailureText)); - - printf ("Backtrace written to " UNIXNAME "-crash.log. Aborting.\n"); - FinalAbort(); -} - -// ------------------------------------------------------------------------------------------------- -// -// Initializes the crash catcher. -// -void InitCrashCatcher() -{ - struct sigaction sighandler; - sighandler.sa_handler = &HandleCrash; - sighandler.sa_flags = 0; - sigemptyset (&sighandler.sa_mask); - - for (int sig : SignalsToCatch) - { - if (sigaction (sig, &sighandler, null) == -1) - { - fprint (stderr, "Couldn't set signal handler %1: %2", sig, strerror (errno)); - SignalsToCatch.removeOne (sig); - } - } - - print ("Crash catcher hooked to signals: %1\n", SignalsToCatch); -} - -#endif // #ifdef __unix__ - -// ------------------------------------------------------------------------------------------------- -// -// This function catches an assertion failure. It 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 HandleAssertFailure (const char* file, int line, const char* funcname, const char* expr) -{ -#ifdef __unix__ - AssertionFailureText = format ("%1:%2: %3: %4", file, line, funcname, expr); -#else - DisplayBombBox (format ( - "<p><b>File</b>: <tt>%1</tt><br />" - "<b>Line</b>: <tt>%2</tt><br />" - "<b>Function:</b> <tt>%3</tt></p>" - "<p>Assertion <b><tt>`%4'</tt></b> failed.</p>", - file, line, funcname, expr)); -#endif - - abort(); -} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/crashCatcher.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,161 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QProcess> +#include <QTemporaryFile> +#include <unistd.h> +#include <signal.h> +#include "crashCatcher.h" +#include "dialogs.h" + +#ifdef __unix__ +# ifdef Q_OS_LINUX +# include <sys/prctl.h> +# endif + +// Is the crash catcher active now? +static bool IsCrashCatcherActive = false; + +// If an assertion failed, what was it? +static QString AssertionFailureText; + +// List of signals to catch and crash on +static QList<int> SignalsToCatch ({ + SIGSEGV, // segmentation fault + SIGABRT, // abort() calls + SIGFPE, // floating point exceptions (e.g. division by zero) + SIGILL, // illegal instructions +}); + +// ------------------------------------------------------------------------------------------------- +// +// Removes the signal handler from SIGABRT and then aborts. +// +static void FinalAbort() +{ + struct sigaction sighandler; + sighandler.sa_handler = SIG_DFL; + sighandler.sa_flags = 0; + sigaction (SIGABRT, &sighandler, 0); + abort(); +} + +// ------------------------------------------------------------------------------------------------- +// +static void HandleCrash (int sig) +{ + printf ("!! Caught signal %d, launching gdb\n", sig); + + if (IsCrashCatcherActive) + { + printf ("Caught signal while crash catcher is active! Execution cannot continue.\n"); + FinalAbort(); + } + + pid_t const pid (getpid()); + QProcess proc; + QTemporaryFile commandsFile; + + IsCrashCatcherActive = true; + + if (commandsFile.open()) + { + commandsFile.write (format ("attach %1\n", pid).toLocal8Bit()); + commandsFile.write (QString ("backtrace full\n").toLocal8Bit()); + commandsFile.write (QString ("detach\n").toLocal8Bit()); + commandsFile.write (QString ("quit").toLocal8Bit()); + commandsFile.close(); + } + + proc.start ("gdb", {"-x", commandsFile.fileName()}); + + // Linux doesn't allow ptrace to be used on anything but direct child processes + // so we need to use prctl to register an exception to this to allow GDB attach to us. + // We need to do this now and no earlier because only now we actually know GDB's PID. +#ifdef Q_OS_LINUX + prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0); +#endif + + proc.waitForFinished (1000); + QString output (proc.readAllStandardOutput()); + QString err (proc.readAllStandardError()); + QFile f (UNIXNAME "-crash.log"); + + if (f.open (QIODevice::WriteOnly)) + { + fprint (f, format ("=== Program crashed with signal %1 ===\n\n%2" + "GDB stdout:\n%3\nGDB stderr:\n%4\n", sig, + (not AssertionFailureText.isEmpty()) ? AssertionFailureText + "\n\n" : "", + output, err)); + f.close(); + } + + if (not AssertionFailureText.isEmpty()) + printf ("Assertion failed: \"%s\".\n", qPrintable (AssertionFailureText)); + + printf ("Backtrace written to " UNIXNAME "-crash.log. Aborting.\n"); + FinalAbort(); +} + +// ------------------------------------------------------------------------------------------------- +// +// Initializes the crash catcher. +// +void InitCrashCatcher() +{ + struct sigaction sighandler; + sighandler.sa_handler = &HandleCrash; + sighandler.sa_flags = 0; + sigemptyset (&sighandler.sa_mask); + + for (int sig : SignalsToCatch) + { + if (sigaction (sig, &sighandler, null) == -1) + { + fprint (stderr, "Couldn't set signal handler %1: %2", sig, strerror (errno)); + SignalsToCatch.removeOne (sig); + } + } + + print ("Crash catcher hooked to signals: %1\n", SignalsToCatch); +} + +#endif // #ifdef __unix__ + +// ------------------------------------------------------------------------------------------------- +// +// This function catches an assertion failure. It 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 HandleAssertFailure (const char* file, int line, const char* funcname, const char* expr) +{ +#ifdef __unix__ + AssertionFailureText = format ("%1:%2: %3: %4", file, line, funcname, expr); +#else + DisplayBombBox (format ( + "<p><b>File</b>: <tt>%1</tt><br />" + "<b>Line</b>: <tt>%2</tt><br />" + "<b>Function:</b> <tt>%3</tt></p>" + "<p>Assertion <b><tt>`%4'</tt></b> failed.</p>", + file, line, funcname, expr)); +#endif + + abort(); +} \ No newline at end of file
--- a/src/dialogs.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,378 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QDialog> -#include <QLineEdit> -#include <QSpinBox> -#include <QDialogButtonBox> -#include <QFileDialog> -#include <QLabel> -#include <QPushButton> -#include <QBoxLayout> -#include <QGridLayout> -#include <QProgressBar> -#include <QCheckBox> -#include <QDesktopServices> -#include <QMessageBox> -#include <QUrl> -#include "dialogs.h" -#include "radioGroup.h" -#include "mainWindow.h" -#include "glRenderer.h" -#include "documentation.h" -#include "ldDocument.h" -#include "dialogs.h" -#include "ui_overlay.h" -#include "ui_ldrawpath.h" -#include "ui_openprogress.h" -#include "ui_extprogpath.h" -#include "ui_about.h" -#include "ui_bombbox.h" - -extern const char* g_extProgPathFilter; -EXTERN_CFGENTRY (String, LDrawPath) - -// ============================================================================= -// ============================================================================= -OverlayDialog::OverlayDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) -{ - ui = new Ui_OverlayUI; - ui->setupUi (this); - - m_cameraArgs = - { - { ui->top, ETopCamera }, - { ui->bottom, EBottomCamera }, - { ui->front, EFrontCamera }, - { ui->back, EBackCamera }, - { ui->left, ELeftCamera }, - { ui->right, ERightCamera } - }; - - ECamera cam = g_win->R()->camera(); - - if (cam == EFreeCamera) - cam = 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> (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) or (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 (cfg::LDrawPath); - - 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 (not newpath.isEmpty()) - { - setPath (newpath); - slot_tryConfigure(); - } -} - -// ============================================================================= -// ============================================================================= -void LDrawPathDialog::slot_exit() -{ - Exit(); -} - -// ============================================================================= -// ============================================================================= -void LDrawPathDialog::slot_tryConfigure() -{ - if (not LDPaths::tryConfigure (filename())) - { - ui->status->setText (format ("<span style=\"color:#700; \">%1</span>", LDPaths::getError())); - okButton()->setEnabled (false); - return; - } - - ui->status->setText ("<span style=\"color: #270; \">OK!</span>"); - okButton()->setEnabled (true); -} - -// ============================================================================= -// ============================================================================= -void LDrawPathDialog::slot_accept() -{ - Config::Save(); - accept(); -} - -// ============================================================================= -// ============================================================================= -OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) -{ - ui = new Ui_OpenProgressUI; - ui->setupUi (this); - ui->progressText->setText ("Parsing..."); - setNumLines (0); - m_progress = 0; -} - -// ============================================================================= -// ============================================================================= -OpenProgressDialog::~OpenProgressDialog() -{ - delete ui; -} - -// ============================================================================= -// ============================================================================= -void OpenProgressDialog::setNumLines (int const& a) -{ - m_numLines = a; - ui->progressBar->setRange (0, numLines()); - updateValues(); -} - -// ============================================================================= -// ============================================================================= -void OpenProgressDialog::updateValues() -{ - ui->progressText->setText (format ("Parsing... %1 / %2", progress(), numLines())); - ui->progressBar->setValue (progress()); -} - -// ============================================================================= -// ============================================================================= -void OpenProgressDialog::updateProgress (int progress) -{ - setProgress (progress); - updateValues(); -} - -// ============================================================================= -// ============================================================================= -ExtProgPathPrompt::ExtProgPathPrompt (QString progName, QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f), - ui (new Ui_ExtProgPath) -{ - ui->setupUi (this); - QString labelText = ui->m_label->text(); - labelText.replace ("<PROGRAM>", progName); - ui->m_label->setText (labelText); - connect (ui->m_findPath, SIGNAL (clicked (bool)), this, SLOT (findPath())); -} - -// ============================================================================= -// ============================================================================= -ExtProgPathPrompt::~ExtProgPathPrompt() -{ - delete ui; -} - -// ============================================================================= -// ============================================================================= -void ExtProgPathPrompt::findPath() -{ - QString path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter); - - if (not path.isEmpty()) - ui->m_path->setText (path); -} - -// ============================================================================= -// ============================================================================= -QString ExtProgPathPrompt::getPath() const -{ - return ui->m_path->text(); -} - -// ============================================================================= -// ============================================================================= -AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) : - QDialog (parent, f) -{ - Ui::AboutUI ui; - ui.setupUi (this); - ui.versionInfo->setText (APPNAME " " + QString (FullVersionString())); - - QPushButton* mailButton = new QPushButton; - mailButton->setText (tr ("Contact")); - mailButton->setIcon (GetIcon ("mail")); - ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole); - connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail())); - - setWindowTitle (format (tr ("About %1"), APPNAME)); -} - -// ============================================================================= -// ============================================================================= -void AboutDialog::slot_mail() -{ - QDesktopServices::openUrl (QUrl ("mailto:Teemu Piippo <arezey@gmail.com>?subject=LDForge")); -} - -// ============================================================================= -// ============================================================================= -void DisplayBombBox (const QString& message) -{ - QDialog dlg (g_win); - Ui_BombBox ui; - - ui.setupUi (&dlg); - ui.m_text->setText (message); - ui.buttonBox->button (QDialogButtonBox::Close)->setText (QObject::tr ("Damn it")); - dlg.exec(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dialogs.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,378 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QDialog> +#include <QLineEdit> +#include <QSpinBox> +#include <QDialogButtonBox> +#include <QFileDialog> +#include <QLabel> +#include <QPushButton> +#include <QBoxLayout> +#include <QGridLayout> +#include <QProgressBar> +#include <QCheckBox> +#include <QDesktopServices> +#include <QMessageBox> +#include <QUrl> +#include "dialogs.h" +#include "radioGroup.h" +#include "mainWindow.h" +#include "glRenderer.h" +#include "documentation.h" +#include "ldDocument.h" +#include "dialogs.h" +#include "ui_overlay.h" +#include "ui_ldrawpath.h" +#include "ui_openprogress.h" +#include "ui_extprogpath.h" +#include "ui_about.h" +#include "ui_bombbox.h" + +extern const char* g_extProgPathFilter; +EXTERN_CFGENTRY (String, LDrawPath) + +// ============================================================================= +// ============================================================================= +OverlayDialog::OverlayDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) +{ + ui = new Ui_OverlayUI; + ui->setupUi (this); + + m_cameraArgs = + { + { ui->top, ETopCamera }, + { ui->bottom, EBottomCamera }, + { ui->front, EFrontCamera }, + { ui->back, EBackCamera }, + { ui->left, ELeftCamera }, + { ui->right, ERightCamera } + }; + + ECamera cam = g_win->R()->camera(); + + if (cam == EFreeCamera) + cam = 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> (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) or (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 (cfg::LDrawPath); + + 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 (not newpath.isEmpty()) + { + setPath (newpath); + slot_tryConfigure(); + } +} + +// ============================================================================= +// ============================================================================= +void LDrawPathDialog::slot_exit() +{ + Exit(); +} + +// ============================================================================= +// ============================================================================= +void LDrawPathDialog::slot_tryConfigure() +{ + if (not LDPaths::tryConfigure (filename())) + { + ui->status->setText (format ("<span style=\"color:#700; \">%1</span>", LDPaths::getError())); + okButton()->setEnabled (false); + return; + } + + ui->status->setText ("<span style=\"color: #270; \">OK!</span>"); + okButton()->setEnabled (true); +} + +// ============================================================================= +// ============================================================================= +void LDrawPathDialog::slot_accept() +{ + Config::Save(); + accept(); +} + +// ============================================================================= +// ============================================================================= +OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) +{ + ui = new Ui_OpenProgressUI; + ui->setupUi (this); + ui->progressText->setText ("Parsing..."); + setNumLines (0); + m_progress = 0; +} + +// ============================================================================= +// ============================================================================= +OpenProgressDialog::~OpenProgressDialog() +{ + delete ui; +} + +// ============================================================================= +// ============================================================================= +void OpenProgressDialog::setNumLines (int const& a) +{ + m_numLines = a; + ui->progressBar->setRange (0, numLines()); + updateValues(); +} + +// ============================================================================= +// ============================================================================= +void OpenProgressDialog::updateValues() +{ + ui->progressText->setText (format ("Parsing... %1 / %2", progress(), numLines())); + ui->progressBar->setValue (progress()); +} + +// ============================================================================= +// ============================================================================= +void OpenProgressDialog::updateProgress (int progress) +{ + setProgress (progress); + updateValues(); +} + +// ============================================================================= +// ============================================================================= +ExtProgPathPrompt::ExtProgPathPrompt (QString progName, QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f), + ui (new Ui_ExtProgPath) +{ + ui->setupUi (this); + QString labelText = ui->m_label->text(); + labelText.replace ("<PROGRAM>", progName); + ui->m_label->setText (labelText); + connect (ui->m_findPath, SIGNAL (clicked (bool)), this, SLOT (findPath())); +} + +// ============================================================================= +// ============================================================================= +ExtProgPathPrompt::~ExtProgPathPrompt() +{ + delete ui; +} + +// ============================================================================= +// ============================================================================= +void ExtProgPathPrompt::findPath() +{ + QString path = QFileDialog::getOpenFileName (null, "", "", g_extProgPathFilter); + + if (not path.isEmpty()) + ui->m_path->setText (path); +} + +// ============================================================================= +// ============================================================================= +QString ExtProgPathPrompt::getPath() const +{ + return ui->m_path->text(); +} + +// ============================================================================= +// ============================================================================= +AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) : + QDialog (parent, f) +{ + Ui::AboutUI ui; + ui.setupUi (this); + ui.versionInfo->setText (APPNAME " " + QString (FullVersionString())); + + QPushButton* mailButton = new QPushButton; + mailButton->setText (tr ("Contact")); + mailButton->setIcon (GetIcon ("mail")); + ui.buttonBox->addButton (static_cast<QAbstractButton*> (mailButton), QDialogButtonBox::HelpRole); + connect (ui.buttonBox, SIGNAL (helpRequested()), this, SLOT (slot_mail())); + + setWindowTitle (format (tr ("About %1"), APPNAME)); +} + +// ============================================================================= +// ============================================================================= +void AboutDialog::slot_mail() +{ + QDesktopServices::openUrl (QUrl ("mailto:Teemu Piippo <arezey@gmail.com>?subject=LDForge")); +} + +// ============================================================================= +// ============================================================================= +void DisplayBombBox (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/documentation.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QDialog> -#include <QTextEdit> -#include <QDialogButtonBox> -#include <QBoxLayout> -#include "main.h" -#include "basics.h" - -// ============================================================================= -// ============================================================================= -class DocumentViewer : public QDialog -{ - public: - explicit DocumentViewer (QWidget* parent = null, Qt::WindowFlags f = 0) : QDialog (parent, f) - { - te_text = new QTextEdit (this); - te_text->setMinimumSize (QSize (400, 300)); - te_text->setReadOnly (true); - - QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Close); - QVBoxLayout* layout = new QVBoxLayout (this); - layout->addWidget (te_text); - layout->addWidget (bbx_buttons); - - connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); - } - - void setText (const char* text) - { - te_text->setText (text); - } - - private: - QTextEdit* te_text; -}; - -const char* g_docs_overlays = - "<h1>Overlay images</h1><br />" - "<p>" APPNAME " supports drawing transparent images over the part model. This " - "can be used to have, for instance, a photo of the part overlaid on top of the " - "model and use it for drawing curves somewhat accurately.</p>" - "<p>For this purpose, a specific photo has to be taken of the part; it should " - "represent the part as true as possible to the actual camera used for editing. " - "The image should be taken from straight above the part, at as an orthogonal " - "angle as possible. It is recommended to take a lot of pictures this way and " - "select the best candidate.</p>" - "<p>The image should then be cropped with the knowledge of the image's LDU " - "dimensions in mind. The offset should then be identified in the image in pixels.</p>" - "<p>Finally, use the \"Set Overlay Image\" dialog and fill in the details. The " - "overlay image should then be ready for use."; - -// ============================================================================= -// ============================================================================= -void showDocumentation (const char* text) -{ - DocumentViewer dlg; - dlg.setText (text); - dlg.exec(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/documentation.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,76 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QDialog> +#include <QTextEdit> +#include <QDialogButtonBox> +#include <QBoxLayout> +#include "main.h" +#include "basics.h" + +// ============================================================================= +// ============================================================================= +class DocumentViewer : public QDialog +{ + public: + explicit DocumentViewer (QWidget* parent = null, Qt::WindowFlags f = 0) : QDialog (parent, f) + { + te_text = new QTextEdit (this); + te_text->setMinimumSize (QSize (400, 300)); + te_text->setReadOnly (true); + + QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Close); + QVBoxLayout* layout = new QVBoxLayout (this); + layout->addWidget (te_text); + layout->addWidget (bbx_buttons); + + connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); + } + + void setText (const char* text) + { + te_text->setText (text); + } + + private: + QTextEdit* te_text; +}; + +const char* g_docs_overlays = + "<h1>Overlay images</h1><br />" + "<p>" APPNAME " supports drawing transparent images over the part model. This " + "can be used to have, for instance, a photo of the part overlaid on top of the " + "model and use it for drawing curves somewhat accurately.</p>" + "<p>For this purpose, a specific photo has to be taken of the part; it should " + "represent the part as true as possible to the actual camera used for editing. " + "The image should be taken from straight above the part, at as an orthogonal " + "angle as possible. It is recommended to take a lot of pictures this way and " + "select the best candidate.</p>" + "<p>The image should then be cropped with the knowledge of the image's LDU " + "dimensions in mind. The offset should then be identified in the image in pixels.</p>" + "<p>Finally, use the \"Set Overlay Image\" dialog and fill in the details. The " + "overlay image should then be ready for use."; + +// ============================================================================= +// ============================================================================= +void showDocumentation (const char* text) +{ + DocumentViewer dlg; + dlg.setText (text); + dlg.exec(); +}
--- a/src/editHistory.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,203 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "editHistory.h" -#include "ldObject.h" -#include "ldDocument.h" -#include "miscallenous.h" -#include "mainWindow.h" -#include "glRenderer.h" - -// ============================================================================= -// -History::History() : - m_position (-1), - m_isIgnoring (false) {} - -// ============================================================================= -// -void History::undo() -{ - if (m_changesets.isEmpty() or position() == -1) - return; - - // Don't take the changes done here as actual edits to the document - setIgnoring (true); - - const Changeset& set = getChangeset (position()); - - // Iterate the list in reverse and undo all actions - for (int i = set.size() - 1; i >= 0; --i) - { - AbstractHistoryEntry* change = set[i]; - change->undo(); - } - - m_position--; - g_win->refresh(); - g_win->updateActions(); - dprint ("Position is now %1", position()); - setIgnoring (false); -} - -// ============================================================================= -// -void History::redo() -{ - if (position() == m_changesets.size()) - return; - - setIgnoring (true); - const Changeset& set = getChangeset (position() + 1); - - // Redo things - in the order as they were done in the first place - for (const AbstractHistoryEntry* change : set) - change->redo(); - - setPosition (position() + 1); - g_win->refresh(); - g_win->updateActions(); - dprint ("Position is now %1", position()); - setIgnoring (false); -} - -// ============================================================================= -// -void History::clear() -{ - for (Changeset set : m_changesets) - for (AbstractHistoryEntry* change : set) - delete change; - - m_changesets.clear(); - dprint ("History: cleared"); -} - -// ============================================================================= -// -void History::addStep() -{ - if (m_currentChangeset.isEmpty()) - return; - - while (position() < getSize() - 1) - { - Changeset last = m_changesets.last(); - - for (AbstractHistoryEntry* entry : last) - delete entry; - - m_changesets.removeLast(); - } - -// dprint ("History: step added (%1 changes)", m_currentChangeset.size()); - m_changesets << m_currentChangeset; - m_currentChangeset.clear(); - setPosition (position() + 1); - g_win->updateActions(); -} - -// ============================================================================= -// -void History::add (AbstractHistoryEntry* entry) -{ - if (isIgnoring()) - { - delete entry; - return; - } - - entry->setParent (this); - m_currentChangeset << entry; -// dprint ("History: added entry of type %1", entry->getTypeName()); -} - -// ============================================================================= -// -void AddHistory::undo() const -{ - LDObjectPtr obj = parent()->document().toStrongRef()->getObject (index()); - obj->destroy(); -} - -// ============================================================================= -// -void AddHistory::redo() const -{ - LDObjectPtr obj = ParseLine (code()); - parent()->document().toStrongRef()->insertObj (index(), obj); - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// -DelHistory::DelHistory (int idx, LDObjectPtr obj) : - m_index (idx), - m_code (obj->asText()) {} - -// ============================================================================= -// heh -// -void DelHistory::undo() const -{ - LDObjectPtr obj = ParseLine (code()); - parent()->document().toStrongRef()->insertObj (index(), obj); - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// -void DelHistory::redo() const -{ - LDObjectPtr obj = parent()->document().toStrongRef()->getObject (index()); - obj->destroy(); -} - -// ============================================================================= -// -void EditHistory::undo() const -{ - LDObjectPtr obj = CurrentDocument()->getObject (index()); - LDObjectPtr newobj = ParseLine (oldCode()); - obj->replace (newobj); - g_win->R()->compileObject (newobj); -} - -// ============================================================================= -// -void EditHistory::redo() const -{ - LDObjectPtr obj = CurrentDocument()->getObject (index()); - LDObjectPtr newobj = ParseLine (newCode()); - obj->replace (newobj); - g_win->R()->compileObject (newobj); -} - -// ============================================================================= -// -void SwapHistory::undo() const -{ - LDObject::fromID (a)->swap (LDObject::fromID (b)); -} - -// ============================================================================= -// -void SwapHistory::redo() const -{ - undo(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editHistory.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,203 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "editHistory.h" +#include "ldObject.h" +#include "ldDocument.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "glRenderer.h" + +// ============================================================================= +// +History::History() : + m_position (-1), + m_isIgnoring (false) {} + +// ============================================================================= +// +void History::undo() +{ + if (m_changesets.isEmpty() or position() == -1) + return; + + // Don't take the changes done here as actual edits to the document + setIgnoring (true); + + const Changeset& set = getChangeset (position()); + + // Iterate the list in reverse and undo all actions + for (int i = set.size() - 1; i >= 0; --i) + { + AbstractHistoryEntry* change = set[i]; + change->undo(); + } + + m_position--; + g_win->refresh(); + g_win->updateActions(); + dprint ("Position is now %1", position()); + setIgnoring (false); +} + +// ============================================================================= +// +void History::redo() +{ + if (position() == m_changesets.size()) + return; + + setIgnoring (true); + const Changeset& set = getChangeset (position() + 1); + + // Redo things - in the order as they were done in the first place + for (const AbstractHistoryEntry* change : set) + change->redo(); + + setPosition (position() + 1); + g_win->refresh(); + g_win->updateActions(); + dprint ("Position is now %1", position()); + setIgnoring (false); +} + +// ============================================================================= +// +void History::clear() +{ + for (Changeset set : m_changesets) + for (AbstractHistoryEntry* change : set) + delete change; + + m_changesets.clear(); + dprint ("History: cleared"); +} + +// ============================================================================= +// +void History::addStep() +{ + if (m_currentChangeset.isEmpty()) + return; + + while (position() < getSize() - 1) + { + Changeset last = m_changesets.last(); + + for (AbstractHistoryEntry* entry : last) + delete entry; + + m_changesets.removeLast(); + } + +// dprint ("History: step added (%1 changes)", m_currentChangeset.size()); + m_changesets << m_currentChangeset; + m_currentChangeset.clear(); + setPosition (position() + 1); + g_win->updateActions(); +} + +// ============================================================================= +// +void History::add (AbstractHistoryEntry* entry) +{ + if (isIgnoring()) + { + delete entry; + return; + } + + entry->setParent (this); + m_currentChangeset << entry; +// dprint ("History: added entry of type %1", entry->getTypeName()); +} + +// ============================================================================= +// +void AddHistory::undo() const +{ + LDObjectPtr obj = parent()->document().toStrongRef()->getObject (index()); + obj->destroy(); +} + +// ============================================================================= +// +void AddHistory::redo() const +{ + LDObjectPtr obj = ParseLine (code()); + parent()->document().toStrongRef()->insertObj (index(), obj); + g_win->R()->compileObject (obj); +} + +// ============================================================================= +// +DelHistory::DelHistory (int idx, LDObjectPtr obj) : + m_index (idx), + m_code (obj->asText()) {} + +// ============================================================================= +// heh +// +void DelHistory::undo() const +{ + LDObjectPtr obj = ParseLine (code()); + parent()->document().toStrongRef()->insertObj (index(), obj); + g_win->R()->compileObject (obj); +} + +// ============================================================================= +// +void DelHistory::redo() const +{ + LDObjectPtr obj = parent()->document().toStrongRef()->getObject (index()); + obj->destroy(); +} + +// ============================================================================= +// +void EditHistory::undo() const +{ + LDObjectPtr obj = CurrentDocument()->getObject (index()); + LDObjectPtr newobj = ParseLine (oldCode()); + obj->replace (newobj); + g_win->R()->compileObject (newobj); +} + +// ============================================================================= +// +void EditHistory::redo() const +{ + LDObjectPtr obj = CurrentDocument()->getObject (index()); + LDObjectPtr newobj = ParseLine (newCode()); + obj->replace (newobj); + g_win->R()->compileObject (newobj); +} + +// ============================================================================= +// +void SwapHistory::undo() const +{ + LDObject::fromID (a)->swap (LDObject::fromID (b)); +} + +// ============================================================================= +// +void SwapHistory::redo() const +{ + undo(); +}
--- a/src/extPrograms.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,721 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QProcess> -#include <QTemporaryFile> -#include <QDialog> -#include <QDialogButtonBox> -#include <QSpinBox> -#include <QCheckBox> -#include <QComboBox> -#include <QGridLayout> -#include <QFileInfo> -#include "main.h" -#include "configuration.h" -#include "miscallenous.h" -#include "mainWindow.h" -#include "ldDocument.h" -#include "radioGroup.h" -#include "editHistory.h" -#include "ui_ytruder.h" -#include "ui_intersector.h" -#include "ui_rectifier.h" -#include "ui_coverer.h" -#include "ui_isecalc.h" -#include "ui_edger2.h" -#include "dialogs.h" - -enum extprog -{ - Isecalc, - Intersector, - Coverer, - Ytruder, - Rectifier, - Edger2, -}; - -// ============================================================================= -// -CFGENTRY (String, IsecalcPath, "") -CFGENTRY (String, IntersectorPath, "") -CFGENTRY (String, CovererPath, "") -CFGENTRY (String, YtruderPath, "") -CFGENTRY (String, RectifierPath, "") -CFGENTRY (String, Edger2Path, "") - -QString* const g_extProgPaths[] = -{ - &cfg::IsecalcPath, - &cfg::IntersectorPath, - &cfg::CovererPath, - &cfg::YtruderPath, - &cfg::RectifierPath, - &cfg::Edger2Path, -}; - -CFGENTRY (Bool, IsecalcUsesWine, false) -CFGENTRY (Bool, IntersectorUsesWine, false) -CFGENTRY (Bool, CovererUsesWine, false) -CFGENTRY (Bool, YtruderUsesWine, false) -CFGENTRY (Bool, RectifierUsesWine, false) -CFGENTRY (Bool, Edger2UsesWine, false) - -bool* const g_extProgWine[] = -{ - &cfg::IsecalcUsesWine, - &cfg::IntersectorUsesWine, - &cfg::CovererUsesWine, - &cfg::YtruderUsesWine, - &cfg::RectifierUsesWine, - &cfg::Edger2UsesWine, -}; - -const char* g_extProgNames[] = -{ - "Isecalc", - "Intersector", - "Coverer", - "Ytruder", - "Rectifier", - "Edger2" -}; - -// ============================================================================= -// -static bool MakeTempFile (QTemporaryFile& tmp, QString& fname) -{ - if (not tmp.open()) - return false; - - fname = tmp.fileName(); - tmp.close(); - return true; -} - -// ============================================================================= -// -static bool CheckExtProgramPath (const extprog prog) -{ - QString& path = *g_extProgPaths[prog]; - - if (not path.isEmpty()) - return true; - - ExtProgPathPrompt* dlg = new ExtProgPathPrompt (g_extProgNames[prog]); - - if (dlg->exec() and not dlg->getPath().isEmpty()) - { - path = dlg->getPath(); - return true; - } - - return false; -} - -// ============================================================================= -// -static QString ProcessExtProgError (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 "; -#else - (void) prog; -#endif - - return format ("Program failed to start, %1check your permissions", wineblurb); - } break; - - case QProcess::Crashed: - return "Crashed."; - - case QProcess::WriteError: - case QProcess::ReadError: - return "I/O error."; - - case QProcess::UnknownError: - return "Unknown error"; - - case QProcess::Timedout: - return format ("Timed out (30 seconds)"); - } - - return ""; -} - -// ============================================================================= -// -static void WriteObjects (const LDObjectList& objects, QFile& f) -{ - for (LDObjectPtr obj : objects) - { - if (obj->type() == OBJ_Subfile) - { - LDSubfilePtr ref = obj.staticCast<LDSubfile>(); - LDObjectList objs = ref->inlineContents (true, false); - - WriteObjects (objs, f); - - for (LDObjectPtr obj : objs) - obj->destroy(); - } - else - f.write ((obj->asText() + "\r\n").toUtf8()); - } -} - -// ============================================================================= -// -static void WriteObjects (const LDObjectList& objects, QString fname) -{ - // Write the input file - QFile f (fname); - - if (not f.open (QIODevice::WriteOnly | QIODevice::Text)) - { - Critical (format ("Couldn't open temporary file %1 for writing: %2\n", fname, f.errorString())); - return; - } - - WriteObjects (objects, f); - f.close(); - -#ifdef DEBUG - QFile::copy (fname, "debug_lastInput"); -#endif -} - -// ============================================================================= -// -void WriteSelection (QString fname) -{ - WriteObjects (Selection(), fname); -} - -// ============================================================================= -// -void WriteColorGroup (LDColor color, QString fname) -{ - LDObjectList objects; - - for (LDObjectPtr obj : CurrentDocument()->objects()) - { - if (not obj->isColored() or obj->color() != color) - continue; - - objects << obj; - } - - WriteObjects (objects, fname); -} - -// ============================================================================= -// -bool RunExtProgram (extprog prog, QString path, QString argvstr) -{ - QTemporaryFile input; - QStringList argv = argvstr.split (" ", QString::SkipEmptyParts); - -#ifndef _WIN32 - if (*g_extProgWine[prog]) - { - argv.insert (0, path); - path = "wine"; - } -#endif // _WIN32 - - print ("Running command: %1 %2\n", path, argv.join (" ")); - - if (not input.open()) - return false; - - QProcess proc; - - // Begin! - proc.setStandardInputFile (input.fileName()); - proc.start (path, argv); - - if (not proc.waitForStarted()) - { - Critical (format ("Couldn't start %1: %2\n", g_extProgNames[prog], ProcessExtProgError (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 = ProcessExtProgError (prog, proc); - - // Check the return code - if (proc.exitCode() != 0) - err = format ("Program exited abnormally (return code %1).", proc.exitCode()); - - if (not err.isEmpty()) - { - Critical (format ("%1 failed: %2\n", g_extProgNames[prog], err)); - QString filename ("externalProgramOutput.txt"); - QFile file (filename); - - if (file.open (QIODevice::WriteOnly | QIODevice::Text)) - { - file.write (proc.readAllStandardOutput()); - file.write (proc.readAllStandardError()); - print ("Wrote output and error logs to %1", QFileInfo (file).absoluteFilePath()); - } - else - { - print ("Couldn't open %1 for writing: %2", - QFileInfo (filename).absoluteFilePath(), file.errorString()); - } - - return false; - } - - return true; -} - -// ============================================================================= -// -static void InsertOutput (QString fname, bool replace, QList<LDColor> colorsToReplace) -{ -#ifdef DEBUG - QFile::copy (fname, "./debug_lastOutput"); -#endif // RELEASE - - // Read the output file - QFile f (fname); - - if (not f.open (QIODevice::ReadOnly)) - { - Critical (format ("Couldn't open temporary file %1 for reading.\n", fname)); - return; - } - - LDObjectList objs = LoadFileContents (&f, null); - - // If we replace the objects, delete the selection now. - if (replace) - g_win->deleteSelection(); - - for (LDColor color : colorsToReplace) - g_win->deleteByColor (color); - - // Insert the new objects - CurrentDocument()->clearSelection(); - - for (LDObjectPtr obj : objs) - { - if (not obj->isScemantic()) - { - obj->destroy(); - continue; - } - - CurrentDocument()->addObject (obj); - obj->select(); - } - - g_win->doFullRefresh(); -} - -// ============================================================================= -// Interface for Ytruder -// ============================================================================= -void MainWindow::actionYtruder() -{ - setlocale (LC_ALL, "C"); - - if (not CheckExtProgramPath (Ytruder)) - return; - - QDialog* dlg = new QDialog; - Ui::YtruderUI ui; - ui.setupUi (dlg); - - if (not 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 (not MakeTempFile (indat, inDATName) or not MakeTempFile (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 (not RunExtProgram (Ytruder, cfg::YtruderPath, argv)) - return; - - InsertOutput (outDATName, false, {}); -} - -// ============================================================================= -// Rectifier interface -// ============================================================================= -void MainWindow::actionRectifier() -{ - setlocale (LC_ALL, "C"); - - if (not CheckExtProgramPath (Rectifier)) - return; - - QDialog* dlg = new QDialog; - Ui::RectifierUI ui; - ui.setupUi (dlg); - - if (not dlg->exec()) - return; - - QTemporaryFile indat, outdat; - QString inDATName, outDATName; - - // Make temp files for the input and output files - if (not MakeTempFile (indat, inDATName) or not MakeTempFile (outdat, outDATName)) - return; - - // Compose arguments - QString argv = Join ( - { - (not ui.cb_condense->isChecked()) ? "-q" : "", - (not 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 (not RunExtProgram (Rectifier, cfg::RectifierPath, argv)) - return; - - InsertOutput (outDATName, true, {}); -} - -// ============================================================================= -// Intersector interface -// ============================================================================= -void MainWindow::actionIntersector() -{ - setlocale (LC_ALL, "C"); - - if (not CheckExtProgramPath (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."); - - LDColor inCol, cutCol; - const bool repeatInverse = ui.cb_repeat->isChecked(); - - forever - { - if (not dlg->exec()) - return; - - inCol = LDColor::fromIndex (ui.cmb_incol->itemData (ui.cmb_incol->currentIndex()).toInt()); - cutCol = LDColor::fromIndex (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 (not MakeTempFile (indat, inDATName) or - not MakeTempFile (cutdat, cutDATName) or - not MakeTempFile (outdat, outDATName) or - not MakeTempFile (outdat2, outDAT2Name) or - not MakeTempFile (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 (not RunExtProgram (Intersector, cfg::IntersectorPath, argv_normal)) - return; - - InsertOutput (outDATName, false, {inCol}); - - if (repeatInverse and RunExtProgram (Intersector, cfg::IntersectorPath, argv_inverse)) - InsertOutput (outDAT2Name, false, {cutCol}); - - if (ui.cb_edges->isChecked() and CheckExtProgramPath (Isecalc) and - RunExtProgram (Isecalc, cfg::IsecalcPath, Join ({inDATName, cutDATName, edgesDATName}))) - { - InsertOutput (edgesDATName, false, {}); - } -} - -// ============================================================================= -// -void MainWindow::actionCoverer() -{ - setlocale (LC_ALL, "C"); - - if (not CheckExtProgramPath (Coverer)) - return; - - QDialog* dlg = new QDialog; - Ui::CovererUI ui; - ui.setupUi (dlg); - MakeColorComboBox (ui.cmb_col1); - MakeColorComboBox (ui.cmb_col2); - - LDColor in1Col, in2Col; - - forever - { - if (not dlg->exec()) - return; - - in1Col = LDColor::fromIndex (ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt()); - in2Col = LDColor::fromIndex (ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt()); - - if (in1Col == in2Col) - { - Critical ("Cannot use the same color group for both inputs!"); - continue; - } - - break; - } - - QTemporaryFile in1dat, in2dat, outdat; - QString in1DATName, in2DATName, outDATName; - - if (not MakeTempFile (in1dat, in1DATName) or - not MakeTempFile (in2dat, in2DATName) or - not MakeTempFile (outdat, outDATName)) - { - return; - } - - QString argv = Join ( - { - (ui.cb_oldsweep->isChecked() ? "-s" : ""), - (ui.cb_reverse->isChecked() ? "-r" : ""), - (ui.dsb_segsplit->value() != 0 ? format ("-l %1", ui.dsb_segsplit->value()) : ""), - (ui.sb_bias->value() != 0 ? format ("-s %1", ui.sb_bias->value()) : ""), - in1DATName, - in2DATName, - outDATName - }); - - WriteColorGroup (in1Col, in1DATName); - WriteColorGroup (in2Col, in2DATName); - - if (not RunExtProgram (Coverer, cfg::CovererPath, argv)) - return; - - InsertOutput (outDATName, false, {}); -} - -// ============================================================================= -// -void MainWindow::actionIsecalc() -{ - setlocale (LC_ALL, "C"); - - if (not CheckExtProgramPath (Isecalc)) - return; - - Ui::IsecalcUI ui; - QDialog* dlg = new QDialog; - ui.setupUi (dlg); - - MakeColorComboBox (ui.cmb_col1); - MakeColorComboBox (ui.cmb_col2); - - LDColor in1Col, in2Col; - - // Run the dialog and validate input - forever - { - if (not dlg->exec()) - return; - - in1Col = LDColor::fromIndex (ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt()); - in2Col = LDColor::fromIndex (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 (not MakeTempFile (in1dat, in1DATName) or - not MakeTempFile (in2dat, in2DATName) or - not MakeTempFile (outdat, outDATName)) - { - return; - } - - QString argv = Join ( - { - in1DATName, - in2DATName, - outDATName - }); - - WriteColorGroup (in1Col, in1DATName); - WriteColorGroup (in2Col, in2DATName); - RunExtProgram (Isecalc, cfg::IsecalcPath, argv); - InsertOutput (outDATName, false, {}); -} - -// ============================================================================= -// -void MainWindow::actionEdger2() -{ - setlocale (LC_ALL, "C"); - - if (not CheckExtProgramPath (Edger2)) - return; - - QDialog* dlg = new QDialog; - Ui::Edger2Dialog ui; - ui.setupUi (dlg); - - if (not dlg->exec()) - return; - - QTemporaryFile in, out; - QString inName, outName; - - if (not MakeTempFile (in, inName) or not MakeTempFile (out, outName)) - return; - - int unmatched = ui.unmatched->currentIndex(); - - QString argv = Join ( - { - format ("-p %1", ui.precision->value()), - format ("-af %1", ui.flatAngle->value()), - format ("-ac %1", ui.condAngle->value()), - format ("-ae %1", ui.edgeAngle->value()), - ui.delLines->isChecked() ? "-de" : "", - ui.delCondLines->isChecked() ? "-dc" : "", - ui.colored->isChecked() ? "-c" : "", - ui.bfc->isChecked() ? "-b" : "", - ui.convex->isChecked() ? "-cx" : "", - ui.concave->isChecked() ? "-cv" : "", - unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""), - inName, - outName, - }); - - WriteSelection (inName); - - if (not RunExtProgram (Edger2, cfg::Edger2Path, argv)) - return; - - InsertOutput (outName, true, {}); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/extPrograms.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,721 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QProcess> +#include <QTemporaryFile> +#include <QDialog> +#include <QDialogButtonBox> +#include <QSpinBox> +#include <QCheckBox> +#include <QComboBox> +#include <QGridLayout> +#include <QFileInfo> +#include "main.h" +#include "configuration.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "ldDocument.h" +#include "radioGroup.h" +#include "editHistory.h" +#include "ui_ytruder.h" +#include "ui_intersector.h" +#include "ui_rectifier.h" +#include "ui_coverer.h" +#include "ui_isecalc.h" +#include "ui_edger2.h" +#include "dialogs.h" + +enum extprog +{ + Isecalc, + Intersector, + Coverer, + Ytruder, + Rectifier, + Edger2, +}; + +// ============================================================================= +// +CFGENTRY (String, IsecalcPath, "") +CFGENTRY (String, IntersectorPath, "") +CFGENTRY (String, CovererPath, "") +CFGENTRY (String, YtruderPath, "") +CFGENTRY (String, RectifierPath, "") +CFGENTRY (String, Edger2Path, "") + +QString* const g_extProgPaths[] = +{ + &cfg::IsecalcPath, + &cfg::IntersectorPath, + &cfg::CovererPath, + &cfg::YtruderPath, + &cfg::RectifierPath, + &cfg::Edger2Path, +}; + +CFGENTRY (Bool, IsecalcUsesWine, false) +CFGENTRY (Bool, IntersectorUsesWine, false) +CFGENTRY (Bool, CovererUsesWine, false) +CFGENTRY (Bool, YtruderUsesWine, false) +CFGENTRY (Bool, RectifierUsesWine, false) +CFGENTRY (Bool, Edger2UsesWine, false) + +bool* const g_extProgWine[] = +{ + &cfg::IsecalcUsesWine, + &cfg::IntersectorUsesWine, + &cfg::CovererUsesWine, + &cfg::YtruderUsesWine, + &cfg::RectifierUsesWine, + &cfg::Edger2UsesWine, +}; + +const char* g_extProgNames[] = +{ + "Isecalc", + "Intersector", + "Coverer", + "Ytruder", + "Rectifier", + "Edger2" +}; + +// ============================================================================= +// +static bool MakeTempFile (QTemporaryFile& tmp, QString& fname) +{ + if (not tmp.open()) + return false; + + fname = tmp.fileName(); + tmp.close(); + return true; +} + +// ============================================================================= +// +static bool CheckExtProgramPath (const extprog prog) +{ + QString& path = *g_extProgPaths[prog]; + + if (not path.isEmpty()) + return true; + + ExtProgPathPrompt* dlg = new ExtProgPathPrompt (g_extProgNames[prog]); + + if (dlg->exec() and not dlg->getPath().isEmpty()) + { + path = dlg->getPath(); + return true; + } + + return false; +} + +// ============================================================================= +// +static QString ProcessExtProgError (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 "; +#else + (void) prog; +#endif + + return format ("Program failed to start, %1check your permissions", wineblurb); + } break; + + case QProcess::Crashed: + return "Crashed."; + + case QProcess::WriteError: + case QProcess::ReadError: + return "I/O error."; + + case QProcess::UnknownError: + return "Unknown error"; + + case QProcess::Timedout: + return format ("Timed out (30 seconds)"); + } + + return ""; +} + +// ============================================================================= +// +static void WriteObjects (const LDObjectList& objects, QFile& f) +{ + for (LDObjectPtr obj : objects) + { + if (obj->type() == OBJ_Subfile) + { + LDSubfilePtr ref = obj.staticCast<LDSubfile>(); + LDObjectList objs = ref->inlineContents (true, false); + + WriteObjects (objs, f); + + for (LDObjectPtr obj : objs) + obj->destroy(); + } + else + f.write ((obj->asText() + "\r\n").toUtf8()); + } +} + +// ============================================================================= +// +static void WriteObjects (const LDObjectList& objects, QString fname) +{ + // Write the input file + QFile f (fname); + + if (not f.open (QIODevice::WriteOnly | QIODevice::Text)) + { + Critical (format ("Couldn't open temporary file %1 for writing: %2\n", fname, f.errorString())); + return; + } + + WriteObjects (objects, f); + f.close(); + +#ifdef DEBUG + QFile::copy (fname, "debug_lastInput"); +#endif +} + +// ============================================================================= +// +void WriteSelection (QString fname) +{ + WriteObjects (Selection(), fname); +} + +// ============================================================================= +// +void WriteColorGroup (LDColor color, QString fname) +{ + LDObjectList objects; + + for (LDObjectPtr obj : CurrentDocument()->objects()) + { + if (not obj->isColored() or obj->color() != color) + continue; + + objects << obj; + } + + WriteObjects (objects, fname); +} + +// ============================================================================= +// +bool RunExtProgram (extprog prog, QString path, QString argvstr) +{ + QTemporaryFile input; + QStringList argv = argvstr.split (" ", QString::SkipEmptyParts); + +#ifndef _WIN32 + if (*g_extProgWine[prog]) + { + argv.insert (0, path); + path = "wine"; + } +#endif // _WIN32 + + print ("Running command: %1 %2\n", path, argv.join (" ")); + + if (not input.open()) + return false; + + QProcess proc; + + // Begin! + proc.setStandardInputFile (input.fileName()); + proc.start (path, argv); + + if (not proc.waitForStarted()) + { + Critical (format ("Couldn't start %1: %2\n", g_extProgNames[prog], ProcessExtProgError (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 = ProcessExtProgError (prog, proc); + + // Check the return code + if (proc.exitCode() != 0) + err = format ("Program exited abnormally (return code %1).", proc.exitCode()); + + if (not err.isEmpty()) + { + Critical (format ("%1 failed: %2\n", g_extProgNames[prog], err)); + QString filename ("externalProgramOutput.txt"); + QFile file (filename); + + if (file.open (QIODevice::WriteOnly | QIODevice::Text)) + { + file.write (proc.readAllStandardOutput()); + file.write (proc.readAllStandardError()); + print ("Wrote output and error logs to %1", QFileInfo (file).absoluteFilePath()); + } + else + { + print ("Couldn't open %1 for writing: %2", + QFileInfo (filename).absoluteFilePath(), file.errorString()); + } + + return false; + } + + return true; +} + +// ============================================================================= +// +static void InsertOutput (QString fname, bool replace, QList<LDColor> colorsToReplace) +{ +#ifdef DEBUG + QFile::copy (fname, "./debug_lastOutput"); +#endif // RELEASE + + // Read the output file + QFile f (fname); + + if (not f.open (QIODevice::ReadOnly)) + { + Critical (format ("Couldn't open temporary file %1 for reading.\n", fname)); + return; + } + + LDObjectList objs = LoadFileContents (&f, null); + + // If we replace the objects, delete the selection now. + if (replace) + g_win->deleteSelection(); + + for (LDColor color : colorsToReplace) + g_win->deleteByColor (color); + + // Insert the new objects + CurrentDocument()->clearSelection(); + + for (LDObjectPtr obj : objs) + { + if (not obj->isScemantic()) + { + obj->destroy(); + continue; + } + + CurrentDocument()->addObject (obj); + obj->select(); + } + + g_win->doFullRefresh(); +} + +// ============================================================================= +// Interface for Ytruder +// ============================================================================= +void MainWindow::actionYtruder() +{ + setlocale (LC_ALL, "C"); + + if (not CheckExtProgramPath (Ytruder)) + return; + + QDialog* dlg = new QDialog; + Ui::YtruderUI ui; + ui.setupUi (dlg); + + if (not 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 (not MakeTempFile (indat, inDATName) or not MakeTempFile (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 (not RunExtProgram (Ytruder, cfg::YtruderPath, argv)) + return; + + InsertOutput (outDATName, false, {}); +} + +// ============================================================================= +// Rectifier interface +// ============================================================================= +void MainWindow::actionRectifier() +{ + setlocale (LC_ALL, "C"); + + if (not CheckExtProgramPath (Rectifier)) + return; + + QDialog* dlg = new QDialog; + Ui::RectifierUI ui; + ui.setupUi (dlg); + + if (not dlg->exec()) + return; + + QTemporaryFile indat, outdat; + QString inDATName, outDATName; + + // Make temp files for the input and output files + if (not MakeTempFile (indat, inDATName) or not MakeTempFile (outdat, outDATName)) + return; + + // Compose arguments + QString argv = Join ( + { + (not ui.cb_condense->isChecked()) ? "-q" : "", + (not 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 (not RunExtProgram (Rectifier, cfg::RectifierPath, argv)) + return; + + InsertOutput (outDATName, true, {}); +} + +// ============================================================================= +// Intersector interface +// ============================================================================= +void MainWindow::actionIntersector() +{ + setlocale (LC_ALL, "C"); + + if (not CheckExtProgramPath (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."); + + LDColor inCol, cutCol; + const bool repeatInverse = ui.cb_repeat->isChecked(); + + forever + { + if (not dlg->exec()) + return; + + inCol = LDColor::fromIndex (ui.cmb_incol->itemData (ui.cmb_incol->currentIndex()).toInt()); + cutCol = LDColor::fromIndex (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 (not MakeTempFile (indat, inDATName) or + not MakeTempFile (cutdat, cutDATName) or + not MakeTempFile (outdat, outDATName) or + not MakeTempFile (outdat2, outDAT2Name) or + not MakeTempFile (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 (not RunExtProgram (Intersector, cfg::IntersectorPath, argv_normal)) + return; + + InsertOutput (outDATName, false, {inCol}); + + if (repeatInverse and RunExtProgram (Intersector, cfg::IntersectorPath, argv_inverse)) + InsertOutput (outDAT2Name, false, {cutCol}); + + if (ui.cb_edges->isChecked() and CheckExtProgramPath (Isecalc) and + RunExtProgram (Isecalc, cfg::IsecalcPath, Join ({inDATName, cutDATName, edgesDATName}))) + { + InsertOutput (edgesDATName, false, {}); + } +} + +// ============================================================================= +// +void MainWindow::actionCoverer() +{ + setlocale (LC_ALL, "C"); + + if (not CheckExtProgramPath (Coverer)) + return; + + QDialog* dlg = new QDialog; + Ui::CovererUI ui; + ui.setupUi (dlg); + MakeColorComboBox (ui.cmb_col1); + MakeColorComboBox (ui.cmb_col2); + + LDColor in1Col, in2Col; + + forever + { + if (not dlg->exec()) + return; + + in1Col = LDColor::fromIndex (ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt()); + in2Col = LDColor::fromIndex (ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt()); + + if (in1Col == in2Col) + { + Critical ("Cannot use the same color group for both inputs!"); + continue; + } + + break; + } + + QTemporaryFile in1dat, in2dat, outdat; + QString in1DATName, in2DATName, outDATName; + + if (not MakeTempFile (in1dat, in1DATName) or + not MakeTempFile (in2dat, in2DATName) or + not MakeTempFile (outdat, outDATName)) + { + return; + } + + QString argv = Join ( + { + (ui.cb_oldsweep->isChecked() ? "-s" : ""), + (ui.cb_reverse->isChecked() ? "-r" : ""), + (ui.dsb_segsplit->value() != 0 ? format ("-l %1", ui.dsb_segsplit->value()) : ""), + (ui.sb_bias->value() != 0 ? format ("-s %1", ui.sb_bias->value()) : ""), + in1DATName, + in2DATName, + outDATName + }); + + WriteColorGroup (in1Col, in1DATName); + WriteColorGroup (in2Col, in2DATName); + + if (not RunExtProgram (Coverer, cfg::CovererPath, argv)) + return; + + InsertOutput (outDATName, false, {}); +} + +// ============================================================================= +// +void MainWindow::actionIsecalc() +{ + setlocale (LC_ALL, "C"); + + if (not CheckExtProgramPath (Isecalc)) + return; + + Ui::IsecalcUI ui; + QDialog* dlg = new QDialog; + ui.setupUi (dlg); + + MakeColorComboBox (ui.cmb_col1); + MakeColorComboBox (ui.cmb_col2); + + LDColor in1Col, in2Col; + + // Run the dialog and validate input + forever + { + if (not dlg->exec()) + return; + + in1Col = LDColor::fromIndex (ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt()); + in2Col = LDColor::fromIndex (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 (not MakeTempFile (in1dat, in1DATName) or + not MakeTempFile (in2dat, in2DATName) or + not MakeTempFile (outdat, outDATName)) + { + return; + } + + QString argv = Join ( + { + in1DATName, + in2DATName, + outDATName + }); + + WriteColorGroup (in1Col, in1DATName); + WriteColorGroup (in2Col, in2DATName); + RunExtProgram (Isecalc, cfg::IsecalcPath, argv); + InsertOutput (outDATName, false, {}); +} + +// ============================================================================= +// +void MainWindow::actionEdger2() +{ + setlocale (LC_ALL, "C"); + + if (not CheckExtProgramPath (Edger2)) + return; + + QDialog* dlg = new QDialog; + Ui::Edger2Dialog ui; + ui.setupUi (dlg); + + if (not dlg->exec()) + return; + + QTemporaryFile in, out; + QString inName, outName; + + if (not MakeTempFile (in, inName) or not MakeTempFile (out, outName)) + return; + + int unmatched = ui.unmatched->currentIndex(); + + QString argv = Join ( + { + format ("-p %1", ui.precision->value()), + format ("-af %1", ui.flatAngle->value()), + format ("-ac %1", ui.condAngle->value()), + format ("-ae %1", ui.edgeAngle->value()), + ui.delLines->isChecked() ? "-de" : "", + ui.delCondLines->isChecked() ? "-dc" : "", + ui.colored->isChecked() ? "-c" : "", + ui.bfc->isChecked() ? "-b" : "", + ui.convex->isChecked() ? "-cx" : "", + ui.concave->isChecked() ? "-cv" : "", + unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""), + inName, + outName, + }); + + WriteSelection (inName); + + if (not RunExtProgram (Edger2, cfg::Edger2Path, argv)) + return; + + InsertOutput (outName, true, {}); +}
--- a/src/glCompiler.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,423 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#define GL_GLEXT_PROTOTYPES -#include <GL/glu.h> -#include <GL/glext.h> -#include "glCompiler.h" -#include "ldObject.h" -#include "colors.h" -#include "ldDocument.h" -#include "miscallenous.h" -#include "glRenderer.h" -#include "dialogs.h" - -struct GLErrorInfo -{ - GLenum value; - QString text; -}; - -static const GLErrorInfo g_GLErrors[] = -{ - { GL_NO_ERROR, "No error" }, - { GL_INVALID_ENUM, "Unacceptable enumerator passed" }, - { GL_INVALID_VALUE, "Numeric argument out of range" }, - { GL_INVALID_OPERATION, "The operation is not allowed to be done in this state" }, - { GL_INVALID_FRAMEBUFFER_OPERATION, "Framebuffer object is not complete"}, - { GL_OUT_OF_MEMORY, "Out of memory" }, - { GL_STACK_UNDERFLOW, "The operation would have caused an underflow" }, - { GL_STACK_OVERFLOW, "The operation would have caused an overflow" }, -}; - -CFGENTRY (String, SelectColorBlend, "#0080FF") -EXTERN_CFGENTRY (Bool, BlackEdges) -EXTERN_CFGENTRY (String, BackgroundColor) - -static QList<int> g_warnedColors; -static const QColor g_BFCFrontColor (64, 192, 80); -static const QColor g_BFCBackColor (208, 64, 64); - -// static QMap<LDObjectPtr, String> g_objectOrigins; - -// ============================================================================= -// -void CheckGLErrorImpl (const char* file, int line) -{ - QString errmsg; - GLenum errnum = glGetError(); - - if (errnum == GL_NO_ERROR) - return; - - for (const GLErrorInfo& err : g_GLErrors) - { - if (err.value == errnum) - { - errmsg = err.text; - break; - } - } - - print ("OpenGL ERROR: at %1:%2: %3", Basename (QString (file)), line, errmsg); -} - -// ============================================================================= -// -GLCompiler::GLCompiler (GLRenderer* renderer) : - m_renderer (renderer) -{ - needMerge(); - memset (m_vboSizes, 0, sizeof m_vboSizes); -} - -// ============================================================================= -// -void GLCompiler::initialize() -{ - initializeOpenGLFunctions(); - glGenBuffers (g_numVBOs, &m_vbo[0]); - CHECK_GL_ERROR(); -} - -// ============================================================================= -// -GLCompiler::~GLCompiler() -{ - glDeleteBuffers (g_numVBOs, &m_vbo[0]); - CHECK_GL_ERROR(); - - if (m_renderer != null) - m_renderer->setCompiler (null); -} - -// ============================================================================= -// -uint32 GLCompiler::colorToRGB (const QColor& color) -{ - return - (color.red() & 0xFF) << 0x00 | - (color.green() & 0xFF) << 0x08 | - (color.blue() & 0xFF) << 0x10 | - (color.alpha() & 0xFF) << 0x18; -} - -// ============================================================================= -// -QColor GLCompiler::indexColorForID (int id) const -{ - // Calculate a color based from this index. This method caters for - // 16777216 objects. I don't think that will be exceeded anytime soon. :) - int r = (id / 0x10000) % 0x100, - g = (id / 0x100) % 0x100, - b = id % 0x100; - - return QColor (r, g, b); -} - -// ============================================================================= -// -QColor GLCompiler::getColorForPolygon (LDPolygon& poly, LDObjectPtr topobj, - EVBOComplement complement) const -{ - QColor qcol; - - switch (complement) - { - case VBOCM_Surfaces: - case VBOCM_NumComplements: - return QColor(); - - case VBOCM_BFCFrontColors: - qcol = g_BFCFrontColor; - break; - - case VBOCM_BFCBackColors: - qcol = g_BFCBackColor; - break; - - case VBOCM_PickColors: - return indexColorForID (topobj->id()); - - case VBOCM_RandomColors: - qcol = topobj->randomColor(); - break; - - case VBOCM_NormalColors: - if (poly.color == MainColorIndex) - { - if (topobj->color() == MainColor()) - qcol = GLRenderer::getMainColor(); - else - qcol = topobj->color().faceColor(); - } - elif (poly.color == EdgeColorIndex) - { - qcol = Luma (QColor (cfg::BackgroundColor)) > 40 ? Qt::black : Qt::white; - } - else - { - LDColor col = LDColor::fromIndex (poly.color); - - if (col) - qcol = col.faceColor(); - } - break; - } - - if (not qcol.isValid()) - { - // The color was unknown. Use main color to make the polygon at least - // not appear pitch-black. - if (poly.num != 2 and poly.num != 5) - qcol = GLRenderer::getMainColor(); - else - qcol = Qt::black; - - // Warn about the unknown color, but only once. - if (not g_warnedColors.contains (poly.color)) - { - print ("Unknown color %1!\n", poly.color); - g_warnedColors << poly.color; - } - - return qcol; - } - - double blendAlpha = 0.0; - - if (topobj->isSelected()) - blendAlpha = 1.0; - elif (topobj == m_renderer->objectAtCursor()) - blendAlpha = 0.5; - - if (blendAlpha != 0.0) - { - QColor selcolor (cfg::SelectColorBlend); - double denom = blendAlpha + 1.0; - qcol.setRed ((qcol.red() + (selcolor.red() * blendAlpha)) / denom); - qcol.setGreen ((qcol.green() + (selcolor.green() * blendAlpha)) / denom); - qcol.setBlue ((qcol.blue() + (selcolor.blue() * blendAlpha)) / denom); - } - - return qcol; -} - -// ============================================================================= -// -void GLCompiler::needMerge() -{ - for (int i = 0; i < countof (m_vboChanged); ++i) - m_vboChanged[i] = true; -} - -// ============================================================================= -// -void GLCompiler::stageForCompilation (LDObjectPtr obj) -{ - /* - g_objectOrigins[obj] = format ("%1:%2 (%3)", - obj->document()->getDisplayName(), obj->lineNumber(), obj->typeName()); - */ - - m_staged << LDObjectWeakPtr (obj); -} - -// ============================================================================= -// -void GLCompiler::unstage (LDObjectPtr obj) -{ - m_staged.removeOne (LDObjectWeakPtr (obj)); -} - -// ============================================================================= -// -void GLCompiler::compileDocument (LDDocumentPtr doc) -{ - if (doc == null) - return; - - for (LDObjectPtr obj : doc->objects()) - compileObject (obj); -} - -// ============================================================================= -// -void GLCompiler::compileStaged() -{ - RemoveDuplicates (m_staged); - - for (auto it = m_staged.begin(); it != m_staged.end(); ++it) - { - if (*it == null) - continue; - - compileObject (*it); - } - - m_staged.clear(); -} - -// ============================================================================= -// -void GLCompiler::prepareVBO (int vbonum) -{ - // Compile anything that still awaits it - compileStaged(); - - if (not m_vboChanged[vbonum]) - return; - - QVector<GLfloat> vbodata; - - for (auto it = m_objectInfo.begin(); it != m_objectInfo.end();) - { - if (it.key() == null) - { - it = m_objectInfo.erase (it); - continue; - } - - if (it.key().toStrongRef()->document() == CurrentDocument() - and not it.key().toStrongRef()->isHidden()) - { - vbodata += it->data[vbonum]; - } - - ++it; - } - - glBindBuffer (GL_ARRAY_BUFFER, m_vbo[vbonum]); - glBufferData (GL_ARRAY_BUFFER, vbodata.size() * sizeof(GLfloat), vbodata.constData(), GL_STATIC_DRAW); - glBindBuffer (GL_ARRAY_BUFFER, 0); - CHECK_GL_ERROR(); - m_vboChanged[vbonum] = false; - m_vboSizes[vbonum] = vbodata.size(); -} - -// ============================================================================= -// -void GLCompiler::dropObject (LDObjectPtr obj) -{ - auto it = m_objectInfo.find (obj); - - if (it != m_objectInfo.end()) - { - m_objectInfo.erase (it); - needMerge(); - } - - unstage (obj); -} - -// ============================================================================= -// -void GLCompiler::compileObject (LDObjectPtr obj) -{ -// print ("Compile %1\n", g_objectOrigins[obj]); - - if (obj == null or obj->document() == null or obj->document().toStrongRef()->isImplicit()) - return; - - ObjectVBOInfo info; - info.isChanged = true; - dropObject (obj); - - switch (obj->type()) - { - // Note: We cannot split quads into triangles here, it would mess up the - // wireframe view. Quads must go into separate vbos. - case OBJ_Triangle: - case OBJ_Quad: - case OBJ_Line: - case OBJ_CondLine: - { - LDPolygon* poly = obj->getPolygon(); - poly->id = obj->id(); - compilePolygon (*poly, obj, &info); - delete poly; - break; - } - - case OBJ_Subfile: - { - LDSubfilePtr ref = obj.staticCast<LDSubfile>(); - auto data = ref->inlinePolygons(); - - for (LDPolygon& poly : data) - { - poly.id = obj->id(); - compilePolygon (poly, obj, &info); - } - break; - } - - default: - break; - } - - m_objectInfo[obj] = info; - needMerge(); -} - -// ============================================================================= -// -void GLCompiler::compilePolygon (LDPolygon& poly, LDObjectPtr topobj, ObjectVBOInfo* objinfo) -{ - EVBOSurface surface; - int numverts; - - switch (poly.num) - { - case 2: surface = VBOSF_Lines; numverts = 2; break; - case 3: surface = VBOSF_Triangles; numverts = 3; break; - case 4: surface = VBOSF_Quads; numverts = 4; break; - case 5: surface = VBOSF_CondLines; numverts = 2; break; - default: return; - } - - for (EVBOComplement complement = VBOCM_First; complement < VBOCM_NumComplements; ++complement) - { - const int vbonum = vboNumber (surface, complement); - QVector<GLfloat>& vbodata = objinfo->data[vbonum]; - const QColor color = getColorForPolygon (poly, topobj, complement); - - for (int vert = 0; vert < numverts; ++vert) - { - if (complement == VBOCM_Surfaces) - { - // Write coordinates. Apparently Z must be flipped too? - vbodata << poly.vertices[vert].x() - << -poly.vertices[vert].y() - << -poly.vertices[vert].z(); - } - else - { - vbodata << ((GLfloat) color.red()) / 255.0f - << ((GLfloat) color.green()) / 255.0f - << ((GLfloat) color.blue()) / 255.0f - << ((GLfloat) color.alpha()) / 255.0f; - } - } - } -} - -void GLCompiler::setRenderer (GLRenderer* renderer) -{ - m_renderer = renderer; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/glCompiler.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,423 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define GL_GLEXT_PROTOTYPES +#include <GL/glu.h> +#include <GL/glext.h> +#include "glCompiler.h" +#include "ldObject.h" +#include "colors.h" +#include "ldDocument.h" +#include "miscallenous.h" +#include "glRenderer.h" +#include "dialogs.h" + +struct GLErrorInfo +{ + GLenum value; + QString text; +}; + +static const GLErrorInfo g_GLErrors[] = +{ + { GL_NO_ERROR, "No error" }, + { GL_INVALID_ENUM, "Unacceptable enumerator passed" }, + { GL_INVALID_VALUE, "Numeric argument out of range" }, + { GL_INVALID_OPERATION, "The operation is not allowed to be done in this state" }, + { GL_INVALID_FRAMEBUFFER_OPERATION, "Framebuffer object is not complete"}, + { GL_OUT_OF_MEMORY, "Out of memory" }, + { GL_STACK_UNDERFLOW, "The operation would have caused an underflow" }, + { GL_STACK_OVERFLOW, "The operation would have caused an overflow" }, +}; + +CFGENTRY (String, SelectColorBlend, "#0080FF") +EXTERN_CFGENTRY (Bool, BlackEdges) +EXTERN_CFGENTRY (String, BackgroundColor) + +static QList<int> g_warnedColors; +static const QColor g_BFCFrontColor (64, 192, 80); +static const QColor g_BFCBackColor (208, 64, 64); + +// static QMap<LDObjectPtr, String> g_objectOrigins; + +// ============================================================================= +// +void CheckGLErrorImpl (const char* file, int line) +{ + QString errmsg; + GLenum errnum = glGetError(); + + if (errnum == GL_NO_ERROR) + return; + + for (const GLErrorInfo& err : g_GLErrors) + { + if (err.value == errnum) + { + errmsg = err.text; + break; + } + } + + print ("OpenGL ERROR: at %1:%2: %3", Basename (QString (file)), line, errmsg); +} + +// ============================================================================= +// +GLCompiler::GLCompiler (GLRenderer* renderer) : + m_renderer (renderer) +{ + needMerge(); + memset (m_vboSizes, 0, sizeof m_vboSizes); +} + +// ============================================================================= +// +void GLCompiler::initialize() +{ + initializeOpenGLFunctions(); + glGenBuffers (g_numVBOs, &m_vbo[0]); + CHECK_GL_ERROR(); +} + +// ============================================================================= +// +GLCompiler::~GLCompiler() +{ + glDeleteBuffers (g_numVBOs, &m_vbo[0]); + CHECK_GL_ERROR(); + + if (m_renderer != null) + m_renderer->setCompiler (null); +} + +// ============================================================================= +// +uint32 GLCompiler::colorToRGB (const QColor& color) +{ + return + (color.red() & 0xFF) << 0x00 | + (color.green() & 0xFF) << 0x08 | + (color.blue() & 0xFF) << 0x10 | + (color.alpha() & 0xFF) << 0x18; +} + +// ============================================================================= +// +QColor GLCompiler::indexColorForID (int id) const +{ + // Calculate a color based from this index. This method caters for + // 16777216 objects. I don't think that will be exceeded anytime soon. :) + int r = (id / 0x10000) % 0x100, + g = (id / 0x100) % 0x100, + b = id % 0x100; + + return QColor (r, g, b); +} + +// ============================================================================= +// +QColor GLCompiler::getColorForPolygon (LDPolygon& poly, LDObjectPtr topobj, + EVBOComplement complement) const +{ + QColor qcol; + + switch (complement) + { + case VBOCM_Surfaces: + case VBOCM_NumComplements: + return QColor(); + + case VBOCM_BFCFrontColors: + qcol = g_BFCFrontColor; + break; + + case VBOCM_BFCBackColors: + qcol = g_BFCBackColor; + break; + + case VBOCM_PickColors: + return indexColorForID (topobj->id()); + + case VBOCM_RandomColors: + qcol = topobj->randomColor(); + break; + + case VBOCM_NormalColors: + if (poly.color == MainColorIndex) + { + if (topobj->color() == MainColor()) + qcol = GLRenderer::getMainColor(); + else + qcol = topobj->color().faceColor(); + } + elif (poly.color == EdgeColorIndex) + { + qcol = Luma (QColor (cfg::BackgroundColor)) > 40 ? Qt::black : Qt::white; + } + else + { + LDColor col = LDColor::fromIndex (poly.color); + + if (col) + qcol = col.faceColor(); + } + break; + } + + if (not qcol.isValid()) + { + // The color was unknown. Use main color to make the polygon at least + // not appear pitch-black. + if (poly.num != 2 and poly.num != 5) + qcol = GLRenderer::getMainColor(); + else + qcol = Qt::black; + + // Warn about the unknown color, but only once. + if (not g_warnedColors.contains (poly.color)) + { + print ("Unknown color %1!\n", poly.color); + g_warnedColors << poly.color; + } + + return qcol; + } + + double blendAlpha = 0.0; + + if (topobj->isSelected()) + blendAlpha = 1.0; + elif (topobj == m_renderer->objectAtCursor()) + blendAlpha = 0.5; + + if (blendAlpha != 0.0) + { + QColor selcolor (cfg::SelectColorBlend); + double denom = blendAlpha + 1.0; + qcol.setRed ((qcol.red() + (selcolor.red() * blendAlpha)) / denom); + qcol.setGreen ((qcol.green() + (selcolor.green() * blendAlpha)) / denom); + qcol.setBlue ((qcol.blue() + (selcolor.blue() * blendAlpha)) / denom); + } + + return qcol; +} + +// ============================================================================= +// +void GLCompiler::needMerge() +{ + for (int i = 0; i < countof (m_vboChanged); ++i) + m_vboChanged[i] = true; +} + +// ============================================================================= +// +void GLCompiler::stageForCompilation (LDObjectPtr obj) +{ + /* + g_objectOrigins[obj] = format ("%1:%2 (%3)", + obj->document()->getDisplayName(), obj->lineNumber(), obj->typeName()); + */ + + m_staged << LDObjectWeakPtr (obj); +} + +// ============================================================================= +// +void GLCompiler::unstage (LDObjectPtr obj) +{ + m_staged.removeOne (LDObjectWeakPtr (obj)); +} + +// ============================================================================= +// +void GLCompiler::compileDocument (LDDocumentPtr doc) +{ + if (doc == null) + return; + + for (LDObjectPtr obj : doc->objects()) + compileObject (obj); +} + +// ============================================================================= +// +void GLCompiler::compileStaged() +{ + RemoveDuplicates (m_staged); + + for (auto it = m_staged.begin(); it != m_staged.end(); ++it) + { + if (*it == null) + continue; + + compileObject (*it); + } + + m_staged.clear(); +} + +// ============================================================================= +// +void GLCompiler::prepareVBO (int vbonum) +{ + // Compile anything that still awaits it + compileStaged(); + + if (not m_vboChanged[vbonum]) + return; + + QVector<GLfloat> vbodata; + + for (auto it = m_objectInfo.begin(); it != m_objectInfo.end();) + { + if (it.key() == null) + { + it = m_objectInfo.erase (it); + continue; + } + + if (it.key().toStrongRef()->document() == CurrentDocument() + and not it.key().toStrongRef()->isHidden()) + { + vbodata += it->data[vbonum]; + } + + ++it; + } + + glBindBuffer (GL_ARRAY_BUFFER, m_vbo[vbonum]); + glBufferData (GL_ARRAY_BUFFER, vbodata.size() * sizeof(GLfloat), vbodata.constData(), GL_STATIC_DRAW); + glBindBuffer (GL_ARRAY_BUFFER, 0); + CHECK_GL_ERROR(); + m_vboChanged[vbonum] = false; + m_vboSizes[vbonum] = vbodata.size(); +} + +// ============================================================================= +// +void GLCompiler::dropObject (LDObjectPtr obj) +{ + auto it = m_objectInfo.find (obj); + + if (it != m_objectInfo.end()) + { + m_objectInfo.erase (it); + needMerge(); + } + + unstage (obj); +} + +// ============================================================================= +// +void GLCompiler::compileObject (LDObjectPtr obj) +{ +// print ("Compile %1\n", g_objectOrigins[obj]); + + if (obj == null or obj->document() == null or obj->document().toStrongRef()->isImplicit()) + return; + + ObjectVBOInfo info; + info.isChanged = true; + dropObject (obj); + + switch (obj->type()) + { + // Note: We cannot split quads into triangles here, it would mess up the + // wireframe view. Quads must go into separate vbos. + case OBJ_Triangle: + case OBJ_Quad: + case OBJ_Line: + case OBJ_CondLine: + { + LDPolygon* poly = obj->getPolygon(); + poly->id = obj->id(); + compilePolygon (*poly, obj, &info); + delete poly; + break; + } + + case OBJ_Subfile: + { + LDSubfilePtr ref = obj.staticCast<LDSubfile>(); + auto data = ref->inlinePolygons(); + + for (LDPolygon& poly : data) + { + poly.id = obj->id(); + compilePolygon (poly, obj, &info); + } + break; + } + + default: + break; + } + + m_objectInfo[obj] = info; + needMerge(); +} + +// ============================================================================= +// +void GLCompiler::compilePolygon (LDPolygon& poly, LDObjectPtr topobj, ObjectVBOInfo* objinfo) +{ + EVBOSurface surface; + int numverts; + + switch (poly.num) + { + case 2: surface = VBOSF_Lines; numverts = 2; break; + case 3: surface = VBOSF_Triangles; numverts = 3; break; + case 4: surface = VBOSF_Quads; numverts = 4; break; + case 5: surface = VBOSF_CondLines; numverts = 2; break; + default: return; + } + + for (EVBOComplement complement = VBOCM_First; complement < VBOCM_NumComplements; ++complement) + { + const int vbonum = vboNumber (surface, complement); + QVector<GLfloat>& vbodata = objinfo->data[vbonum]; + const QColor color = getColorForPolygon (poly, topobj, complement); + + for (int vert = 0; vert < numverts; ++vert) + { + if (complement == VBOCM_Surfaces) + { + // Write coordinates. Apparently Z must be flipped too? + vbodata << poly.vertices[vert].x() + << -poly.vertices[vert].y() + << -poly.vertices[vert].z(); + } + else + { + vbodata << ((GLfloat) color.red()) / 255.0f + << ((GLfloat) color.green()) / 255.0f + << ((GLfloat) color.blue()) / 255.0f + << ((GLfloat) color.alpha()) / 255.0f; + } + } + } +} + +void GLCompiler::setRenderer (GLRenderer* renderer) +{ + m_renderer = renderer; +}
--- a/src/glRenderer.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1642 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#define GL_GLEXT_PROTOTYPES -#include <GL/glu.h> -#include <GL/glext.h> -#include <QGLWidget> -#include <QWheelEvent> -#include <QMouseEvent> -#include <QContextMenuEvent> -#include <QInputDialog> -#include <QToolTip> -#include <QTextDocument> -#include <QTimer> -#include <GL/glu.h> -#include "main.h" -#include "configuration.h" -#include "ldDocument.h" -#include "glRenderer.h" -#include "colors.h" -#include "mainWindow.h" -#include "miscallenous.h" -#include "editHistory.h" -#include "dialogs.h" -#include "addObjectDialog.h" -#include "messageLog.h" -#include "glCompiler.h" -#include "primitives.h" - -const LDFixedCamera g_FixedCameras[6] = -{ - {{ 1, 0, 0 }, X, Z, false, false, false }, // top - {{ 0, 0, 0 }, X, Y, false, true, false }, // front - {{ 0, 1, 0 }, Z, Y, true, true, false }, // left - {{ -1, 0, 0 }, X, Z, false, true, true }, // bottom - {{ 0, 0, 0 }, X, Y, true, true, true }, // back - {{ 0, -1, 0 }, Z, Y, false, true, true }, // right -}; - -CFGENTRY (String, BackgroundColor, "#FFFFFF") -CFGENTRY (String, MainColor, "#A0A0A0") -CFGENTRY (Float, MainColorAlpha, 1.0) -CFGENTRY (Int, LineThickness, 2) -CFGENTRY (Bool, BFCRedGreenView, false) -CFGENTRY (Int, Camera, EFreeCamera) -CFGENTRY (Bool, BlackEdges, false) -CFGENTRY (Bool, DrawAxes, false) -CFGENTRY (Bool, DrawWireframe, false) -CFGENTRY (Bool, UseLogoStuds, false) -CFGENTRY (Bool, AntiAliasedLines, true) -CFGENTRY (Bool, RandomColors, false) -CFGENTRY (Bool, HighlightObjectBelowCursor, true) -CFGENTRY (Bool, DrawSurfaces, true) -CFGENTRY (Bool, DrawEdgeLines, true) -CFGENTRY (Bool, DrawConditionalLines, 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") -}; - -struct LDGLAxis -{ - const QColor col; - const Vertex vert; -}; - -// Definitions for visual axes, drawn on the screen -static const LDGLAxis g_GLAxes[3] = -{ - { QColor (192, 96, 96), Vertex (10000, 0, 0) }, // X - { QColor (48, 192, 48), Vertex (0, 10000, 0) }, // Y - { QColor (48, 112, 192), Vertex (0, 0, 10000) }, // Z -}; - -static bool RendererInitialized (false); - -// ============================================================================= -// -GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) -{ - m_isPicking = false; - m_camera = (ECamera) cfg::Camera; - m_drawToolTip = false; - m_editmode = AbstractEditMode::createByType (this, EditModeType::Select); - m_panning = false; - m_compiler = new GLCompiler (this); - setDrawOnly (false); - setMessageLog (null); - m_width = m_height = -1; - m_position3D = Origin; - m_toolTipTimer = new QTimer (this); - m_toolTipTimer->setSingleShot (true); - m_isCameraMoving = false; - m_thinBorderPen = QPen (QColor (0, 0, 0, 208), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); - m_thinBorderPen.setWidth (1); - setAcceptDrops (true); - connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer())); - - // Init camera icons - for (ECamera cam = EFirstCamera; cam < ENumCameras; ++cam) - { - QString iconname = format ("camera-%1", tr (g_CameraNames[cam]).toLower()); - CameraIcon* info = &m_cameraIcons[cam]; - info->img = new QPixmap (GetIcon (iconname)); - info->cam = cam; - } - - calcCameraIcons(); -} - -// ============================================================================= -// -GLRenderer::~GLRenderer() -{ - for (int i = 0; i < 6; ++i) - delete currentDocumentData().overlays[i].img; - - for (CameraIcon& info : m_cameraIcons) - delete info.img; - - if (messageLog()) - messageLog()->setRenderer (null); - - m_compiler->setRenderer (null); - delete m_compiler; - delete m_editmode; - - glDeleteBuffers (1, &m_axesVBO); - glDeleteBuffers (1, &m_axesColorVBO); -} - -// ============================================================================= -// 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 (cfg::AntiAliasedLines) - { - 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::needZoomToFit() -{ - if (document() != null) - currentDocumentData().needZoomToFit = true; -} - -// ============================================================================= -// -void GLRenderer::resetAngles() -{ - rot (X) = 30.0f; - rot (Y) = 325.f; - pan (X) = pan (Y) = rot (Z) = 0.0f; - needZoomToFit(); -} - -// ============================================================================= -// -void GLRenderer::resetAllAngles() -{ - ECamera oldcam = camera(); - - for (int i = 0; i < 7; ++i) - { - setCamera ((ECamera) i); - resetAngles(); - } - - setCamera (oldcam); -} - -// ============================================================================= -// -void GLRenderer::initializeGL() -{ -#ifdef USE_QT5 - initializeOpenGLFunctions(); -#endif - setBackground(); - glLineWidth (cfg::LineThickness); - glLineStipple (1, 0x6666); - setAutoFillBackground (false); - setMouseTracking (true); - setFocusPolicy (Qt::WheelFocus); - compiler()->initialize(); - initializeAxes(); - RendererInitialized = true; -} - -// ============================================================================= -// -void GLRenderer::initializeAxes() -{ - float axesdata[18]; - float colordata[18]; - memset (axesdata, 0, sizeof axesdata); - - for (int i = 0; i < 3; ++i) - { - for_axes (ax) - { - axesdata[(i * 6) + ax] = g_GLAxes[i].vert[ax]; - axesdata[(i * 6) + 3 + ax] = -g_GLAxes[i].vert[ax]; - } - - for (int j = 0; j < 2; ++j) - { - colordata[(i * 6) + (j * 3) + 0] = g_GLAxes[i].col.red(); - colordata[(i * 6) + (j * 3) + 1] = g_GLAxes[i].col.green(); - colordata[(i * 6) + (j * 3) + 2] = g_GLAxes[i].col.blue(); - } - } - - glGenBuffers (1, &m_axesVBO); - glBindBuffer (GL_ARRAY_BUFFER, m_axesVBO); - glBufferData (GL_ARRAY_BUFFER, sizeof axesdata, axesdata, GL_STATIC_DRAW); - glGenBuffers (1, &m_axesColorVBO); - glBindBuffer (GL_ARRAY_BUFFER, m_axesColorVBO); - glBufferData (GL_ARRAY_BUFFER, sizeof colordata, colordata, GL_STATIC_DRAW); - glBindBuffer (GL_ARRAY_BUFFER, 0); -} - -// ============================================================================= -// -QColor GLRenderer::getMainColor() -{ - QColor col (cfg::MainColor); - - if (not col.isValid()) - return QColor (0, 0, 0); - - col.setAlpha (cfg::MainColorAlpha * 255.f); - return col; -} - -// ============================================================================= -// -void GLRenderer::setBackground() -{ - if (isPicking()) - { - glClearColor (0.0f, 0.0f, 0.0f, 1.0f); - return; - } - - QColor col (cfg::BackgroundColor); - - if (not col.isValid()) - return; - - col.setAlpha (255); - - m_darkbg = Luma (col) < 80; - m_bgcolor = col; - qglClearColor (col); -} - -// ============================================================================= -// -void GLRenderer::refresh() -{ - update(); - - if (isVisible()) - swapBuffers(); -} - -// ============================================================================= -// -void GLRenderer::hardRefresh() -{ - if (not RendererInitialized) - return; - - compiler()->compileDocument (CurrentDocument()); - refresh(); -} - -// ============================================================================= -// -void GLRenderer::resizeGL (int w, int h) -{ - m_width = w; - m_height = h; - - calcCameraIcons(); - - glViewport (0, 0, w, h); - glMatrixMode (GL_PROJECTION); - glLoadIdentity(); - gluPerspective (45.0f, (double) w / (double) h, 1.0f, 10000.0f); - glMatrixMode (GL_MODELVIEW); -} - -// ============================================================================= -// -void GLRenderer::drawGLScene() -{ - if (document() == null) - return; - - if (currentDocumentData().needZoomToFit) - { - currentDocumentData().needZoomToFit = false; - zoomAllToFit(); - } - - if (cfg::DrawWireframe and not isPicking()) - glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); - - glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable (GL_DEPTH_TEST); - - if (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 (camera() != EFrontCamera and 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 (camera() == 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); - } - - glEnableClientState (GL_VERTEX_ARRAY); - glEnableClientState (GL_COLOR_ARRAY); - - if (isPicking()) - { - drawVBOs (VBOSF_Triangles, VBOCM_PickColors, GL_TRIANGLES); - drawVBOs (VBOSF_Quads, VBOCM_PickColors, GL_QUADS); - drawVBOs (VBOSF_Lines, VBOCM_PickColors, GL_LINES); - drawVBOs (VBOSF_CondLines, VBOCM_PickColors, GL_LINES); - } - else - { - if (cfg::BFCRedGreenView) - { - glEnable (GL_CULL_FACE); - glCullFace (GL_BACK); - drawVBOs (VBOSF_Triangles, VBOCM_BFCFrontColors, GL_TRIANGLES); - drawVBOs (VBOSF_Quads, VBOCM_BFCFrontColors, GL_QUADS); - glCullFace (GL_FRONT); - drawVBOs (VBOSF_Triangles, VBOCM_BFCBackColors, GL_TRIANGLES); - drawVBOs (VBOSF_Quads, VBOCM_BFCBackColors, GL_QUADS); - glDisable (GL_CULL_FACE); - } - else - { - if (cfg::RandomColors) - { - drawVBOs (VBOSF_Triangles, VBOCM_RandomColors, GL_TRIANGLES); - drawVBOs (VBOSF_Quads, VBOCM_RandomColors, GL_QUADS); - } - else - { - drawVBOs (VBOSF_Triangles, VBOCM_NormalColors, GL_TRIANGLES); - drawVBOs (VBOSF_Quads, VBOCM_NormalColors, GL_QUADS); - } - } - - drawVBOs (VBOSF_Lines, VBOCM_NormalColors, GL_LINES); - glEnable (GL_LINE_STIPPLE); - drawVBOs (VBOSF_CondLines, VBOCM_NormalColors, GL_LINES); - glDisable (GL_LINE_STIPPLE); - - if (cfg::DrawAxes) - { - glBindBuffer (GL_ARRAY_BUFFER, m_axesVBO); - glVertexPointer (3, GL_FLOAT, 0, NULL); - glBindBuffer (GL_ARRAY_BUFFER, m_axesVBO); - glColorPointer (3, GL_FLOAT, 0, NULL); - glDrawArrays (GL_LINES, 0, 6); - CHECK_GL_ERROR(); - } - } - - glPopMatrix(); - glBindBuffer (GL_ARRAY_BUFFER, 0); - glDisableClientState (GL_VERTEX_ARRAY); - glDisableClientState (GL_COLOR_ARRAY); - CHECK_GL_ERROR(); - glDisable (GL_CULL_FACE); - glMatrixMode (GL_MODELVIEW); - glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); -} - -// ============================================================================= -// -void GLRenderer::drawVBOs (EVBOSurface surface, EVBOComplement colors, GLenum type) -{ - // Filter this through some configuration options - if ((Eq (surface, VBOSF_Quads, VBOSF_Triangles) and cfg::DrawSurfaces == false) or - (surface == VBOSF_Lines and cfg::DrawEdgeLines == false) or - (surface == VBOSF_CondLines and cfg::DrawConditionalLines == false)) - { - return; - } - - int surfacenum = m_compiler->vboNumber (surface, VBOCM_Surfaces); - int colornum = m_compiler->vboNumber (surface, colors); - m_compiler->prepareVBO (surfacenum); - m_compiler->prepareVBO (colornum); - GLuint surfacevbo = m_compiler->vbo (surfacenum); - GLuint colorvbo = m_compiler->vbo (colornum); - GLsizei count = m_compiler->vboSize (surfacenum) / 3; - - if (count > 0) - { - glBindBuffer (GL_ARRAY_BUFFER, surfacevbo); - glVertexPointer (3, GL_FLOAT, 0, null); - CHECK_GL_ERROR(); - glBindBuffer (GL_ARRAY_BUFFER, colorvbo); - glColorPointer (4, GL_FLOAT, 0, null); - CHECK_GL_ERROR(); - glDrawArrays (type, 0, count); - CHECK_GL_ERROR(); - } -} - -// ============================================================================= -// 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 LDFixedCamera* cam = &g_FixedCameras[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::Coordinate); - cy = Grid::Snap (cy, Grid::Coordinate); - } - - cx *= negXFac; - cy *= negYFac; - - RoundToDecimals (cx, 4); - RoundToDecimals (cy, 4); - - // Create the vertex from the coordinates - pos3d.setCoordinate (axisX, cx); - pos3d.setCoordinate (axisY, cy); - pos3d.setCoordinate ((Axis) (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) -{ - GLfloat m[16]; - const LDFixedCamera* cam = &g_FixedCameras[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.setX ((m[0] * x) + (m[1] * y) + (m[2] * z) + m[3]); - transformed.setY ((m[4] * x) + (m[5] * y) + (m[6] * z) + m[7]); - transformed.setZ ((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); -} - -QPen GLRenderer::textPen() const -{ - return QPen (m_darkbg ? Qt::white : Qt::black); -} - -QPen GLRenderer::linePen() const -{ - QPen linepen (m_thinBorderPen); - linepen.setWidth (2); - linepen.setColor (Luma (m_bgcolor) < 40 ? Qt::white : Qt::black); - return linepen; -} - -// ============================================================================= -// -void GLRenderer::paintEvent (QPaintEvent*) -{ - doMakeCurrent(); - m_virtWidth = zoom(); - m_virtHeight = (m_height * m_virtWidth) / m_width; - initGLData(); - drawGLScene(); - - QPainter paint (this); - QFontMetrics metrics = QFontMetrics (QFont()); - paint.setRenderHint (QPainter::HighQualityAntialiasing); - - // If we wish to only draw the brick, stop here - if (isDrawOnly()) - return; - -#ifndef RELEASE - if (not isPicking()) - { - QString text = format ("Rotation: (%1, %2, %3)\nPanning: (%4, %5), Zoom: %6", - rot(X), rot(Y), rot(Z), pan(X), pan(Y), zoom()); - QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); - paint.setPen (textPen()); - paint.drawText ((width() - textSize.width()) / 2, height() - textSize.height(), textSize.width(), - textSize.height(), Qt::AlignCenter, text); - } -#endif - - if (camera() != EFreeCamera and not isPicking()) - { - // Paint the overlay image if we have one - const LDGLOverlay& overlay = currentDocumentData().overlays[camera()]; - - if (overlay.img != null) - { - QPoint v0 = coordconv3_2 (currentDocumentData().overlays[camera()].v0), - v1 = coordconv3_2 (currentDocumentData().overlays[camera()].v1); - - QRect targRect (v0.x(), v0.y(), Abs (v1.x() - v0.x()), Abs (v1.y() - v0.y())), - srcRect (0, 0, overlay.img->width(), overlay.img->height()); - paint.drawImage (targRect, *overlay.img, srcRect); - } - - // Paint the coordinates onto the screen. - QString text = format (tr ("X: %1, Y: %2, Z: %3"), m_position3D[X], m_position3D[Y], m_position3D[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); - } - - if (not isPicking()) - { - // Draw edit mode HUD - m_editmode->render (paint); - - // 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 camera icons - for (CameraIcon& info : m_cameraIcons) - { - // Don't draw the free camera icon when in draw mode - if (&info == &m_cameraIcons[EFreeCamera] and not m_editmode->allowFreeCamera()) - continue; - - paint.drawPixmap (info.destRect, *info.img, info.srcRect); - } - - QString formatstr = tr ("%1 Camera"); - - // Draw a label for the current camera in the bottom left corner - { - const int margin = 4; - - QString label; - label = format (formatstr, tr (g_CameraNames[camera()])); - paint.setPen (textPen()); - paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label); - } - - // Tool tips - if (m_drawToolTip) - { - if (not m_cameraIcons[m_toolTipCamera].destRect.contains (m_mousePosition)) - m_drawToolTip = false; - else - { - QString label = format (formatstr, tr (g_CameraNames[m_toolTipCamera])); - QToolTip::showText (m_globalpos, label); - } - } - } - - // Message log - if (messageLog()) - { - int y = 0; - const int margin = 2; - QColor penColor = textPen().color(); - - for (const MessageManager::Line& line : messageLog()->getLines()) - { - penColor.setAlphaF (line.alpha); - paint.setPen (penColor); - paint.drawText (QPoint (margin, y + margin + metrics.ascent()), line.text); - y += metrics.height(); - } - } -} - -// ============================================================================= -// -void GLRenderer::drawBlip (QPainter& paint, QPointF 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::clampAngle (double& angle) const -{ - while (angle < 0) - angle += 360.0; - - while (angle > 360.0) - angle -= 360.0; -} - -// ============================================================================= -// -void GLRenderer::mouseReleaseEvent (QMouseEvent* ev) -{ - const bool wasLeft = (m_lastButtons & Qt::LeftButton) and not (ev->buttons() & Qt::LeftButton); - - Qt::MouseButtons releasedbuttons = m_lastButtons & ~ev->buttons(); - - if (m_panning) - m_panning = false; - - if (wasLeft) - { - // Check if we selected a camera icon - if (not mouseHasMoved()) - { - for (CameraIcon & info : m_cameraIcons) - { - if (info.destRect.contains (ev->pos())) - { - setCamera (info.cam); - goto end; - } - } - } - } - - if (not isDrawOnly()) - { - AbstractEditMode::MouseEventData data; - data.ev = ev; - data.mouseMoved = mouseHasMoved(); - data.keymods = m_keymods; - data.releasedButtons = releasedbuttons; - - if (m_editmode->mouseReleased (data)) - goto end; - } - -end: - update(); - m_totalmove = 0; -} - -// ============================================================================= -// -void GLRenderer::mousePressEvent (QMouseEvent* ev) -{ - m_totalmove = 0; - m_lastButtons = ev->buttons(); - - if (m_editmode->mousePressed (ev)) - ev->accept(); -} - -// ============================================================================= -// -void GLRenderer::mouseMoveEvent (QMouseEvent* ev) -{ - int dx = ev->x() - m_mousePosition.x(); - int dy = ev->y() - m_mousePosition.y(); - m_totalmove += Abs (dx) + Abs (dy); - setCameraMoving (false); - - if (not m_editmode->mouseMoved (ev)) - { - const bool left = ev->buttons() & Qt::LeftButton, - mid = ev->buttons() & Qt::MidButton, - shift = ev->modifiers() & Qt::ShiftModifier; - - if (mid or (left and shift)) - { - pan (X) += 0.03f * dx * (zoom() / 7.5f); - pan (Y) -= 0.03f * dy * (zoom() / 7.5f); - m_panning = true; - setCameraMoving (true); - } - elif (left and camera() == EFreeCamera) - { - rot (X) = rot (X) + dy; - rot (Y) = rot (Y) + dx; - - clampAngle (rot (X)); - clampAngle (rot (Y)); - setCameraMoving (true); - } - } - - // Start the tool tip timer - if (not m_drawToolTip) - m_toolTipTimer->start (500); - - // Update 2d position - m_mousePosition = ev->pos(); - m_globalpos = ev->globalPos(); - -#ifndef USE_QT5 - m_mousePositionF = ev->posF(); -#else - m_mousePositionF = ev->localPos(); -#endif - - // Calculate 3d position of the cursor - m_position3D = (camera() != EFreeCamera) ? coordconv2_3 (m_mousePosition, true) : Origin; - - highlightCursorObject(); - update(); - ev->accept(); -} - -// ============================================================================= -// -void GLRenderer::keyPressEvent (QKeyEvent* ev) -{ - m_keymods = ev->modifiers(); -} - -// ============================================================================= -// -void GLRenderer::keyReleaseEvent (QKeyEvent* ev) -{ - m_keymods = ev->modifiers(); - m_editmode->keyReleased (ev); - update(); -} - -// ============================================================================= -// -void GLRenderer::wheelEvent (QWheelEvent* ev) -{ - doMakeCurrent(); - - zoomNotch (ev->delta() > 0); - zoom() = Clamp (zoom(), 0.01, 10000.0); - setCameraMoving (true); - 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 ECamera cam) -{ - // The edit mode may forbid the free camera. - if (cam == EFreeCamera and not m_editmode->allowFreeCamera()) - return; - - m_camera = cam; - cfg::Camera = (int) cam; - g_win->updateEditModeActions(); -} - -// ============================================================================= -// -void GLRenderer::pick (int mouseX, int mouseY, bool additive) -{ - pick (QRect (mouseX, mouseY, mouseX + 1, mouseY + 1), additive); -} - -// ============================================================================= -// -void GLRenderer::pick (QRect const& range, bool additive) -{ - doMakeCurrent(); - - // Clear the selection if we do not wish to add to it. - if (not additive) - { - LDObjectList oldsel = Selection(); - CurrentDocument()->clearSelection(); - - for (LDObjectPtr obj : oldsel) - compileObject (obj); - } - - // Paint the picking scene - setPicking (true); - drawGLScene(); - - int x0 = range.left(); - int y0 = range.top(); - int x1 = x0 + range.width(); - int y1 = y0 + range.height(); - - // 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); - - LDObjectPtr removedObj; - QList<qint32> indices; - - // Go through each pixel read and add them to the selection. - // Note: black is background, those indices are skipped. - for (qint32 i = 0; i < numpixels; ++i) - { - qint32 idx = - (*(pixelptr + 0) * 0x10000) + - (*(pixelptr + 1) * 0x100) + - *(pixelptr + 2); - pixelptr += 4; - - if (idx != 0) - indices << idx; - } - - RemoveDuplicates (indices); - - for (qint32 idx : indices) - { - LDObjectPtr 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 (additive) - { - if (obj->isSelected()) - { - obj->deselect(); - removedObj = obj; - break; - } - } - - obj->select(); - } - - delete[] pixeldata; - - // Update everything now. - g_win->updateSelection(); - - // Recompile the objects now to update their color - for (LDObjectPtr obj : Selection()) - compileObject (obj); - - if (removedObj) - compileObject (removedObj); - - setPicking (false); - repaint(); -} - -// -// Simpler version of GLRenderer::pick which simply picks whatever object on the screen -// -LDObjectPtr GLRenderer::pickOneObject (int mouseX, int mouseY) -{ - uchar pixel[4]; - doMakeCurrent(); - setPicking (true); - drawGLScene(); - glReadPixels (mouseX, m_height - mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - LDObjectPtr obj = LDObject::fromID ((pixel[0] * 0x10000) + (pixel[1] * 0x100) + pixel[2]); - setPicking (false); - repaint(); - return obj; -} - -// ============================================================================= -// -void GLRenderer::setEditMode (EditModeType a) -{ - if (m_editmode != null and m_editmode->type() == a) - return; - - delete m_editmode; - m_editmode = AbstractEditMode::createByType (this, a); - - // If we cannot use the free camera, use the top one instead. - if (camera() == EFreeCamera and not m_editmode->allowFreeCamera()) - setCamera (ETopCamera); - - g_win->updateEditModeActions(); - update(); -} - -// ============================================================================= -// -EditModeType GLRenderer::currentEditModeType() const -{ - return m_editmode->type(); -} - -// ============================================================================= -// -void GLRenderer::setDocument (LDDocumentPtr const& a) -{ - m_document = a; - - if (a != null) - { - initOverlaysFromObjects(); - - if (not currentDocumentData().init) - { - resetAllAngles(); - currentDocumentData().init = true; - } - - currentDocumentData().needZoomToFit = true; - } -} - -// ============================================================================= -// -void GLRenderer::setPicking (const bool& a) -{ - m_isPicking = a; - setBackground(); - - if (isPicking()) - { - glDisable (GL_DITHER); - - // Use particularly thick lines while picking ease up selecting lines. - glLineWidth (Max<double> (cfg::LineThickness, 6.5)); - } - else - { - glEnable (GL_DITHER); - - // Restore line thickness - glLineWidth (cfg::LineThickness); - } -} - -// ============================================================================= -// -void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const -{ - const LDFixedCamera* cam = &g_FixedCameras[camera()]; - relX = cam->axisX; - relY = cam->axisY; -} - -// ============================================================================= -// -Axis GLRenderer::getRelativeZ() const -{ - const LDFixedCamera* cam = &g_FixedCameras[camera()]; - return (Axis) (3 - cam->axisX - cam->axisY); -} - -// ============================================================================= -// -static QList<Vertex> GetVerticesOf (LDObjectPtr obj) -{ - QList<Vertex> verts; - - if (obj->numVertices() >= 2) - { - for (int i = 0; i < obj->numVertices(); ++i) - verts << obj->vertex (i); - } - elif (obj->type() == OBJ_Subfile) - { - LDSubfilePtr ref = obj.staticCast<LDSubfile>(); - LDObjectList objs = ref->inlineContents (true, false); - - for (LDObjectPtr obj : objs) - { - verts << GetVerticesOf (obj); - obj->destroy(); - } - } - - return verts; -} - -// ============================================================================= -// -void GLRenderer::compileObject (LDObjectPtr obj) -{ - compiler()->stageForCompilation (obj); -} - -// ============================================================================= -// -void GLRenderer::forgetObject (LDObjectPtr obj) -{ - if (compiler() != null) - compiler()->dropObject (obj); -} - -// ============================================================================= -// -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_mousePosition)) - { - m_toolTipCamera = icon.cam; - m_drawToolTip = true; - update(); - break; - } - } -} - -// ============================================================================= -// -Axis GLRenderer::getCameraAxis (bool y, ECamera camid) -{ - if (camid == (ECamera) -1) - camid = camera(); - - const LDFixedCamera* cam = &g_FixedCameras[camid]; - return (y) ? cam->axisY : cam->axisX; -} - -// ============================================================================= -// -bool GLRenderer::setupOverlay (ECamera 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!")); - currentDocumentData().overlays[cam].invalid = true; - 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; - info.invalid = false; - - 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 = Origin; - info.v0.setCoordinate (x2d, -(info.ox * info.lw * negXFac) / img->width()); - info.v0.setCoordinate (y2d, (info.oy * info.lh * negYFac) / img->height()); - info.v1.setCoordinate (x2d, info.v0[x2d] + info.lw); - info.v1.setCoordinate (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) -{ - zoom() *= inward ? 0.833f : 1.2f; -} - -// ============================================================================= -// -void GLRenderer::zoomToFit() -{ - zoom() = 30.0f; - - if (document() == null or m_width == -1 or m_height == -1) - return; - - bool lastfilled = false; - bool firstrun = true; - enum { black = 0xFF000000 }; - bool inward = true; - int runaway = 50; - - // 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 or zoom() < 0.0) - { - // Nothing to draw if we get here. - zoom() = 30.0; - break; - } - - zoomNotch (inward); - QVector<unsigned char> capture (4 * m_width * m_height); - drawGLScene(); - glReadPixels (0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, capture.data()); - QImage image (capture.constData(), m_width, m_height, QImage::Format_ARGB32); - bool filled = false; - - // Check the top and bottom rows - for (int i = 0; i < image.width(); ++i) - { - if (image.pixel (i, 0) != black or image.pixel (i, m_height - 1) != black) - { - filled = true; - break; - } - } - - // Left and right edges - if (filled == false) - { - for (int i = 0; i < image.height(); ++i) - { - if (image.pixel (0, i) != black or image.pixel (m_width - 1, i) != black) - { - filled = true; - break; - } - } - } - - 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 = not 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 and not 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 (not filled and lastfilled) - break; - - inward = not filled; - } - - lastfilled = filled; - } - - setPicking (false); -} - -// ============================================================================= -// -void GLRenderer::zoomAllToFit() -{ - zoomToFit(); -} - -// ============================================================================= -// -void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev) -{ - if (m_editmode->mouseDoubleClicked (ev)) - ev->accept(); -} - -// ============================================================================= -// -LDOverlayPtr GLRenderer::findOverlayObject (ECamera cam) -{ - for (LDObjectPtr obj : document()->objects()) - { - LDOverlayPtr ovlobj = obj.dynamicCast<LDOverlay>(); - - if (ovlobj != null and obj.staticCast<LDOverlay>()->camera() == cam) - return ovlobj; - } - - return LDOverlayPtr(); -} - -// ============================================================================= -// -// Read in overlays from the current file and update overlay info accordingly. -// -void GLRenderer::initOverlaysFromObjects() -{ - for (ECamera cam = EFirstCamera; cam < ENumCameras; ++cam) - { - if (cam == EFreeCamera) - continue; - - LDGLOverlay& meta = currentDocumentData().overlays[cam]; - LDOverlayPtr ovlobj = findOverlayObject (cam); - - if (ovlobj == null and meta.img != null) - { - delete meta.img; - meta.img = null; - } - elif (ovlobj != null and - (meta.img == null or meta.fname != ovlobj->fileName()) and - not meta.invalid) - { - setupOverlay (cam, ovlobj->fileName(), ovlobj->x(), - ovlobj->y(), ovlobj->width(), ovlobj->height()); - } - } -} - -// ============================================================================= -// -void GLRenderer::updateOverlayObjects() -{ - for (ECamera cam = EFirstCamera; cam < ENumCameras; ++cam) - { - if (cam == EFreeCamera) - continue; - - LDGLOverlay& meta = currentDocumentData().overlays[cam]; - LDOverlayPtr ovlobj = findOverlayObject (cam); - - if (meta.img == null and ovlobj != null) - { - // If this is the last overlay image, we need to remove the empty space after it as well. - LDObjectPtr nextobj = ovlobj->next(); - - if (nextobj and nextobj->type() == OBJ_Empty) - nextobj->destroy(); - - // If the overlay object was there and the overlay itself is - // not, remove the object. - ovlobj->destroy(); - } - elif (meta.img != null and ovlobj == null) - { - // Inverse case: image is there but the overlay object is - // not, thus create the object. - ovlobj = LDSpawn<LDOverlay>(); - - // Find a suitable position to place this object. We want to place - // this into the header, which is everything up to the first scemantic - // object. If we find another overlay object, place this object after - // the last one found. Otherwise, place it before the first schemantic - // object and put an empty object after it (though don't do this if - // there was no schemantic elements at all) - int i, lastOverlay = -1; - bool found = false; - - for (i = 0; i < document()->getObjectCount(); ++i) - { - LDObjectPtr obj = document()->getObject (i); - - if (obj->isScemantic()) - { - found = true; - break; - } - - if (obj->type() == OBJ_Overlay) - lastOverlay = i; - } - - if (lastOverlay != -1) - document()->insertObj (lastOverlay + 1, ovlobj); - else - { - document()->insertObj (i, ovlobj); - - if (found) - document()->insertObj (i + 1, LDSpawn<LDEmpty>()); - } - } - - if (meta.img != null and ovlobj != null) - { - 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(); -} - -// ============================================================================= -// -void GLRenderer::highlightCursorObject() -{ - if (not cfg::HighlightObjectBelowCursor and objectAtCursor() == null) - return; - - LDObjectWeakPtr newObject; - LDObjectWeakPtr oldObject = objectAtCursor(); - qint32 newIndex; - - if (isCameraMoving() or not cfg::HighlightObjectBelowCursor) - { - newIndex = 0; - } - else - { - setPicking (true); - drawGLScene(); - setPicking (false); - - unsigned char pixel[4]; - glReadPixels (m_mousePosition.x(), m_height - m_mousePosition.y(), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel[0]); - newIndex = pixel[0] * 0x10000 | pixel[1] * 0x100 | pixel[2]; - } - - if (newIndex != (oldObject != null ? oldObject.toStrongRef()->id() : 0)) - { - if (newIndex != 0) - newObject = LDObject::fromID (newIndex); - - setObjectAtCursor (newObject); - - if (oldObject != null) - compileObject (oldObject); - - if (newObject != null) - compileObject (newObject); - } - - update(); -} - -void GLRenderer::dragEnterEvent (QDragEnterEvent* ev) -{ - if (g_win != null and ev->source() == g_win->getPrimitivesTree() and g_win->getPrimitivesTree()->currentItem() != null) - ev->acceptProposedAction(); -} - -void GLRenderer::dropEvent (QDropEvent* ev) -{ - if (g_win != null and ev->source() == g_win->getPrimitivesTree()) - { - QString primName = static_cast<SubfileListItem*> (g_win->getPrimitivesTree()->currentItem())->primitive()->name; - LDSubfilePtr ref = LDSpawn<LDSubfile>(); - ref->setColor (MainColor()); - ref->setFileInfo (GetDocument (primName)); - ref->setPosition (Origin); - ref->setTransform (IdentityMatrix); - LDDocument::current()->insertObj (g_win->getInsertionPoint(), ref); - ref->select(); - g_win->buildObjList(); - g_win->R()->refresh(); - ev->acceptProposedAction(); - } -} - -Vertex const& GLRenderer::position3D() const -{ - return m_position3D; -} - -LDFixedCamera const& GLRenderer::getFixedCamera (ECamera cam) const -{ - return g_FixedCameras[cam]; -} - -bool GLRenderer::mouseHasMoved() const -{ - return m_totalmove >= 10; -} - -QPoint const& GLRenderer::mousePosition() const -{ - return m_mousePosition; -} - -QPointF const& GLRenderer::mousePositionF() const -{ - return m_mousePositionF; -} - -void GLRenderer::doMakeCurrent() -{ - makeCurrent(); - initializeOpenGLFunctions(); -} - -int GLRenderer::depthNegateFactor() const -{ - return g_FixedCameras[camera()].negatedDepth ? -1 : 1; -} - -Qt::KeyboardModifiers GLRenderer::keyboardModifiers() const -{ - return m_keymods; -} - -LDFixedCamera const& GetFixedCamera (ECamera cam) -{ - assert (cam != EFreeCamera); - return g_FixedCameras[cam]; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/glRenderer.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,1642 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define GL_GLEXT_PROTOTYPES +#include <GL/glu.h> +#include <GL/glext.h> +#include <QGLWidget> +#include <QWheelEvent> +#include <QMouseEvent> +#include <QContextMenuEvent> +#include <QInputDialog> +#include <QToolTip> +#include <QTextDocument> +#include <QTimer> +#include <GL/glu.h> +#include "main.h" +#include "configuration.h" +#include "ldDocument.h" +#include "glRenderer.h" +#include "colors.h" +#include "mainWindow.h" +#include "miscallenous.h" +#include "editHistory.h" +#include "dialogs.h" +#include "addObjectDialog.h" +#include "messageLog.h" +#include "glCompiler.h" +#include "primitives.h" + +const LDFixedCamera g_FixedCameras[6] = +{ + {{ 1, 0, 0 }, X, Z, false, false, false }, // top + {{ 0, 0, 0 }, X, Y, false, true, false }, // front + {{ 0, 1, 0 }, Z, Y, true, true, false }, // left + {{ -1, 0, 0 }, X, Z, false, true, true }, // bottom + {{ 0, 0, 0 }, X, Y, true, true, true }, // back + {{ 0, -1, 0 }, Z, Y, false, true, true }, // right +}; + +CFGENTRY (String, BackgroundColor, "#FFFFFF") +CFGENTRY (String, MainColor, "#A0A0A0") +CFGENTRY (Float, MainColorAlpha, 1.0) +CFGENTRY (Int, LineThickness, 2) +CFGENTRY (Bool, BFCRedGreenView, false) +CFGENTRY (Int, Camera, EFreeCamera) +CFGENTRY (Bool, BlackEdges, false) +CFGENTRY (Bool, DrawAxes, false) +CFGENTRY (Bool, DrawWireframe, false) +CFGENTRY (Bool, UseLogoStuds, false) +CFGENTRY (Bool, AntiAliasedLines, true) +CFGENTRY (Bool, RandomColors, false) +CFGENTRY (Bool, HighlightObjectBelowCursor, true) +CFGENTRY (Bool, DrawSurfaces, true) +CFGENTRY (Bool, DrawEdgeLines, true) +CFGENTRY (Bool, DrawConditionalLines, 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") +}; + +struct LDGLAxis +{ + const QColor col; + const Vertex vert; +}; + +// Definitions for visual axes, drawn on the screen +static const LDGLAxis g_GLAxes[3] = +{ + { QColor (192, 96, 96), Vertex (10000, 0, 0) }, // X + { QColor (48, 192, 48), Vertex (0, 10000, 0) }, // Y + { QColor (48, 112, 192), Vertex (0, 0, 10000) }, // Z +}; + +static bool RendererInitialized (false); + +// ============================================================================= +// +GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) +{ + m_isPicking = false; + m_camera = (ECamera) cfg::Camera; + m_drawToolTip = false; + m_editmode = AbstractEditMode::createByType (this, EditModeType::Select); + m_panning = false; + m_compiler = new GLCompiler (this); + setDrawOnly (false); + setMessageLog (null); + m_width = m_height = -1; + m_position3D = Origin; + m_toolTipTimer = new QTimer (this); + m_toolTipTimer->setSingleShot (true); + m_isCameraMoving = false; + m_thinBorderPen = QPen (QColor (0, 0, 0, 208), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); + m_thinBorderPen.setWidth (1); + setAcceptDrops (true); + connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer())); + + // Init camera icons + for (ECamera cam = EFirstCamera; cam < ENumCameras; ++cam) + { + QString iconname = format ("camera-%1", tr (g_CameraNames[cam]).toLower()); + CameraIcon* info = &m_cameraIcons[cam]; + info->img = new QPixmap (GetIcon (iconname)); + info->cam = cam; + } + + calcCameraIcons(); +} + +// ============================================================================= +// +GLRenderer::~GLRenderer() +{ + for (int i = 0; i < 6; ++i) + delete currentDocumentData().overlays[i].img; + + for (CameraIcon& info : m_cameraIcons) + delete info.img; + + if (messageLog()) + messageLog()->setRenderer (null); + + m_compiler->setRenderer (null); + delete m_compiler; + delete m_editmode; + + glDeleteBuffers (1, &m_axesVBO); + glDeleteBuffers (1, &m_axesColorVBO); +} + +// ============================================================================= +// 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 (cfg::AntiAliasedLines) + { + 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::needZoomToFit() +{ + if (document() != null) + currentDocumentData().needZoomToFit = true; +} + +// ============================================================================= +// +void GLRenderer::resetAngles() +{ + rot (X) = 30.0f; + rot (Y) = 325.f; + pan (X) = pan (Y) = rot (Z) = 0.0f; + needZoomToFit(); +} + +// ============================================================================= +// +void GLRenderer::resetAllAngles() +{ + ECamera oldcam = camera(); + + for (int i = 0; i < 7; ++i) + { + setCamera ((ECamera) i); + resetAngles(); + } + + setCamera (oldcam); +} + +// ============================================================================= +// +void GLRenderer::initializeGL() +{ +#ifdef USE_QT5 + initializeOpenGLFunctions(); +#endif + setBackground(); + glLineWidth (cfg::LineThickness); + glLineStipple (1, 0x6666); + setAutoFillBackground (false); + setMouseTracking (true); + setFocusPolicy (Qt::WheelFocus); + compiler()->initialize(); + initializeAxes(); + RendererInitialized = true; +} + +// ============================================================================= +// +void GLRenderer::initializeAxes() +{ + float axesdata[18]; + float colordata[18]; + memset (axesdata, 0, sizeof axesdata); + + for (int i = 0; i < 3; ++i) + { + for_axes (ax) + { + axesdata[(i * 6) + ax] = g_GLAxes[i].vert[ax]; + axesdata[(i * 6) + 3 + ax] = -g_GLAxes[i].vert[ax]; + } + + for (int j = 0; j < 2; ++j) + { + colordata[(i * 6) + (j * 3) + 0] = g_GLAxes[i].col.red(); + colordata[(i * 6) + (j * 3) + 1] = g_GLAxes[i].col.green(); + colordata[(i * 6) + (j * 3) + 2] = g_GLAxes[i].col.blue(); + } + } + + glGenBuffers (1, &m_axesVBO); + glBindBuffer (GL_ARRAY_BUFFER, m_axesVBO); + glBufferData (GL_ARRAY_BUFFER, sizeof axesdata, axesdata, GL_STATIC_DRAW); + glGenBuffers (1, &m_axesColorVBO); + glBindBuffer (GL_ARRAY_BUFFER, m_axesColorVBO); + glBufferData (GL_ARRAY_BUFFER, sizeof colordata, colordata, GL_STATIC_DRAW); + glBindBuffer (GL_ARRAY_BUFFER, 0); +} + +// ============================================================================= +// +QColor GLRenderer::getMainColor() +{ + QColor col (cfg::MainColor); + + if (not col.isValid()) + return QColor (0, 0, 0); + + col.setAlpha (cfg::MainColorAlpha * 255.f); + return col; +} + +// ============================================================================= +// +void GLRenderer::setBackground() +{ + if (isPicking()) + { + glClearColor (0.0f, 0.0f, 0.0f, 1.0f); + return; + } + + QColor col (cfg::BackgroundColor); + + if (not col.isValid()) + return; + + col.setAlpha (255); + + m_darkbg = Luma (col) < 80; + m_bgcolor = col; + qglClearColor (col); +} + +// ============================================================================= +// +void GLRenderer::refresh() +{ + update(); + + if (isVisible()) + swapBuffers(); +} + +// ============================================================================= +// +void GLRenderer::hardRefresh() +{ + if (not RendererInitialized) + return; + + compiler()->compileDocument (CurrentDocument()); + refresh(); +} + +// ============================================================================= +// +void GLRenderer::resizeGL (int w, int h) +{ + m_width = w; + m_height = h; + + calcCameraIcons(); + + glViewport (0, 0, w, h); + glMatrixMode (GL_PROJECTION); + glLoadIdentity(); + gluPerspective (45.0f, (double) w / (double) h, 1.0f, 10000.0f); + glMatrixMode (GL_MODELVIEW); +} + +// ============================================================================= +// +void GLRenderer::drawGLScene() +{ + if (document() == null) + return; + + if (currentDocumentData().needZoomToFit) + { + currentDocumentData().needZoomToFit = false; + zoomAllToFit(); + } + + if (cfg::DrawWireframe and not isPicking()) + glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + + glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable (GL_DEPTH_TEST); + + if (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 (camera() != EFrontCamera and 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 (camera() == 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); + } + + glEnableClientState (GL_VERTEX_ARRAY); + glEnableClientState (GL_COLOR_ARRAY); + + if (isPicking()) + { + drawVBOs (VBOSF_Triangles, VBOCM_PickColors, GL_TRIANGLES); + drawVBOs (VBOSF_Quads, VBOCM_PickColors, GL_QUADS); + drawVBOs (VBOSF_Lines, VBOCM_PickColors, GL_LINES); + drawVBOs (VBOSF_CondLines, VBOCM_PickColors, GL_LINES); + } + else + { + if (cfg::BFCRedGreenView) + { + glEnable (GL_CULL_FACE); + glCullFace (GL_BACK); + drawVBOs (VBOSF_Triangles, VBOCM_BFCFrontColors, GL_TRIANGLES); + drawVBOs (VBOSF_Quads, VBOCM_BFCFrontColors, GL_QUADS); + glCullFace (GL_FRONT); + drawVBOs (VBOSF_Triangles, VBOCM_BFCBackColors, GL_TRIANGLES); + drawVBOs (VBOSF_Quads, VBOCM_BFCBackColors, GL_QUADS); + glDisable (GL_CULL_FACE); + } + else + { + if (cfg::RandomColors) + { + drawVBOs (VBOSF_Triangles, VBOCM_RandomColors, GL_TRIANGLES); + drawVBOs (VBOSF_Quads, VBOCM_RandomColors, GL_QUADS); + } + else + { + drawVBOs (VBOSF_Triangles, VBOCM_NormalColors, GL_TRIANGLES); + drawVBOs (VBOSF_Quads, VBOCM_NormalColors, GL_QUADS); + } + } + + drawVBOs (VBOSF_Lines, VBOCM_NormalColors, GL_LINES); + glEnable (GL_LINE_STIPPLE); + drawVBOs (VBOSF_CondLines, VBOCM_NormalColors, GL_LINES); + glDisable (GL_LINE_STIPPLE); + + if (cfg::DrawAxes) + { + glBindBuffer (GL_ARRAY_BUFFER, m_axesVBO); + glVertexPointer (3, GL_FLOAT, 0, NULL); + glBindBuffer (GL_ARRAY_BUFFER, m_axesVBO); + glColorPointer (3, GL_FLOAT, 0, NULL); + glDrawArrays (GL_LINES, 0, 6); + CHECK_GL_ERROR(); + } + } + + glPopMatrix(); + glBindBuffer (GL_ARRAY_BUFFER, 0); + glDisableClientState (GL_VERTEX_ARRAY); + glDisableClientState (GL_COLOR_ARRAY); + CHECK_GL_ERROR(); + glDisable (GL_CULL_FACE); + glMatrixMode (GL_MODELVIEW); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); +} + +// ============================================================================= +// +void GLRenderer::drawVBOs (EVBOSurface surface, EVBOComplement colors, GLenum type) +{ + // Filter this through some configuration options + if ((Eq (surface, VBOSF_Quads, VBOSF_Triangles) and cfg::DrawSurfaces == false) or + (surface == VBOSF_Lines and cfg::DrawEdgeLines == false) or + (surface == VBOSF_CondLines and cfg::DrawConditionalLines == false)) + { + return; + } + + int surfacenum = m_compiler->vboNumber (surface, VBOCM_Surfaces); + int colornum = m_compiler->vboNumber (surface, colors); + m_compiler->prepareVBO (surfacenum); + m_compiler->prepareVBO (colornum); + GLuint surfacevbo = m_compiler->vbo (surfacenum); + GLuint colorvbo = m_compiler->vbo (colornum); + GLsizei count = m_compiler->vboSize (surfacenum) / 3; + + if (count > 0) + { + glBindBuffer (GL_ARRAY_BUFFER, surfacevbo); + glVertexPointer (3, GL_FLOAT, 0, null); + CHECK_GL_ERROR(); + glBindBuffer (GL_ARRAY_BUFFER, colorvbo); + glColorPointer (4, GL_FLOAT, 0, null); + CHECK_GL_ERROR(); + glDrawArrays (type, 0, count); + CHECK_GL_ERROR(); + } +} + +// ============================================================================= +// 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 LDFixedCamera* cam = &g_FixedCameras[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::Coordinate); + cy = Grid::Snap (cy, Grid::Coordinate); + } + + cx *= negXFac; + cy *= negYFac; + + RoundToDecimals (cx, 4); + RoundToDecimals (cy, 4); + + // Create the vertex from the coordinates + pos3d.setCoordinate (axisX, cx); + pos3d.setCoordinate (axisY, cy); + pos3d.setCoordinate ((Axis) (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) +{ + GLfloat m[16]; + const LDFixedCamera* cam = &g_FixedCameras[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.setX ((m[0] * x) + (m[1] * y) + (m[2] * z) + m[3]); + transformed.setY ((m[4] * x) + (m[5] * y) + (m[6] * z) + m[7]); + transformed.setZ ((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); +} + +QPen GLRenderer::textPen() const +{ + return QPen (m_darkbg ? Qt::white : Qt::black); +} + +QPen GLRenderer::linePen() const +{ + QPen linepen (m_thinBorderPen); + linepen.setWidth (2); + linepen.setColor (Luma (m_bgcolor) < 40 ? Qt::white : Qt::black); + return linepen; +} + +// ============================================================================= +// +void GLRenderer::paintEvent (QPaintEvent*) +{ + doMakeCurrent(); + m_virtWidth = zoom(); + m_virtHeight = (m_height * m_virtWidth) / m_width; + initGLData(); + drawGLScene(); + + QPainter paint (this); + QFontMetrics metrics = QFontMetrics (QFont()); + paint.setRenderHint (QPainter::HighQualityAntialiasing); + + // If we wish to only draw the brick, stop here + if (isDrawOnly()) + return; + +#ifndef RELEASE + if (not isPicking()) + { + QString text = format ("Rotation: (%1, %2, %3)\nPanning: (%4, %5), Zoom: %6", + rot(X), rot(Y), rot(Z), pan(X), pan(Y), zoom()); + QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); + paint.setPen (textPen()); + paint.drawText ((width() - textSize.width()) / 2, height() - textSize.height(), textSize.width(), + textSize.height(), Qt::AlignCenter, text); + } +#endif + + if (camera() != EFreeCamera and not isPicking()) + { + // Paint the overlay image if we have one + const LDGLOverlay& overlay = currentDocumentData().overlays[camera()]; + + if (overlay.img != null) + { + QPoint v0 = coordconv3_2 (currentDocumentData().overlays[camera()].v0), + v1 = coordconv3_2 (currentDocumentData().overlays[camera()].v1); + + QRect targRect (v0.x(), v0.y(), Abs (v1.x() - v0.x()), Abs (v1.y() - v0.y())), + srcRect (0, 0, overlay.img->width(), overlay.img->height()); + paint.drawImage (targRect, *overlay.img, srcRect); + } + + // Paint the coordinates onto the screen. + QString text = format (tr ("X: %1, Y: %2, Z: %3"), m_position3D[X], m_position3D[Y], m_position3D[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); + } + + if (not isPicking()) + { + // Draw edit mode HUD + m_editmode->render (paint); + + // 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 camera icons + for (CameraIcon& info : m_cameraIcons) + { + // Don't draw the free camera icon when in draw mode + if (&info == &m_cameraIcons[EFreeCamera] and not m_editmode->allowFreeCamera()) + continue; + + paint.drawPixmap (info.destRect, *info.img, info.srcRect); + } + + QString formatstr = tr ("%1 Camera"); + + // Draw a label for the current camera in the bottom left corner + { + const int margin = 4; + + QString label; + label = format (formatstr, tr (g_CameraNames[camera()])); + paint.setPen (textPen()); + paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label); + } + + // Tool tips + if (m_drawToolTip) + { + if (not m_cameraIcons[m_toolTipCamera].destRect.contains (m_mousePosition)) + m_drawToolTip = false; + else + { + QString label = format (formatstr, tr (g_CameraNames[m_toolTipCamera])); + QToolTip::showText (m_globalpos, label); + } + } + } + + // Message log + if (messageLog()) + { + int y = 0; + const int margin = 2; + QColor penColor = textPen().color(); + + for (const MessageManager::Line& line : messageLog()->getLines()) + { + penColor.setAlphaF (line.alpha); + paint.setPen (penColor); + paint.drawText (QPoint (margin, y + margin + metrics.ascent()), line.text); + y += metrics.height(); + } + } +} + +// ============================================================================= +// +void GLRenderer::drawBlip (QPainter& paint, QPointF 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::clampAngle (double& angle) const +{ + while (angle < 0) + angle += 360.0; + + while (angle > 360.0) + angle -= 360.0; +} + +// ============================================================================= +// +void GLRenderer::mouseReleaseEvent (QMouseEvent* ev) +{ + const bool wasLeft = (m_lastButtons & Qt::LeftButton) and not (ev->buttons() & Qt::LeftButton); + + Qt::MouseButtons releasedbuttons = m_lastButtons & ~ev->buttons(); + + if (m_panning) + m_panning = false; + + if (wasLeft) + { + // Check if we selected a camera icon + if (not mouseHasMoved()) + { + for (CameraIcon & info : m_cameraIcons) + { + if (info.destRect.contains (ev->pos())) + { + setCamera (info.cam); + goto end; + } + } + } + } + + if (not isDrawOnly()) + { + AbstractEditMode::MouseEventData data; + data.ev = ev; + data.mouseMoved = mouseHasMoved(); + data.keymods = m_keymods; + data.releasedButtons = releasedbuttons; + + if (m_editmode->mouseReleased (data)) + goto end; + } + +end: + update(); + m_totalmove = 0; +} + +// ============================================================================= +// +void GLRenderer::mousePressEvent (QMouseEvent* ev) +{ + m_totalmove = 0; + m_lastButtons = ev->buttons(); + + if (m_editmode->mousePressed (ev)) + ev->accept(); +} + +// ============================================================================= +// +void GLRenderer::mouseMoveEvent (QMouseEvent* ev) +{ + int dx = ev->x() - m_mousePosition.x(); + int dy = ev->y() - m_mousePosition.y(); + m_totalmove += Abs (dx) + Abs (dy); + setCameraMoving (false); + + if (not m_editmode->mouseMoved (ev)) + { + const bool left = ev->buttons() & Qt::LeftButton, + mid = ev->buttons() & Qt::MidButton, + shift = ev->modifiers() & Qt::ShiftModifier; + + if (mid or (left and shift)) + { + pan (X) += 0.03f * dx * (zoom() / 7.5f); + pan (Y) -= 0.03f * dy * (zoom() / 7.5f); + m_panning = true; + setCameraMoving (true); + } + elif (left and camera() == EFreeCamera) + { + rot (X) = rot (X) + dy; + rot (Y) = rot (Y) + dx; + + clampAngle (rot (X)); + clampAngle (rot (Y)); + setCameraMoving (true); + } + } + + // Start the tool tip timer + if (not m_drawToolTip) + m_toolTipTimer->start (500); + + // Update 2d position + m_mousePosition = ev->pos(); + m_globalpos = ev->globalPos(); + +#ifndef USE_QT5 + m_mousePositionF = ev->posF(); +#else + m_mousePositionF = ev->localPos(); +#endif + + // Calculate 3d position of the cursor + m_position3D = (camera() != EFreeCamera) ? coordconv2_3 (m_mousePosition, true) : Origin; + + highlightCursorObject(); + update(); + ev->accept(); +} + +// ============================================================================= +// +void GLRenderer::keyPressEvent (QKeyEvent* ev) +{ + m_keymods = ev->modifiers(); +} + +// ============================================================================= +// +void GLRenderer::keyReleaseEvent (QKeyEvent* ev) +{ + m_keymods = ev->modifiers(); + m_editmode->keyReleased (ev); + update(); +} + +// ============================================================================= +// +void GLRenderer::wheelEvent (QWheelEvent* ev) +{ + doMakeCurrent(); + + zoomNotch (ev->delta() > 0); + zoom() = Clamp (zoom(), 0.01, 10000.0); + setCameraMoving (true); + 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 ECamera cam) +{ + // The edit mode may forbid the free camera. + if (cam == EFreeCamera and not m_editmode->allowFreeCamera()) + return; + + m_camera = cam; + cfg::Camera = (int) cam; + g_win->updateEditModeActions(); +} + +// ============================================================================= +// +void GLRenderer::pick (int mouseX, int mouseY, bool additive) +{ + pick (QRect (mouseX, mouseY, mouseX + 1, mouseY + 1), additive); +} + +// ============================================================================= +// +void GLRenderer::pick (QRect const& range, bool additive) +{ + doMakeCurrent(); + + // Clear the selection if we do not wish to add to it. + if (not additive) + { + LDObjectList oldsel = Selection(); + CurrentDocument()->clearSelection(); + + for (LDObjectPtr obj : oldsel) + compileObject (obj); + } + + // Paint the picking scene + setPicking (true); + drawGLScene(); + + int x0 = range.left(); + int y0 = range.top(); + int x1 = x0 + range.width(); + int y1 = y0 + range.height(); + + // 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); + + LDObjectPtr removedObj; + QList<qint32> indices; + + // Go through each pixel read and add them to the selection. + // Note: black is background, those indices are skipped. + for (qint32 i = 0; i < numpixels; ++i) + { + qint32 idx = + (*(pixelptr + 0) * 0x10000) + + (*(pixelptr + 1) * 0x100) + + *(pixelptr + 2); + pixelptr += 4; + + if (idx != 0) + indices << idx; + } + + RemoveDuplicates (indices); + + for (qint32 idx : indices) + { + LDObjectPtr 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 (additive) + { + if (obj->isSelected()) + { + obj->deselect(); + removedObj = obj; + break; + } + } + + obj->select(); + } + + delete[] pixeldata; + + // Update everything now. + g_win->updateSelection(); + + // Recompile the objects now to update their color + for (LDObjectPtr obj : Selection()) + compileObject (obj); + + if (removedObj) + compileObject (removedObj); + + setPicking (false); + repaint(); +} + +// +// Simpler version of GLRenderer::pick which simply picks whatever object on the screen +// +LDObjectPtr GLRenderer::pickOneObject (int mouseX, int mouseY) +{ + uchar pixel[4]; + doMakeCurrent(); + setPicking (true); + drawGLScene(); + glReadPixels (mouseX, m_height - mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + LDObjectPtr obj = LDObject::fromID ((pixel[0] * 0x10000) + (pixel[1] * 0x100) + pixel[2]); + setPicking (false); + repaint(); + return obj; +} + +// ============================================================================= +// +void GLRenderer::setEditMode (EditModeType a) +{ + if (m_editmode != null and m_editmode->type() == a) + return; + + delete m_editmode; + m_editmode = AbstractEditMode::createByType (this, a); + + // If we cannot use the free camera, use the top one instead. + if (camera() == EFreeCamera and not m_editmode->allowFreeCamera()) + setCamera (ETopCamera); + + g_win->updateEditModeActions(); + update(); +} + +// ============================================================================= +// +EditModeType GLRenderer::currentEditModeType() const +{ + return m_editmode->type(); +} + +// ============================================================================= +// +void GLRenderer::setDocument (LDDocumentPtr const& a) +{ + m_document = a; + + if (a != null) + { + initOverlaysFromObjects(); + + if (not currentDocumentData().init) + { + resetAllAngles(); + currentDocumentData().init = true; + } + + currentDocumentData().needZoomToFit = true; + } +} + +// ============================================================================= +// +void GLRenderer::setPicking (const bool& a) +{ + m_isPicking = a; + setBackground(); + + if (isPicking()) + { + glDisable (GL_DITHER); + + // Use particularly thick lines while picking ease up selecting lines. + glLineWidth (Max<double> (cfg::LineThickness, 6.5)); + } + else + { + glEnable (GL_DITHER); + + // Restore line thickness + glLineWidth (cfg::LineThickness); + } +} + +// ============================================================================= +// +void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const +{ + const LDFixedCamera* cam = &g_FixedCameras[camera()]; + relX = cam->axisX; + relY = cam->axisY; +} + +// ============================================================================= +// +Axis GLRenderer::getRelativeZ() const +{ + const LDFixedCamera* cam = &g_FixedCameras[camera()]; + return (Axis) (3 - cam->axisX - cam->axisY); +} + +// ============================================================================= +// +static QList<Vertex> GetVerticesOf (LDObjectPtr obj) +{ + QList<Vertex> verts; + + if (obj->numVertices() >= 2) + { + for (int i = 0; i < obj->numVertices(); ++i) + verts << obj->vertex (i); + } + elif (obj->type() == OBJ_Subfile) + { + LDSubfilePtr ref = obj.staticCast<LDSubfile>(); + LDObjectList objs = ref->inlineContents (true, false); + + for (LDObjectPtr obj : objs) + { + verts << GetVerticesOf (obj); + obj->destroy(); + } + } + + return verts; +} + +// ============================================================================= +// +void GLRenderer::compileObject (LDObjectPtr obj) +{ + compiler()->stageForCompilation (obj); +} + +// ============================================================================= +// +void GLRenderer::forgetObject (LDObjectPtr obj) +{ + if (compiler() != null) + compiler()->dropObject (obj); +} + +// ============================================================================= +// +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_mousePosition)) + { + m_toolTipCamera = icon.cam; + m_drawToolTip = true; + update(); + break; + } + } +} + +// ============================================================================= +// +Axis GLRenderer::getCameraAxis (bool y, ECamera camid) +{ + if (camid == (ECamera) -1) + camid = camera(); + + const LDFixedCamera* cam = &g_FixedCameras[camid]; + return (y) ? cam->axisY : cam->axisX; +} + +// ============================================================================= +// +bool GLRenderer::setupOverlay (ECamera 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!")); + currentDocumentData().overlays[cam].invalid = true; + 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; + info.invalid = false; + + 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 = Origin; + info.v0.setCoordinate (x2d, -(info.ox * info.lw * negXFac) / img->width()); + info.v0.setCoordinate (y2d, (info.oy * info.lh * negYFac) / img->height()); + info.v1.setCoordinate (x2d, info.v0[x2d] + info.lw); + info.v1.setCoordinate (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) +{ + zoom() *= inward ? 0.833f : 1.2f; +} + +// ============================================================================= +// +void GLRenderer::zoomToFit() +{ + zoom() = 30.0f; + + if (document() == null or m_width == -1 or m_height == -1) + return; + + bool lastfilled = false; + bool firstrun = true; + enum { black = 0xFF000000 }; + bool inward = true; + int runaway = 50; + + // 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 or zoom() < 0.0) + { + // Nothing to draw if we get here. + zoom() = 30.0; + break; + } + + zoomNotch (inward); + QVector<unsigned char> capture (4 * m_width * m_height); + drawGLScene(); + glReadPixels (0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, capture.data()); + QImage image (capture.constData(), m_width, m_height, QImage::Format_ARGB32); + bool filled = false; + + // Check the top and bottom rows + for (int i = 0; i < image.width(); ++i) + { + if (image.pixel (i, 0) != black or image.pixel (i, m_height - 1) != black) + { + filled = true; + break; + } + } + + // Left and right edges + if (filled == false) + { + for (int i = 0; i < image.height(); ++i) + { + if (image.pixel (0, i) != black or image.pixel (m_width - 1, i) != black) + { + filled = true; + break; + } + } + } + + 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 = not 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 and not 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 (not filled and lastfilled) + break; + + inward = not filled; + } + + lastfilled = filled; + } + + setPicking (false); +} + +// ============================================================================= +// +void GLRenderer::zoomAllToFit() +{ + zoomToFit(); +} + +// ============================================================================= +// +void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev) +{ + if (m_editmode->mouseDoubleClicked (ev)) + ev->accept(); +} + +// ============================================================================= +// +LDOverlayPtr GLRenderer::findOverlayObject (ECamera cam) +{ + for (LDObjectPtr obj : document()->objects()) + { + LDOverlayPtr ovlobj = obj.dynamicCast<LDOverlay>(); + + if (ovlobj != null and obj.staticCast<LDOverlay>()->camera() == cam) + return ovlobj; + } + + return LDOverlayPtr(); +} + +// ============================================================================= +// +// Read in overlays from the current file and update overlay info accordingly. +// +void GLRenderer::initOverlaysFromObjects() +{ + for (ECamera cam = EFirstCamera; cam < ENumCameras; ++cam) + { + if (cam == EFreeCamera) + continue; + + LDGLOverlay& meta = currentDocumentData().overlays[cam]; + LDOverlayPtr ovlobj = findOverlayObject (cam); + + if (ovlobj == null and meta.img != null) + { + delete meta.img; + meta.img = null; + } + elif (ovlobj != null and + (meta.img == null or meta.fname != ovlobj->fileName()) and + not meta.invalid) + { + setupOverlay (cam, ovlobj->fileName(), ovlobj->x(), + ovlobj->y(), ovlobj->width(), ovlobj->height()); + } + } +} + +// ============================================================================= +// +void GLRenderer::updateOverlayObjects() +{ + for (ECamera cam = EFirstCamera; cam < ENumCameras; ++cam) + { + if (cam == EFreeCamera) + continue; + + LDGLOverlay& meta = currentDocumentData().overlays[cam]; + LDOverlayPtr ovlobj = findOverlayObject (cam); + + if (meta.img == null and ovlobj != null) + { + // If this is the last overlay image, we need to remove the empty space after it as well. + LDObjectPtr nextobj = ovlobj->next(); + + if (nextobj and nextobj->type() == OBJ_Empty) + nextobj->destroy(); + + // If the overlay object was there and the overlay itself is + // not, remove the object. + ovlobj->destroy(); + } + elif (meta.img != null and ovlobj == null) + { + // Inverse case: image is there but the overlay object is + // not, thus create the object. + ovlobj = LDSpawn<LDOverlay>(); + + // Find a suitable position to place this object. We want to place + // this into the header, which is everything up to the first scemantic + // object. If we find another overlay object, place this object after + // the last one found. Otherwise, place it before the first schemantic + // object and put an empty object after it (though don't do this if + // there was no schemantic elements at all) + int i, lastOverlay = -1; + bool found = false; + + for (i = 0; i < document()->getObjectCount(); ++i) + { + LDObjectPtr obj = document()->getObject (i); + + if (obj->isScemantic()) + { + found = true; + break; + } + + if (obj->type() == OBJ_Overlay) + lastOverlay = i; + } + + if (lastOverlay != -1) + document()->insertObj (lastOverlay + 1, ovlobj); + else + { + document()->insertObj (i, ovlobj); + + if (found) + document()->insertObj (i + 1, LDSpawn<LDEmpty>()); + } + } + + if (meta.img != null and ovlobj != null) + { + 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(); +} + +// ============================================================================= +// +void GLRenderer::highlightCursorObject() +{ + if (not cfg::HighlightObjectBelowCursor and objectAtCursor() == null) + return; + + LDObjectWeakPtr newObject; + LDObjectWeakPtr oldObject = objectAtCursor(); + qint32 newIndex; + + if (isCameraMoving() or not cfg::HighlightObjectBelowCursor) + { + newIndex = 0; + } + else + { + setPicking (true); + drawGLScene(); + setPicking (false); + + unsigned char pixel[4]; + glReadPixels (m_mousePosition.x(), m_height - m_mousePosition.y(), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel[0]); + newIndex = pixel[0] * 0x10000 | pixel[1] * 0x100 | pixel[2]; + } + + if (newIndex != (oldObject != null ? oldObject.toStrongRef()->id() : 0)) + { + if (newIndex != 0) + newObject = LDObject::fromID (newIndex); + + setObjectAtCursor (newObject); + + if (oldObject != null) + compileObject (oldObject); + + if (newObject != null) + compileObject (newObject); + } + + update(); +} + +void GLRenderer::dragEnterEvent (QDragEnterEvent* ev) +{ + if (g_win != null and ev->source() == g_win->getPrimitivesTree() and g_win->getPrimitivesTree()->currentItem() != null) + ev->acceptProposedAction(); +} + +void GLRenderer::dropEvent (QDropEvent* ev) +{ + if (g_win != null and ev->source() == g_win->getPrimitivesTree()) + { + QString primName = static_cast<SubfileListItem*> (g_win->getPrimitivesTree()->currentItem())->primitive()->name; + LDSubfilePtr ref = LDSpawn<LDSubfile>(); + ref->setColor (MainColor()); + ref->setFileInfo (GetDocument (primName)); + ref->setPosition (Origin); + ref->setTransform (IdentityMatrix); + LDDocument::current()->insertObj (g_win->getInsertionPoint(), ref); + ref->select(); + g_win->buildObjList(); + g_win->R()->refresh(); + ev->acceptProposedAction(); + } +} + +Vertex const& GLRenderer::position3D() const +{ + return m_position3D; +} + +LDFixedCamera const& GLRenderer::getFixedCamera (ECamera cam) const +{ + return g_FixedCameras[cam]; +} + +bool GLRenderer::mouseHasMoved() const +{ + return m_totalmove >= 10; +} + +QPoint const& GLRenderer::mousePosition() const +{ + return m_mousePosition; +} + +QPointF const& GLRenderer::mousePositionF() const +{ + return m_mousePositionF; +} + +void GLRenderer::doMakeCurrent() +{ + makeCurrent(); + initializeOpenGLFunctions(); +} + +int GLRenderer::depthNegateFactor() const +{ + return g_FixedCameras[camera()].negatedDepth ? -1 : 1; +} + +Qt::KeyboardModifiers GLRenderer::keyboardModifiers() const +{ + return m_keymods; +} + +LDFixedCamera const& GetFixedCamera (ECamera cam) +{ + assert (cam != EFreeCamera); + return g_FixedCameras[cam]; +}
--- a/src/ldConfig.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,216 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QFile> -#include "ldDocument.h" -#include "ldConfig.h" -#include "mainWindow.h" -#include "miscallenous.h" -#include "colors.h" - -// ============================================================================= -// -// Helper function for parseLDConfig -// -static bool ParseLDConfigTag (LDConfigParser& pars, char const* tag, QString& val) -{ - int pos; - - // Try find the token and get its position - if (not pars.findToken (pos, tag, 1)) - return false; - - // Get the token after it and store it into val - return pars.getToken (val, pos + 1); -} - -// ============================================================================= -// -void LDConfigParser::parseLDConfig() -{ - QFile* fp = OpenLDrawFile ("LDConfig.ldr", false); - - if (fp == null) - { - Critical (QObject::tr ("Unable to open LDConfig.ldr for parsing.")); - return; - } - - // Read in the lines - while (not fp->atEnd()) - { - QString line = QString::fromUtf8 (fp->readLine()); - - if (line.isEmpty() or 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 (not pars.tokenCompare (0, "0") or - not pars.tokenCompare (1, "!COLOUR") or - not pars.getToken (name, 2)) - { - continue; - } - - // Replace underscores in the name with spaces for readability - name.replace ("_", " "); - - // Get the CODE tag - if (not ParseLDConfigTag (pars, "CODE", valuestr)) - continue; - - // Ensure that the code is within [0 - 511] - bool ok; - code = valuestr.toShort (&ok); - - if (not ok or code < 0 or code >= 512) - continue; - - // VALUE and EDGE tags - if (not ParseLDConfigTag (pars, "VALUE", facename) or not ParseLDConfigTag (pars, "EDGE", edgename)) - continue; - - // Ensure that our colors are correct - QColor faceColor (facename), - edgeColor (edgename); - - if (not faceColor.isValid() or not edgeColor.isValid()) - continue; - - // Parse alpha if given. - if (ParseLDConfigTag (pars, "ALPHA", valuestr)) - alpha = Clamp (valuestr.toInt(), 0, 255); - - LDColorData* col = new LDColorData; - col->m_name = name; - col->m_faceColor = faceColor; - col->m_edgeColor = edgeColor; - col->m_hexcode = facename; - col->m_faceColor.setAlpha (alpha); - col->m_index = code; - LDColor::addLDConfigColor (code, LDColor (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 (not getToken (tok, inPos)) - return false; - - return (tok == sOther); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldConfig.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,216 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QFile> +#include "ldDocument.h" +#include "ldConfig.h" +#include "mainWindow.h" +#include "miscallenous.h" +#include "colors.h" + +// ============================================================================= +// +// Helper function for parseLDConfig +// +static bool ParseLDConfigTag (LDConfigParser& pars, char const* tag, QString& val) +{ + int pos; + + // Try find the token and get its position + if (not pars.findToken (pos, tag, 1)) + return false; + + // Get the token after it and store it into val + return pars.getToken (val, pos + 1); +} + +// ============================================================================= +// +void LDConfigParser::parseLDConfig() +{ + QFile* fp = OpenLDrawFile ("LDConfig.ldr", false); + + if (fp == null) + { + Critical (QObject::tr ("Unable to open LDConfig.ldr for parsing.")); + return; + } + + // Read in the lines + while (not fp->atEnd()) + { + QString line = QString::fromUtf8 (fp->readLine()); + + if (line.isEmpty() or 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 (not pars.tokenCompare (0, "0") or + not pars.tokenCompare (1, "!COLOUR") or + not pars.getToken (name, 2)) + { + continue; + } + + // Replace underscores in the name with spaces for readability + name.replace ("_", " "); + + // Get the CODE tag + if (not ParseLDConfigTag (pars, "CODE", valuestr)) + continue; + + // Ensure that the code is within [0 - 511] + bool ok; + code = valuestr.toShort (&ok); + + if (not ok or code < 0 or code >= 512) + continue; + + // VALUE and EDGE tags + if (not ParseLDConfigTag (pars, "VALUE", facename) or not ParseLDConfigTag (pars, "EDGE", edgename)) + continue; + + // Ensure that our colors are correct + QColor faceColor (facename), + edgeColor (edgename); + + if (not faceColor.isValid() or not edgeColor.isValid()) + continue; + + // Parse alpha if given. + if (ParseLDConfigTag (pars, "ALPHA", valuestr)) + alpha = Clamp (valuestr.toInt(), 0, 255); + + LDColorData* col = new LDColorData; + col->m_name = name; + col->m_faceColor = faceColor; + col->m_edgeColor = edgeColor; + col->m_hexcode = facename; + col->m_faceColor.setAlpha (alpha); + col->m_index = code; + LDColor::addLDConfigColor (code, LDColor (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 (not getToken (tok, inPos)) + return false; + + return (tok == sOther); +}
--- a/src/ldDocument.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1540 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QMessageBox> -#include <QFileDialog> -#include <QDir> -#include <QTime> -#include <QApplication> - -#include "main.h" -#include "configuration.h" -#include "ldDocument.h" -#include "miscallenous.h" -#include "mainWindow.h" -#include "editHistory.h" -#include "dialogs.h" -#include "glRenderer.h" -#include "glCompiler.h" -#include "partDownloader.h" - -CFGENTRY (String, LDrawPath, "") -CFGENTRY (List, RecentFiles, {}) -CFGENTRY (Bool, TryDownloadMissingFiles, false) -EXTERN_CFGENTRY (String, DownloadFilePath) -EXTERN_CFGENTRY (Bool, UseLogoStuds) - -static bool g_loadingMainFile = false; -static const int g_maxRecentFiles = 10; -static bool g_aborted = false; -static LDDocumentPtr g_logoedStud; -static LDDocumentPtr g_logoedStud2; -static QList<LDDocumentWeakPtr> g_allDocuments; -static QList<LDDocumentPtr> g_explicitDocuments; -static LDDocumentPtr g_currentDocument; -static bool g_loadingLogoedStuds = false; - -const QStringList g_specialSubdirectories ({ "s", "48", "8" }); - -// ============================================================================= -// -namespace LDPaths -{ - static QString pathError; - - struct - { - QString LDConfigPath; - QString partsPath, primsPath; - } pathInfo; - - void initPaths() - { - if (not tryConfigure (cfg::LDrawPath)) - { - LDrawPathDialog dlg (false); - - if (not dlg.exec()) - Exit(); - - cfg::LDrawPath = dlg.filename(); - } - } - - bool tryConfigure (QString path) - { - QDir dir; - - if (not dir.cd (path)) - { - pathError = "Directory does not exist."; - return false; - } - - QStringList mustHave = { "LDConfig.ldr", "parts", "p" }; - QStringList contents = dir.entryList (mustHave); - - if (contents.size() != mustHave.size()) - { - pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/."; - return false; - } - - pathInfo.partsPath = format ("%1" DIRSLASH "parts", path); - pathInfo.LDConfigPath = format ("%1" DIRSLASH "LDConfig.ldr", path); - pathInfo.primsPath = format ("%1" DIRSLASH "p", path); - - return true; - } - - // Accessors - QString getError() - { - return pathError; - } - - QString ldconfig() - { - return pathInfo.LDConfigPath; - } - - QString prims() - { - return pathInfo.primsPath; - } - - QString parts() - { - return pathInfo.partsPath; - } -} - -// ============================================================================= -// -LDDocument::LDDocument (LDDocumentPtr* selfptr) : - m_isImplicit (true), - m_flags (0), - m_verticesOutdated (true), - m_needVertexMerge (true), - m_gldata (new LDGLData) -{ - *selfptr = LDDocumentPtr (this); - setSelf (*selfptr); - setSavePosition (-1); - setTabIndex (-1); - setHistory (new History); - history()->setDocument (*selfptr); - m_needsReCache = true; - g_allDocuments << *selfptr; -} - -// ============================================================================= -// -LDDocumentPtr LDDocument::createNew() -{ - LDDocumentPtr ptr; - new LDDocument (&ptr); - return ptr; -} - -// ============================================================================= -// -LDDocument::~LDDocument() -{ - // Don't bother during program termination - if (IsExiting()) - return; - - g_allDocuments.removeOne (self()); - m_flags |= DOCF_IsBeingDestroyed; - delete m_history; - delete m_gldata; -} - -// ============================================================================= -// -void LDDocument::setImplicit (bool const& a) -{ - if (m_isImplicit != a) - { - m_isImplicit = a; - - if (a == false) - { - g_explicitDocuments << self().toStrongRef(); - print ("Opened %1", name()); - - // Implicit files are not compiled by the GL renderer. Now that this - // part is no longer implicit, it needs to be compiled. - if (g_win != null) - g_win->R()->compiler()->compileDocument (self()); - } - else - { - g_explicitDocuments.removeOne (self().toStrongRef()); - print ("Closed %1", name()); - } - - if (g_win != null) - g_win->updateDocumentList(); - - // If the current document just became implicit (e.g. it was 'closed'), - // we need to get a new current document. - if (current() == self() and isImplicit()) - { - if (explicitDocuments().isEmpty()) - newFile(); - else - setCurrent (explicitDocuments().first()); - } - } -} - -// ============================================================================= -// -QList<LDDocumentPtr> const& LDDocument::explicitDocuments() -{ - return g_explicitDocuments; -} - -// ============================================================================= -// -LDDocumentPtr FindDocument (QString name) -{ - for (LDDocumentWeakPtr weakfile : g_allDocuments) - { - if (weakfile == null) - continue; - - LDDocumentPtr file (weakfile.toStrongRef()); - - if (Eq (name, file->name(), file->defaultName())) - return file; - } - - return LDDocumentPtr(); -} - -// ============================================================================= -// -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 FindDocumentPath (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 (LDDocumentWeakPtr doc : g_allDocuments) - { - if (doc == null) - continue; - - QString partpath = format ("%1/%2", Dirname (doc.toStrongRef()->fullPath()), relpath); - QFile f (partpath); - - if (f.exists()) - { - // ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48 - QString proptop = Basename (Dirname (partpath)); - - bool bogus = false; - - for (QString s : g_specialSubdirectories) - { - if ((proptop == s and reltop != s) or (reltop == s and proptop != s)) - { - bogus = true; - break; - } - } - - if (not bogus) - return partpath; - } - } - - if (QFile::exists (relpath)) - return relpath; - - // Try with just the LDraw path first - fullPath = format ("%1" DIRSLASH "%2", cfg::LDrawPath, relpath); - - if (QFile::exists (fullPath)) - return fullPath; - - if (subdirs) - { - // Look in sub-directories: parts and p. Also look in net_downloadpath, since that's - // where we download parts from the PT to. - for (const QString& topdir : QList<QString> ({ cfg::LDrawPath, cfg::DownloadFilePath })) - { - for (const QString& subdir : QList<QString> ({ "parts", "p" })) - { - fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); - - if (QFile::exists (fullPath)) - return fullPath; - } - } - } - - // Did not find the file. - return ""; -} - -// ============================================================================= -// -QFile* OpenLDrawFile (QString relpath, bool subdirs, QString* pathpointer) -{ - print ("Opening %1...\n", relpath); - QString path = FindDocumentPath (relpath, subdirs); - - if (pathpointer != null) - *pathpointer = path; - - if (path.isEmpty()) - return null; - - QFile* fp = new QFile (path); - - if (fp->open (QIODevice::ReadOnly)) - return fp; - - fp->deleteLater(); - return null; -} - -// ============================================================================= -// -void LDFileLoader::start() -{ - setDone (false); - setProgress (0); - setAborted (false); - - if (isOnForeground()) - { - g_aborted = false; - - // Show a progress dialog if we're loading the main ldDocument.here so we can - // show progress updates and keep the WM posted that we're still here. - // Of course we cannot exec() the dialog because then the dialog would - // block. - dlg = new OpenProgressDialog (g_win); - dlg->setNumLines (lines().size()); - dlg->setModal (true); - dlg->show(); - - // Connect the loader in so we can show updates - connect (this, SIGNAL (workDone()), dlg, SLOT (accept())); - connect (dlg, SIGNAL (rejected()), this, SLOT (abort())); - } - else - dlg = null; - - // Begin working - work (0); -} - -// ============================================================================= -// -void LDFileLoader::work (int i) -{ - // User wishes to abort, so stop here now. - if (isAborted()) - { - for (LDObjectPtr obj : m_objects) - obj->destroy(); - - m_objects.clear(); - setDone (true); - return; - } - - // Parse up to 300 lines per iteration - int max = i + 300; - - for (; i < max and i < (int) lines().size(); ++i) - { - QString line = lines()[i]; - - // Trim the trailing newline - QChar c; - - while (line.endsWith ("\n") or line.endsWith ("\r")) - line.chop (1); - - LDObjectPtr obj = ParseLine (line); - - // Check for parse errors and warn about tthem - if (obj->type() == OBJ_Error) - { - print ("Couldn't parse line #%1: %2", - progress() + 1, obj.staticCast<LDError>()->reason()); - - if (warnings() != null) - (*warnings())++; - } - - m_objects << obj; - setProgress (i); - - // If we have a dialog pointer, update the progress now - if (isOnForeground()) - dlg->updateProgress (i); - } - - // If we're done now, tell the environment we're done and stop. - if (i >= ((int) lines().size()) - 1) - { - emit workDone(); - setDone (true); - return; - } - - // Otherwise, continue, by recursing back. - if (not 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 (not fp->atEnd()) - 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 (not loader->isDone()) - qApp->processEvents(); - - // If we wanted the success value, supply that now - if (ok) - *ok = not loader->isAborted(); - - objs = loader->objects(); - delete loader; - return objs; -} - -// ============================================================================= -// -LDDocumentPtr OpenDocument (QString path, bool search, bool implicit, LDDocumentPtr fileToOverride) -{ - // Convert the file name to lowercase when searching because some parts contain subfile - // subfile references with uppercase file names. I'll assume here that the library will always - // use lowercase file names for the part files. - QFile* fp; - QString fullpath; - - if (search) - { - fp = OpenLDrawFile (path.toLower(), true, &fullpath); - } - else - { - fp = new QFile (path); - fullpath = path; - - if (not fp->open (QIODevice::ReadOnly)) - { - delete fp; - return LDDocumentPtr(); - } - } - - if (not fp) - return LDDocumentPtr(); - - LDDocumentPtr load = (fileToOverride != null ? fileToOverride : LDDocument::createNew()); - load->setImplicit (implicit); - load->setFullPath (fullpath); - load->setName (LDDocument::shortenName (load->fullPath())); - - // Loading the file shouldn't count as actual edits to the document. - load->history()->setIgnoring (true); - - int numWarnings; - bool ok; - LDObjectList objs = LoadFileContents (fp, &numWarnings, &ok); - fp->close(); - fp->deleteLater(); - - if (not ok) - { - load->dismiss(); - return LDDocumentPtr(); - } - - load->addObjects (objs); - - if (g_loadingMainFile) - { - LDDocument::setCurrent (load); - g_win->R()->setDocument (load); - print (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); - } - - load->history()->setIgnoring (false); - return load; -} - -// ============================================================================= -// -bool LDDocument::isSafeToClose() -{ - using msgbox = QMessageBox; - setlocale (LC_ALL, "C"); - - // If we have unsaved changes, warn and give the option of saving. - if (hasUnsavedChanges()) - { - QString message = format (QObject::tr ("There are unsaved changes to %1. Should it be saved?"), getDisplayName()); - - int button = msgbox::question (g_win, QObject::tr ("Unsaved Changes"), message, - (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); - - switch (button) - { - case msgbox::Yes: - { - // If we don't have a file path yet, we have to ask the user for one. - if (name().length() == 0) - { - QString newpath = QFileDialog::getSaveFileName (g_win, QObject::tr ("Save As"), - CurrentDocument()->name(), QObject::tr ("LDraw files (*.dat *.ldr)")); - - if (newpath.length() == 0) - return false; - - setName (newpath); - } - - if (not save()) - { - message = format (QObject::tr ("Failed to save %1 (%2)\nDo you still want to close?"), - name(), strerror (errno)); - - if (msgbox::critical (g_win, QObject::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 CloseAllDocuments() -{ - for (LDDocumentPtr file : g_explicitDocuments) - file->dismiss(); -} - -// ============================================================================= -// -void newFile() -{ - // Create a new anonymous file and set it to our current - LDDocumentPtr f = LDDocument::createNew(); - f->setName (""); - f->setImplicit (false); - LDDocument::setCurrent (f); - LDDocument::closeInitialFile(); - g_win->R()->setDocument (f); - g_win->doFullRefresh(); - g_win->updateTitle(); - g_win->updateActions(); -} - -// ============================================================================= -// -void AddRecentFile (QString path) -{ - int idx = cfg::RecentFiles.indexOf (path); - - // If this file already is in the list, pop it out. - if (idx != -1) - { - if (idx == cfg::RecentFiles.size() - 1) - return; // first recent file - abort and do nothing - - cfg::RecentFiles.removeAt (idx); - } - - // If there's too many recent files, drop one out. - while (cfg::RecentFiles.size() > (g_maxRecentFiles - 1)) - cfg::RecentFiles.removeAt (0); - - // Add the file - cfg::RecentFiles << path; - - Config::Save(); - g_win->updateRecentFilesMenu(); -} - -// ============================================================================= -// Open an LDraw file and set it as the main model -// ============================================================================= -void OpenMainModel (QString path) -{ - // If there's already a file with the same name, this file must replace it. - LDDocumentPtr documentToReplace; - LDDocumentPtr file; - QString shortName = LDDocument::shortenName (path); - - for (LDDocumentWeakPtr doc : g_allDocuments) - { - if (doc != null and doc.toStrongRef()->name() == shortName) - { - documentToReplace = doc; - break; - } - } - - // We cannot open this file if the document this would replace is not - // safe to close. - if (documentToReplace != null and not documentToReplace->isSafeToClose()) - return; - - g_loadingMainFile = true; - - // If we're replacing an existing document, clear the document and - // make it ready for being loaded to. - if (documentToReplace != null) - { - file = documentToReplace; - file->clear(); - } - - file = OpenDocument (path, false, false, file); - - if (file == null) - { - if (not g_aborted) - { - // Tell the user loading failed. - setlocale (LC_ALL, "C"); - Critical (format (QObject::tr ("Failed to open %1: %2"), path, strerror (errno))); - } - - g_loadingMainFile = false; - return; - } - - file->setImplicit (false); - - // 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; - - // If there were problems loading subfile references, try see if we can find these - // files on the parts tracker. - QStringList unknowns; - - for (LDObjectPtr obj : file->objects()) - { - if (obj->type() != OBJ_Error or obj.staticCast<LDError>()->fileReferenced().isEmpty()) - continue; - - unknowns << obj.staticCast<LDError>()->fileReferenced(); - } - - if (cfg::TryDownloadMissingFiles and not unknowns.isEmpty()) - { - PartDownloader dl; - - if (dl.checkValidPath()) - { - dl.setSource (PartDownloader::PartsTracker); - dl.setPrimaryFile (file); - - for (QString const& unknown : unknowns) - dl.downloadFromPartsTracker (unknown); - - dl.exec(); - dl.checkIfFinished(); - file->reloadAllSubfiles(); - } - } -} - -// ============================================================================= -// -bool LDDocument::save (QString path, int64* sizeptr) -{ - if (isImplicit()) - return false; - - if (not path.length()) - path = fullPath(); - - // If the second object in the list holds the file name, update that now. - LDObjectPtr nameObject = getObject (1); - - if (nameObject != null and nameObject->type() == OBJ_Comment) - { - LDCommentPtr nameComment = nameObject.staticCast<LDComment>(); - - if (nameComment->text().left (6) == "Name: ") - { - QString newname = shortenName (path); - nameComment->setText (format ("Name: %1", newname)); - g_win->buildObjList(); - } - } - - QByteArray data; - - if (sizeptr != null) - *sizeptr = 0; - - // 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 (LDObjectPtr obj : objects()) - { - QByteArray subdata ((obj->asText() + "\r\n").toUtf8()); - data.append (subdata); - - if (sizeptr != null) - *sizeptr += subdata.size(); - } - - QFile f (path); - - if (not f.open (QIODevice::WriteOnly)) - return false; - - f.write (data); - f.close(); - - // We have successfully saved, update the save position now. - setSavePosition (history()->position()); - setFullPath (path); - setName (shortenName (path)); - - g_win->updateDocumentListItem (self().toStrongRef()); - g_win->updateTitle(); - return true; -} - -// ============================================================================= -// -void LDDocument::clear() -{ - for (LDObjectPtr obj : objects()) - forgetObject (obj); -} - -// ============================================================================= -// -static void CheckTokenCount (const QStringList& tokens, int num) -{ - if (tokens.size() != num) - throw QString (format ("Bad amount of tokens, expected %1, got %2", num, tokens.size())); -} - -// ============================================================================= -// -static void CheckTokenNumbers (const QStringList& tokens, int min, int max) -{ - bool ok; - - QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+"); - - for (int i = min; i <= max; ++i) - { - // Check for floating point - tokens[i].toDouble (&ok); - if (ok) - return; - - // Check hex - if (tokens[i].startsWith ("0x")) - { - tokens[i].mid (2).toInt (&ok, 16); - - if (ok) - return; - } - - // Check scientific notation, e.g. 7.99361e-15 - if (scient.exactMatch (tokens[i])) - return; - - throw QString (format ("Token #%1 was `%2`, expected a number (matched length: %3)", - (i + 1), tokens[i], scient.matchedLength())); - } -} - -// ============================================================================= -// -static Vertex ParseVertex (QStringList& s, const int n) -{ - Vertex v; - v.apply ([&] (Axis ax, double& a) { a = s[n + ax].toDouble(); }); - return v; -} - -static int32 StringToNumber (QString a, bool* ok = null) -{ - int base = 10; - - if (a.startsWith ("0x")) - { - a.remove (0, 2); - base = 16; - } - - return a.toLong (ok, base); -} - -// ============================================================================= -// 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. -// ============================================================================= -LDObjectPtr ParseLine (QString line) -{ - try - { - QStringList tokens = line.split (" ", QString::SkipEmptyParts); - - if (tokens.size() <= 0) - { - // Line was empty, or only consisted of whitespace - return LDSpawn<LDEmpty>(); - } - - if (tokens[0].length() != 1 or not tokens[0][0].isDigit()) - throw QString ("Illogical line code"); - - int num = tokens[0][0].digitValue(); - - switch (num) - { - case 0: - { - // Comment - QString commentText (line.mid (line.indexOf ("0") + 2)); - QString commentTextSimplified (commentText.simplified()); - - // Handle BFC statements - if (tokens.size() > 2 and tokens[1] == "BFC") - { - for_enum (BFCStatement, i) - { - if (commentTextSimplified == format ("BFC %1", - LDBFC::StatementStrings[int (i)])) - { - return LDSpawn<LDBFC> (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. - if (commentTextSimplified == "BFC CERTIFY INVERTNEXT") - return LDSpawn<LDBFC> (BFCStatement::InvertNext); - elif (commentTextSimplified == "BFC CERTIFY CLIP") - return LDSpawn<LDBFC> (BFCStatement::Clip); - elif (commentTextSimplified == "BFC CERTIFY NOCLIP") - return LDSpawn<LDBFC> (BFCStatement::NoClip); - } - - if (tokens.size() > 2 and tokens[1] == "!LDFORGE") - { - // Handle LDForge-specific types, they're embedded into comments too - if (tokens[2] == "OVERLAY") - { - CheckTokenCount (tokens, 9); - CheckTokenNumbers (tokens, 5, 8); - - LDOverlayPtr obj = LDSpawn<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: - LDCommentPtr obj = LDSpawn<LDComment>(); - obj->setText (commentText); - return obj; - } - - case 1: - { - // Subfile - CheckTokenCount (tokens, 15); - CheckTokenNumbers (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; - LDDocumentPtr 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 (not load) - { - LDErrorPtr obj = LDSpawn<LDError> (line, format ("Could not open %1", tokens[14])); - obj->setFileReferenced (tokens[14]); - return obj; - } - - LDSubfilePtr obj = LDSpawn<LDSubfile>(); - obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1]))); - 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 (tokens, 8); - CheckTokenNumbers (tokens, 1, 7); - - // Line - LDLinePtr obj (LDSpawn<LDLine>()); - obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1]))); - - for (int i = 0; i < 2; ++i) - obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 7 - - return obj; - } - - case 3: - { - CheckTokenCount (tokens, 11); - CheckTokenNumbers (tokens, 1, 10); - - // Triangle - LDTrianglePtr obj (LDSpawn<LDTriangle>()); - obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1]))); - - for (int i = 0; i < 3; ++i) - obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 10 - - return obj; - } - - case 4: - case 5: - { - CheckTokenCount (tokens, 14); - CheckTokenNumbers (tokens, 1, 13); - - // Quadrilateral / Conditional line - LDObjectPtr obj; - - if (num == 4) - obj = LDSpawn<LDQuad>(); - else - obj = LDSpawn<LDCondLine>(); - - obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1]))); - - for (int i = 0; i < 4; ++i) - obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 13 - - return obj; - } - - default: - throw QString ("Unknown line code number"); - } - } - catch (QString& e) - { - // Strange line we couldn't parse - return LDSpawn<LDError> (line, e); - } -} - -// ============================================================================= -// -LDDocumentPtr GetDocument (QString filename) -{ - // Try find the file in the list of loaded files - LDDocumentPtr doc = FindDocument (filename); - - // If it's not loaded, try open it - if (not doc) - doc = OpenDocument (filename, true, true); - - return doc; -} - -// ============================================================================= -// -void LDDocument::reloadAllSubfiles() -{ - print ("Reloading subfiles of %1", getDisplayName()); - - // Go through all objects in the current file and reload the subfiles - for (LDObjectPtr obj : objects()) - { - if (obj->type() == OBJ_Subfile) - { - LDSubfilePtr ref = obj.staticCast<LDSubfile>(); - LDDocumentPtr fileInfo = GetDocument (ref->fileInfo()->name()); - - if (fileInfo != null) - { - ref->setFileInfo (fileInfo); - } - else - { - ref->replace (LDSpawn<LDError> (ref->asText(), - format ("Could not open %1", ref->fileInfo()->name()))); - } - } - - // Reparse gibberish files. It could be that they are invalid because - // of loading errors. Circumstances may be different now. - if (obj->type() == OBJ_Error) - obj->replace (ParseLine (obj.staticCast<LDError>()->contents())); - } - - m_needsReCache = true; - - if (self() == CurrentDocument()) - g_win->buildObjList(); -} - -// ============================================================================= -// -int LDDocument::addObject (LDObjectPtr obj) -{ - history()->add (new AddHistory (objects().size(), obj)); - m_objects << obj; - addKnownVertices (obj); - obj->setDocument (self()); - g_win->R()->compileObject (obj); - return getObjectCount() - 1; -} - -// ============================================================================= -// -void LDDocument::addObjects (const LDObjectList& objs) -{ - for (LDObjectPtr obj : objs) - { - if (obj != null) - addObject (obj); - } -} - -// ============================================================================= -// -void LDDocument::insertObj (int pos, LDObjectPtr obj) -{ - history()->add (new AddHistory (pos, obj)); - m_objects.insert (pos, obj); - obj->setDocument (self()); - g_win->R()->compileObject (obj); - - -#ifdef DEBUG - if (not isImplicit()) - dprint ("Inserted object #%1 (%2) at %3\n", obj->id(), obj->typeName(), pos); -#endif -} - -// ============================================================================= -// -void LDDocument::addKnownVertices (LDObjectPtr obj) -{ - auto it = m_objectVertices.find (obj); - - if (it == m_objectVertices.end()) - it = m_objectVertices.insert (obj, QVector<Vertex>()); - else - it->clear(); - - obj->getVertices (*it); - needVertexMerge(); -} - -// ============================================================================= -// -void LDDocument::forgetObject (LDObjectPtr obj) -{ - int idx = obj->lineNumber(); - obj->deselect(); - assert (m_objects[idx] == obj); - - if (not isImplicit() and not (flags() & DOCF_IsBeingDestroyed)) - { - history()->add (new DelHistory (idx, obj)); - m_objectVertices.remove (obj); - } - - m_objects.removeAt (idx); - obj->setDocument (LDDocumentPtr()); -} - -// ============================================================================= -// -bool IsSafeToCloseAll() -{ - for (LDDocumentPtr f : LDDocument::explicitDocuments()) - { - if (not f->isSafeToClose()) - return false; - } - - return true; -} - -// ============================================================================= -// -void LDDocument::setObject (int idx, LDObjectPtr obj) -{ - assert (idx >= 0 and idx < m_objects.size()); - - // Mark this change to history - if (not m_history->isIgnoring()) - { - QString oldcode = getObject (idx)->asText(); - QString newcode = obj->asText(); - *m_history << new EditHistory (idx, oldcode, newcode); - } - - m_objectVertices.remove (m_objects[idx]); - m_objects[idx]->deselect(); - m_objects[idx]->setDocument (LDDocumentPtr()); - obj->setDocument (self()); - addKnownVertices (obj); - g_win->R()->compileObject (obj); - m_objects[idx] = obj; - needVertexMerge(); -} - -// ============================================================================= -// -LDObjectPtr LDDocument::getObject (int pos) const -{ - if (m_objects.size() <= pos) - return LDObjectPtr(); - - return m_objects[pos]; -} - -// ============================================================================= -// -int LDDocument::getObjectCount() const -{ - return objects().size(); -} - -// ============================================================================= -// -bool LDDocument::hasUnsavedChanges() const -{ - return not isImplicit() and history()->position() != savePosition(); -} - -// ============================================================================= -// -QString LDDocument::getDisplayName() -{ - if (not name().isEmpty()) - return name(); - - if (not defaultName().isEmpty()) - return "[" + defaultName() + "]"; - - return QObject::tr ("untitled"); -} - -// ============================================================================= -// -void LDDocument::initializeCachedData() -{ - if (m_needsReCache) - { - m_vertices.clear(); - - for (LDObjectPtr obj : inlineContents (true, true)) - { - if (obj->type() == OBJ_Subfile) - { - print ("Warning: unable to inline %1 into %2", - obj.staticCast<LDSubfile>()->fileInfo()->getDisplayName(), - getDisplayName()); - continue; - } - - LDPolygon* data = obj->getPolygon(); - - if (data != null) - { - m_polygonData << *data; - delete data; - } - } - - m_needsReCache = false; - } - - if (m_verticesOutdated) - { - m_objectVertices.clear(); - - for (LDObjectPtr obj : inlineContents (true, false)) - addKnownVertices (obj); - - mergeVertices(); - m_verticesOutdated = false; - } - - if (m_needVertexMerge) - mergeVertices(); -} - -// ============================================================================= -// -void LDDocument::mergeVertices() -{ - m_vertices.clear(); - - for (QVector<Vertex> const& verts : m_objectVertices) - m_vertices << verts; - - RemoveDuplicates (m_vertices); - m_needVertexMerge = false; -} - -// ============================================================================= -// -QList<LDPolygon> LDDocument::inlinePolygons() -{ - initializeCachedData(); - return polygonData(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObjectList LDDocument::inlineContents (bool deep, bool renderinline) -{ - // Possibly substitute with logoed studs: - // stud.dat -> stud-logo.dat - // stud2.dat -> stud-logo2.dat - if (cfg::UseLogoStuds and renderinline) - { - // Ensure logoed studs are loaded first - LoadLogoStuds(); - - if (name() == "stud.dat" and g_logoedStud != null) - return g_logoedStud->inlineContents (deep, renderinline); - elif (name() == "stud2.dat" and g_logoedStud2 != null) - return g_logoedStud2->inlineContents (deep, renderinline); - } - - LDObjectList objs, objcache; - - for (LDObjectPtr obj : objects()) - { - // Skip those without scemantic meaning - if (not obj->isScemantic()) - continue; - - // Got another sub-file reference, inline it if we're deep-inlining. If not, - // just add it into the objects normally. Yay, recursion! - if (deep == true and obj->type() == OBJ_Subfile) - { - for (LDObjectPtr otherobj : obj.staticCast<LDSubfile>()->inlineContents (deep, renderinline)) - objs << otherobj; - } - else - objs << obj->createCopy(); - } - - return objs; -} - -// ============================================================================= -// -LDDocumentPtr LDDocument::current() -{ - return g_currentDocument; -} - -// ============================================================================= -// 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 (LDDocumentPtr f) -{ - // Implicit files were loaded for caching purposes and must never be set - // current. - if (f != null and f->isImplicit()) - return; - - g_currentDocument = f; - - if (g_win and f) - { - // A ton of stuff needs to be updated - g_win->updateDocumentListItem (f); - g_win->buildObjList(); - g_win->updateTitle(); - g_win->R()->setDocument (f); - g_win->R()->compiler()->needMerge(); - print ("Changed file to %1", f->getDisplayName()); - } -} - -// ============================================================================= -// -int LDDocument::countExplicitFiles() -{ - return g_explicitDocuments.size(); -} - -// ============================================================================= -// This little beauty closes the initial file that was open at first when opening -// a new file over it. -// ============================================================================= -void LDDocument::closeInitialFile() -{ - if (g_explicitDocuments.size() == 2 and - g_explicitDocuments[0]->name().isEmpty() and - not g_explicitDocuments[1]->name().isEmpty() and - not g_explicitDocuments[0]->hasUnsavedChanges()) - { - LDDocumentPtr filetoclose = g_explicitDocuments.first(); - filetoclose->dismiss(); - } -} - -// ============================================================================= -// -void LoadLogoStuds() -{ - if (g_loadingLogoedStuds or (g_logoedStud and g_logoedStud2)) - return; - - g_loadingLogoedStuds = true; - g_logoedStud = OpenDocument ("stud-logo.dat", true, true); - g_logoedStud2 = OpenDocument ("stud2-logo.dat", true, true); - print (QObject::tr ("Logoed studs loaded.\n")); - g_loadingLogoedStuds = false; -} - -// ============================================================================= -// -void LDDocument::addToSelection (LDObjectPtr obj) // [protected] -{ - if (obj->isSelected()) - return; - - assert (obj->document() == self()); - m_sel << obj; - g_win->R()->compileObject (obj); - obj->setSelected (true); -} - -// ============================================================================= -// -void LDDocument::removeFromSelection (LDObjectPtr obj) // [protected] -{ - if (not obj->isSelected()) - return; - - assert (obj->document() == self()); - m_sel.removeOne (obj); - g_win->R()->compileObject (obj); - obj->setSelected (false); -} - -// ============================================================================= -// -void LDDocument::clearSelection() -{ - for (LDObjectPtr obj : m_sel) - removeFromSelection (obj); - - assert (m_sel.isEmpty()); -} - -// ============================================================================= -// -const LDObjectList& LDDocument::getSelection() const -{ - return m_sel; -} - -// ============================================================================= -// -void LDDocument::swapObjects (LDObjectPtr one, LDObjectPtr other) -{ - int a = m_objects.indexOf (one); - int b = m_objects.indexOf (other); - assert (a != b and a != -1 and b != -1); - m_objects[b] = one; - m_objects[a] = other; - addToHistory (new SwapHistory (one->id(), other->id())); -} - -// ============================================================================= -// -QString LDDocument::shortenName (QString a) // [static] -{ - QString shortname = Basename (a); - QString topdirname = Basename (Dirname (a)); - - if (g_specialSubdirectories.contains (topdirname)) - shortname.prepend (topdirname + "\\"); - - return shortname; -} - -// ============================================================================= -// -QVector<Vertex> const& LDDocument::inlineVertices() -{ - initializeCachedData(); - return m_vertices; -} - -void LDDocument::redoVertices() -{ - m_verticesOutdated = true; -} - -void LDDocument::needVertexMerge() -{ - m_needVertexMerge = true; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldDocument.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,1540 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QMessageBox> +#include <QFileDialog> +#include <QDir> +#include <QTime> +#include <QApplication> + +#include "main.h" +#include "configuration.h" +#include "ldDocument.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "editHistory.h" +#include "dialogs.h" +#include "glRenderer.h" +#include "glCompiler.h" +#include "partDownloader.h" + +CFGENTRY (String, LDrawPath, "") +CFGENTRY (List, RecentFiles, {}) +CFGENTRY (Bool, TryDownloadMissingFiles, false) +EXTERN_CFGENTRY (String, DownloadFilePath) +EXTERN_CFGENTRY (Bool, UseLogoStuds) + +static bool g_loadingMainFile = false; +static const int g_maxRecentFiles = 10; +static bool g_aborted = false; +static LDDocumentPtr g_logoedStud; +static LDDocumentPtr g_logoedStud2; +static QList<LDDocumentWeakPtr> g_allDocuments; +static QList<LDDocumentPtr> g_explicitDocuments; +static LDDocumentPtr g_currentDocument; +static bool g_loadingLogoedStuds = false; + +const QStringList g_specialSubdirectories ({ "s", "48", "8" }); + +// ============================================================================= +// +namespace LDPaths +{ + static QString pathError; + + struct + { + QString LDConfigPath; + QString partsPath, primsPath; + } pathInfo; + + void initPaths() + { + if (not tryConfigure (cfg::LDrawPath)) + { + LDrawPathDialog dlg (false); + + if (not dlg.exec()) + Exit(); + + cfg::LDrawPath = dlg.filename(); + } + } + + bool tryConfigure (QString path) + { + QDir dir; + + if (not dir.cd (path)) + { + pathError = "Directory does not exist."; + return false; + } + + QStringList mustHave = { "LDConfig.ldr", "parts", "p" }; + QStringList contents = dir.entryList (mustHave); + + if (contents.size() != mustHave.size()) + { + pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/."; + return false; + } + + pathInfo.partsPath = format ("%1" DIRSLASH "parts", path); + pathInfo.LDConfigPath = format ("%1" DIRSLASH "LDConfig.ldr", path); + pathInfo.primsPath = format ("%1" DIRSLASH "p", path); + + return true; + } + + // Accessors + QString getError() + { + return pathError; + } + + QString ldconfig() + { + return pathInfo.LDConfigPath; + } + + QString prims() + { + return pathInfo.primsPath; + } + + QString parts() + { + return pathInfo.partsPath; + } +} + +// ============================================================================= +// +LDDocument::LDDocument (LDDocumentPtr* selfptr) : + m_isImplicit (true), + m_flags (0), + m_verticesOutdated (true), + m_needVertexMerge (true), + m_gldata (new LDGLData) +{ + *selfptr = LDDocumentPtr (this); + setSelf (*selfptr); + setSavePosition (-1); + setTabIndex (-1); + setHistory (new History); + history()->setDocument (*selfptr); + m_needsReCache = true; + g_allDocuments << *selfptr; +} + +// ============================================================================= +// +LDDocumentPtr LDDocument::createNew() +{ + LDDocumentPtr ptr; + new LDDocument (&ptr); + return ptr; +} + +// ============================================================================= +// +LDDocument::~LDDocument() +{ + // Don't bother during program termination + if (IsExiting()) + return; + + g_allDocuments.removeOne (self()); + m_flags |= DOCF_IsBeingDestroyed; + delete m_history; + delete m_gldata; +} + +// ============================================================================= +// +void LDDocument::setImplicit (bool const& a) +{ + if (m_isImplicit != a) + { + m_isImplicit = a; + + if (a == false) + { + g_explicitDocuments << self().toStrongRef(); + print ("Opened %1", name()); + + // Implicit files are not compiled by the GL renderer. Now that this + // part is no longer implicit, it needs to be compiled. + if (g_win != null) + g_win->R()->compiler()->compileDocument (self()); + } + else + { + g_explicitDocuments.removeOne (self().toStrongRef()); + print ("Closed %1", name()); + } + + if (g_win != null) + g_win->updateDocumentList(); + + // If the current document just became implicit (e.g. it was 'closed'), + // we need to get a new current document. + if (current() == self() and isImplicit()) + { + if (explicitDocuments().isEmpty()) + newFile(); + else + setCurrent (explicitDocuments().first()); + } + } +} + +// ============================================================================= +// +QList<LDDocumentPtr> const& LDDocument::explicitDocuments() +{ + return g_explicitDocuments; +} + +// ============================================================================= +// +LDDocumentPtr FindDocument (QString name) +{ + for (LDDocumentWeakPtr weakfile : g_allDocuments) + { + if (weakfile == null) + continue; + + LDDocumentPtr file (weakfile.toStrongRef()); + + if (Eq (name, file->name(), file->defaultName())) + return file; + } + + return LDDocumentPtr(); +} + +// ============================================================================= +// +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 FindDocumentPath (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 (LDDocumentWeakPtr doc : g_allDocuments) + { + if (doc == null) + continue; + + QString partpath = format ("%1/%2", Dirname (doc.toStrongRef()->fullPath()), relpath); + QFile f (partpath); + + if (f.exists()) + { + // ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48 + QString proptop = Basename (Dirname (partpath)); + + bool bogus = false; + + for (QString s : g_specialSubdirectories) + { + if ((proptop == s and reltop != s) or (reltop == s and proptop != s)) + { + bogus = true; + break; + } + } + + if (not bogus) + return partpath; + } + } + + if (QFile::exists (relpath)) + return relpath; + + // Try with just the LDraw path first + fullPath = format ("%1" DIRSLASH "%2", cfg::LDrawPath, relpath); + + if (QFile::exists (fullPath)) + return fullPath; + + if (subdirs) + { + // Look in sub-directories: parts and p. Also look in net_downloadpath, since that's + // where we download parts from the PT to. + for (const QString& topdir : QList<QString> ({ cfg::LDrawPath, cfg::DownloadFilePath })) + { + for (const QString& subdir : QList<QString> ({ "parts", "p" })) + { + fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); + + if (QFile::exists (fullPath)) + return fullPath; + } + } + } + + // Did not find the file. + return ""; +} + +// ============================================================================= +// +QFile* OpenLDrawFile (QString relpath, bool subdirs, QString* pathpointer) +{ + print ("Opening %1...\n", relpath); + QString path = FindDocumentPath (relpath, subdirs); + + if (pathpointer != null) + *pathpointer = path; + + if (path.isEmpty()) + return null; + + QFile* fp = new QFile (path); + + if (fp->open (QIODevice::ReadOnly)) + return fp; + + fp->deleteLater(); + return null; +} + +// ============================================================================= +// +void LDFileLoader::start() +{ + setDone (false); + setProgress (0); + setAborted (false); + + if (isOnForeground()) + { + g_aborted = false; + + // Show a progress dialog if we're loading the main ldDocument.here so we can + // show progress updates and keep the WM posted that we're still here. + // Of course we cannot exec() the dialog because then the dialog would + // block. + dlg = new OpenProgressDialog (g_win); + dlg->setNumLines (lines().size()); + dlg->setModal (true); + dlg->show(); + + // Connect the loader in so we can show updates + connect (this, SIGNAL (workDone()), dlg, SLOT (accept())); + connect (dlg, SIGNAL (rejected()), this, SLOT (abort())); + } + else + dlg = null; + + // Begin working + work (0); +} + +// ============================================================================= +// +void LDFileLoader::work (int i) +{ + // User wishes to abort, so stop here now. + if (isAborted()) + { + for (LDObjectPtr obj : m_objects) + obj->destroy(); + + m_objects.clear(); + setDone (true); + return; + } + + // Parse up to 300 lines per iteration + int max = i + 300; + + for (; i < max and i < (int) lines().size(); ++i) + { + QString line = lines()[i]; + + // Trim the trailing newline + QChar c; + + while (line.endsWith ("\n") or line.endsWith ("\r")) + line.chop (1); + + LDObjectPtr obj = ParseLine (line); + + // Check for parse errors and warn about tthem + if (obj->type() == OBJ_Error) + { + print ("Couldn't parse line #%1: %2", + progress() + 1, obj.staticCast<LDError>()->reason()); + + if (warnings() != null) + (*warnings())++; + } + + m_objects << obj; + setProgress (i); + + // If we have a dialog pointer, update the progress now + if (isOnForeground()) + dlg->updateProgress (i); + } + + // If we're done now, tell the environment we're done and stop. + if (i >= ((int) lines().size()) - 1) + { + emit workDone(); + setDone (true); + return; + } + + // Otherwise, continue, by recursing back. + if (not 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 (not fp->atEnd()) + 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 (not loader->isDone()) + qApp->processEvents(); + + // If we wanted the success value, supply that now + if (ok) + *ok = not loader->isAborted(); + + objs = loader->objects(); + delete loader; + return objs; +} + +// ============================================================================= +// +LDDocumentPtr OpenDocument (QString path, bool search, bool implicit, LDDocumentPtr fileToOverride) +{ + // Convert the file name to lowercase when searching because some parts contain subfile + // subfile references with uppercase file names. I'll assume here that the library will always + // use lowercase file names for the part files. + QFile* fp; + QString fullpath; + + if (search) + { + fp = OpenLDrawFile (path.toLower(), true, &fullpath); + } + else + { + fp = new QFile (path); + fullpath = path; + + if (not fp->open (QIODevice::ReadOnly)) + { + delete fp; + return LDDocumentPtr(); + } + } + + if (not fp) + return LDDocumentPtr(); + + LDDocumentPtr load = (fileToOverride != null ? fileToOverride : LDDocument::createNew()); + load->setImplicit (implicit); + load->setFullPath (fullpath); + load->setName (LDDocument::shortenName (load->fullPath())); + + // Loading the file shouldn't count as actual edits to the document. + load->history()->setIgnoring (true); + + int numWarnings; + bool ok; + LDObjectList objs = LoadFileContents (fp, &numWarnings, &ok); + fp->close(); + fp->deleteLater(); + + if (not ok) + { + load->dismiss(); + return LDDocumentPtr(); + } + + load->addObjects (objs); + + if (g_loadingMainFile) + { + LDDocument::setCurrent (load); + g_win->R()->setDocument (load); + print (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); + } + + load->history()->setIgnoring (false); + return load; +} + +// ============================================================================= +// +bool LDDocument::isSafeToClose() +{ + using msgbox = QMessageBox; + setlocale (LC_ALL, "C"); + + // If we have unsaved changes, warn and give the option of saving. + if (hasUnsavedChanges()) + { + QString message = format (QObject::tr ("There are unsaved changes to %1. Should it be saved?"), getDisplayName()); + + int button = msgbox::question (g_win, QObject::tr ("Unsaved Changes"), message, + (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); + + switch (button) + { + case msgbox::Yes: + { + // If we don't have a file path yet, we have to ask the user for one. + if (name().length() == 0) + { + QString newpath = QFileDialog::getSaveFileName (g_win, QObject::tr ("Save As"), + CurrentDocument()->name(), QObject::tr ("LDraw files (*.dat *.ldr)")); + + if (newpath.length() == 0) + return false; + + setName (newpath); + } + + if (not save()) + { + message = format (QObject::tr ("Failed to save %1 (%2)\nDo you still want to close?"), + name(), strerror (errno)); + + if (msgbox::critical (g_win, QObject::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 CloseAllDocuments() +{ + for (LDDocumentPtr file : g_explicitDocuments) + file->dismiss(); +} + +// ============================================================================= +// +void newFile() +{ + // Create a new anonymous file and set it to our current + LDDocumentPtr f = LDDocument::createNew(); + f->setName (""); + f->setImplicit (false); + LDDocument::setCurrent (f); + LDDocument::closeInitialFile(); + g_win->R()->setDocument (f); + g_win->doFullRefresh(); + g_win->updateTitle(); + g_win->updateActions(); +} + +// ============================================================================= +// +void AddRecentFile (QString path) +{ + int idx = cfg::RecentFiles.indexOf (path); + + // If this file already is in the list, pop it out. + if (idx != -1) + { + if (idx == cfg::RecentFiles.size() - 1) + return; // first recent file - abort and do nothing + + cfg::RecentFiles.removeAt (idx); + } + + // If there's too many recent files, drop one out. + while (cfg::RecentFiles.size() > (g_maxRecentFiles - 1)) + cfg::RecentFiles.removeAt (0); + + // Add the file + cfg::RecentFiles << path; + + Config::Save(); + g_win->updateRecentFilesMenu(); +} + +// ============================================================================= +// Open an LDraw file and set it as the main model +// ============================================================================= +void OpenMainModel (QString path) +{ + // If there's already a file with the same name, this file must replace it. + LDDocumentPtr documentToReplace; + LDDocumentPtr file; + QString shortName = LDDocument::shortenName (path); + + for (LDDocumentWeakPtr doc : g_allDocuments) + { + if (doc != null and doc.toStrongRef()->name() == shortName) + { + documentToReplace = doc; + break; + } + } + + // We cannot open this file if the document this would replace is not + // safe to close. + if (documentToReplace != null and not documentToReplace->isSafeToClose()) + return; + + g_loadingMainFile = true; + + // If we're replacing an existing document, clear the document and + // make it ready for being loaded to. + if (documentToReplace != null) + { + file = documentToReplace; + file->clear(); + } + + file = OpenDocument (path, false, false, file); + + if (file == null) + { + if (not g_aborted) + { + // Tell the user loading failed. + setlocale (LC_ALL, "C"); + Critical (format (QObject::tr ("Failed to open %1: %2"), path, strerror (errno))); + } + + g_loadingMainFile = false; + return; + } + + file->setImplicit (false); + + // 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; + + // If there were problems loading subfile references, try see if we can find these + // files on the parts tracker. + QStringList unknowns; + + for (LDObjectPtr obj : file->objects()) + { + if (obj->type() != OBJ_Error or obj.staticCast<LDError>()->fileReferenced().isEmpty()) + continue; + + unknowns << obj.staticCast<LDError>()->fileReferenced(); + } + + if (cfg::TryDownloadMissingFiles and not unknowns.isEmpty()) + { + PartDownloader dl; + + if (dl.checkValidPath()) + { + dl.setSource (PartDownloader::PartsTracker); + dl.setPrimaryFile (file); + + for (QString const& unknown : unknowns) + dl.downloadFromPartsTracker (unknown); + + dl.exec(); + dl.checkIfFinished(); + file->reloadAllSubfiles(); + } + } +} + +// ============================================================================= +// +bool LDDocument::save (QString path, int64* sizeptr) +{ + if (isImplicit()) + return false; + + if (not path.length()) + path = fullPath(); + + // If the second object in the list holds the file name, update that now. + LDObjectPtr nameObject = getObject (1); + + if (nameObject != null and nameObject->type() == OBJ_Comment) + { + LDCommentPtr nameComment = nameObject.staticCast<LDComment>(); + + if (nameComment->text().left (6) == "Name: ") + { + QString newname = shortenName (path); + nameComment->setText (format ("Name: %1", newname)); + g_win->buildObjList(); + } + } + + QByteArray data; + + if (sizeptr != null) + *sizeptr = 0; + + // 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 (LDObjectPtr obj : objects()) + { + QByteArray subdata ((obj->asText() + "\r\n").toUtf8()); + data.append (subdata); + + if (sizeptr != null) + *sizeptr += subdata.size(); + } + + QFile f (path); + + if (not f.open (QIODevice::WriteOnly)) + return false; + + f.write (data); + f.close(); + + // We have successfully saved, update the save position now. + setSavePosition (history()->position()); + setFullPath (path); + setName (shortenName (path)); + + g_win->updateDocumentListItem (self().toStrongRef()); + g_win->updateTitle(); + return true; +} + +// ============================================================================= +// +void LDDocument::clear() +{ + for (LDObjectPtr obj : objects()) + forgetObject (obj); +} + +// ============================================================================= +// +static void CheckTokenCount (const QStringList& tokens, int num) +{ + if (tokens.size() != num) + throw QString (format ("Bad amount of tokens, expected %1, got %2", num, tokens.size())); +} + +// ============================================================================= +// +static void CheckTokenNumbers (const QStringList& tokens, int min, int max) +{ + bool ok; + + QRegExp scient ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+"); + + for (int i = min; i <= max; ++i) + { + // Check for floating point + tokens[i].toDouble (&ok); + if (ok) + return; + + // Check hex + if (tokens[i].startsWith ("0x")) + { + tokens[i].mid (2).toInt (&ok, 16); + + if (ok) + return; + } + + // Check scientific notation, e.g. 7.99361e-15 + if (scient.exactMatch (tokens[i])) + return; + + throw QString (format ("Token #%1 was `%2`, expected a number (matched length: %3)", + (i + 1), tokens[i], scient.matchedLength())); + } +} + +// ============================================================================= +// +static Vertex ParseVertex (QStringList& s, const int n) +{ + Vertex v; + v.apply ([&] (Axis ax, double& a) { a = s[n + ax].toDouble(); }); + return v; +} + +static int32 StringToNumber (QString a, bool* ok = null) +{ + int base = 10; + + if (a.startsWith ("0x")) + { + a.remove (0, 2); + base = 16; + } + + return a.toLong (ok, base); +} + +// ============================================================================= +// 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. +// ============================================================================= +LDObjectPtr ParseLine (QString line) +{ + try + { + QStringList tokens = line.split (" ", QString::SkipEmptyParts); + + if (tokens.size() <= 0) + { + // Line was empty, or only consisted of whitespace + return LDSpawn<LDEmpty>(); + } + + if (tokens[0].length() != 1 or not tokens[0][0].isDigit()) + throw QString ("Illogical line code"); + + int num = tokens[0][0].digitValue(); + + switch (num) + { + case 0: + { + // Comment + QString commentText (line.mid (line.indexOf ("0") + 2)); + QString commentTextSimplified (commentText.simplified()); + + // Handle BFC statements + if (tokens.size() > 2 and tokens[1] == "BFC") + { + for_enum (BFCStatement, i) + { + if (commentTextSimplified == format ("BFC %1", + LDBFC::StatementStrings[int (i)])) + { + return LDSpawn<LDBFC> (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. + if (commentTextSimplified == "BFC CERTIFY INVERTNEXT") + return LDSpawn<LDBFC> (BFCStatement::InvertNext); + elif (commentTextSimplified == "BFC CERTIFY CLIP") + return LDSpawn<LDBFC> (BFCStatement::Clip); + elif (commentTextSimplified == "BFC CERTIFY NOCLIP") + return LDSpawn<LDBFC> (BFCStatement::NoClip); + } + + if (tokens.size() > 2 and tokens[1] == "!LDFORGE") + { + // Handle LDForge-specific types, they're embedded into comments too + if (tokens[2] == "OVERLAY") + { + CheckTokenCount (tokens, 9); + CheckTokenNumbers (tokens, 5, 8); + + LDOverlayPtr obj = LDSpawn<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: + LDCommentPtr obj = LDSpawn<LDComment>(); + obj->setText (commentText); + return obj; + } + + case 1: + { + // Subfile + CheckTokenCount (tokens, 15); + CheckTokenNumbers (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; + LDDocumentPtr 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 (not load) + { + LDErrorPtr obj = LDSpawn<LDError> (line, format ("Could not open %1", tokens[14])); + obj->setFileReferenced (tokens[14]); + return obj; + } + + LDSubfilePtr obj = LDSpawn<LDSubfile>(); + obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1]))); + 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 (tokens, 8); + CheckTokenNumbers (tokens, 1, 7); + + // Line + LDLinePtr obj (LDSpawn<LDLine>()); + obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1]))); + + for (int i = 0; i < 2; ++i) + obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 7 + + return obj; + } + + case 3: + { + CheckTokenCount (tokens, 11); + CheckTokenNumbers (tokens, 1, 10); + + // Triangle + LDTrianglePtr obj (LDSpawn<LDTriangle>()); + obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1]))); + + for (int i = 0; i < 3; ++i) + obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 10 + + return obj; + } + + case 4: + case 5: + { + CheckTokenCount (tokens, 14); + CheckTokenNumbers (tokens, 1, 13); + + // Quadrilateral / Conditional line + LDObjectPtr obj; + + if (num == 4) + obj = LDSpawn<LDQuad>(); + else + obj = LDSpawn<LDCondLine>(); + + obj->setColor (LDColor::fromIndex (StringToNumber (tokens[1]))); + + for (int i = 0; i < 4; ++i) + obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 13 + + return obj; + } + + default: + throw QString ("Unknown line code number"); + } + } + catch (QString& e) + { + // Strange line we couldn't parse + return LDSpawn<LDError> (line, e); + } +} + +// ============================================================================= +// +LDDocumentPtr GetDocument (QString filename) +{ + // Try find the file in the list of loaded files + LDDocumentPtr doc = FindDocument (filename); + + // If it's not loaded, try open it + if (not doc) + doc = OpenDocument (filename, true, true); + + return doc; +} + +// ============================================================================= +// +void LDDocument::reloadAllSubfiles() +{ + print ("Reloading subfiles of %1", getDisplayName()); + + // Go through all objects in the current file and reload the subfiles + for (LDObjectPtr obj : objects()) + { + if (obj->type() == OBJ_Subfile) + { + LDSubfilePtr ref = obj.staticCast<LDSubfile>(); + LDDocumentPtr fileInfo = GetDocument (ref->fileInfo()->name()); + + if (fileInfo != null) + { + ref->setFileInfo (fileInfo); + } + else + { + ref->replace (LDSpawn<LDError> (ref->asText(), + format ("Could not open %1", ref->fileInfo()->name()))); + } + } + + // Reparse gibberish files. It could be that they are invalid because + // of loading errors. Circumstances may be different now. + if (obj->type() == OBJ_Error) + obj->replace (ParseLine (obj.staticCast<LDError>()->contents())); + } + + m_needsReCache = true; + + if (self() == CurrentDocument()) + g_win->buildObjList(); +} + +// ============================================================================= +// +int LDDocument::addObject (LDObjectPtr obj) +{ + history()->add (new AddHistory (objects().size(), obj)); + m_objects << obj; + addKnownVertices (obj); + obj->setDocument (self()); + g_win->R()->compileObject (obj); + return getObjectCount() - 1; +} + +// ============================================================================= +// +void LDDocument::addObjects (const LDObjectList& objs) +{ + for (LDObjectPtr obj : objs) + { + if (obj != null) + addObject (obj); + } +} + +// ============================================================================= +// +void LDDocument::insertObj (int pos, LDObjectPtr obj) +{ + history()->add (new AddHistory (pos, obj)); + m_objects.insert (pos, obj); + obj->setDocument (self()); + g_win->R()->compileObject (obj); + + +#ifdef DEBUG + if (not isImplicit()) + dprint ("Inserted object #%1 (%2) at %3\n", obj->id(), obj->typeName(), pos); +#endif +} + +// ============================================================================= +// +void LDDocument::addKnownVertices (LDObjectPtr obj) +{ + auto it = m_objectVertices.find (obj); + + if (it == m_objectVertices.end()) + it = m_objectVertices.insert (obj, QVector<Vertex>()); + else + it->clear(); + + obj->getVertices (*it); + needVertexMerge(); +} + +// ============================================================================= +// +void LDDocument::forgetObject (LDObjectPtr obj) +{ + int idx = obj->lineNumber(); + obj->deselect(); + assert (m_objects[idx] == obj); + + if (not isImplicit() and not (flags() & DOCF_IsBeingDestroyed)) + { + history()->add (new DelHistory (idx, obj)); + m_objectVertices.remove (obj); + } + + m_objects.removeAt (idx); + obj->setDocument (LDDocumentPtr()); +} + +// ============================================================================= +// +bool IsSafeToCloseAll() +{ + for (LDDocumentPtr f : LDDocument::explicitDocuments()) + { + if (not f->isSafeToClose()) + return false; + } + + return true; +} + +// ============================================================================= +// +void LDDocument::setObject (int idx, LDObjectPtr obj) +{ + assert (idx >= 0 and idx < m_objects.size()); + + // Mark this change to history + if (not m_history->isIgnoring()) + { + QString oldcode = getObject (idx)->asText(); + QString newcode = obj->asText(); + *m_history << new EditHistory (idx, oldcode, newcode); + } + + m_objectVertices.remove (m_objects[idx]); + m_objects[idx]->deselect(); + m_objects[idx]->setDocument (LDDocumentPtr()); + obj->setDocument (self()); + addKnownVertices (obj); + g_win->R()->compileObject (obj); + m_objects[idx] = obj; + needVertexMerge(); +} + +// ============================================================================= +// +LDObjectPtr LDDocument::getObject (int pos) const +{ + if (m_objects.size() <= pos) + return LDObjectPtr(); + + return m_objects[pos]; +} + +// ============================================================================= +// +int LDDocument::getObjectCount() const +{ + return objects().size(); +} + +// ============================================================================= +// +bool LDDocument::hasUnsavedChanges() const +{ + return not isImplicit() and history()->position() != savePosition(); +} + +// ============================================================================= +// +QString LDDocument::getDisplayName() +{ + if (not name().isEmpty()) + return name(); + + if (not defaultName().isEmpty()) + return "[" + defaultName() + "]"; + + return QObject::tr ("untitled"); +} + +// ============================================================================= +// +void LDDocument::initializeCachedData() +{ + if (m_needsReCache) + { + m_vertices.clear(); + + for (LDObjectPtr obj : inlineContents (true, true)) + { + if (obj->type() == OBJ_Subfile) + { + print ("Warning: unable to inline %1 into %2", + obj.staticCast<LDSubfile>()->fileInfo()->getDisplayName(), + getDisplayName()); + continue; + } + + LDPolygon* data = obj->getPolygon(); + + if (data != null) + { + m_polygonData << *data; + delete data; + } + } + + m_needsReCache = false; + } + + if (m_verticesOutdated) + { + m_objectVertices.clear(); + + for (LDObjectPtr obj : inlineContents (true, false)) + addKnownVertices (obj); + + mergeVertices(); + m_verticesOutdated = false; + } + + if (m_needVertexMerge) + mergeVertices(); +} + +// ============================================================================= +// +void LDDocument::mergeVertices() +{ + m_vertices.clear(); + + for (QVector<Vertex> const& verts : m_objectVertices) + m_vertices << verts; + + RemoveDuplicates (m_vertices); + m_needVertexMerge = false; +} + +// ============================================================================= +// +QList<LDPolygon> LDDocument::inlinePolygons() +{ + initializeCachedData(); + return polygonData(); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObjectList LDDocument::inlineContents (bool deep, bool renderinline) +{ + // Possibly substitute with logoed studs: + // stud.dat -> stud-logo.dat + // stud2.dat -> stud-logo2.dat + if (cfg::UseLogoStuds and renderinline) + { + // Ensure logoed studs are loaded first + LoadLogoStuds(); + + if (name() == "stud.dat" and g_logoedStud != null) + return g_logoedStud->inlineContents (deep, renderinline); + elif (name() == "stud2.dat" and g_logoedStud2 != null) + return g_logoedStud2->inlineContents (deep, renderinline); + } + + LDObjectList objs, objcache; + + for (LDObjectPtr obj : objects()) + { + // Skip those without scemantic meaning + if (not obj->isScemantic()) + continue; + + // Got another sub-file reference, inline it if we're deep-inlining. If not, + // just add it into the objects normally. Yay, recursion! + if (deep == true and obj->type() == OBJ_Subfile) + { + for (LDObjectPtr otherobj : obj.staticCast<LDSubfile>()->inlineContents (deep, renderinline)) + objs << otherobj; + } + else + objs << obj->createCopy(); + } + + return objs; +} + +// ============================================================================= +// +LDDocumentPtr LDDocument::current() +{ + return g_currentDocument; +} + +// ============================================================================= +// 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 (LDDocumentPtr f) +{ + // Implicit files were loaded for caching purposes and must never be set + // current. + if (f != null and f->isImplicit()) + return; + + g_currentDocument = f; + + if (g_win and f) + { + // A ton of stuff needs to be updated + g_win->updateDocumentListItem (f); + g_win->buildObjList(); + g_win->updateTitle(); + g_win->R()->setDocument (f); + g_win->R()->compiler()->needMerge(); + print ("Changed file to %1", f->getDisplayName()); + } +} + +// ============================================================================= +// +int LDDocument::countExplicitFiles() +{ + return g_explicitDocuments.size(); +} + +// ============================================================================= +// This little beauty closes the initial file that was open at first when opening +// a new file over it. +// ============================================================================= +void LDDocument::closeInitialFile() +{ + if (g_explicitDocuments.size() == 2 and + g_explicitDocuments[0]->name().isEmpty() and + not g_explicitDocuments[1]->name().isEmpty() and + not g_explicitDocuments[0]->hasUnsavedChanges()) + { + LDDocumentPtr filetoclose = g_explicitDocuments.first(); + filetoclose->dismiss(); + } +} + +// ============================================================================= +// +void LoadLogoStuds() +{ + if (g_loadingLogoedStuds or (g_logoedStud and g_logoedStud2)) + return; + + g_loadingLogoedStuds = true; + g_logoedStud = OpenDocument ("stud-logo.dat", true, true); + g_logoedStud2 = OpenDocument ("stud2-logo.dat", true, true); + print (QObject::tr ("Logoed studs loaded.\n")); + g_loadingLogoedStuds = false; +} + +// ============================================================================= +// +void LDDocument::addToSelection (LDObjectPtr obj) // [protected] +{ + if (obj->isSelected()) + return; + + assert (obj->document() == self()); + m_sel << obj; + g_win->R()->compileObject (obj); + obj->setSelected (true); +} + +// ============================================================================= +// +void LDDocument::removeFromSelection (LDObjectPtr obj) // [protected] +{ + if (not obj->isSelected()) + return; + + assert (obj->document() == self()); + m_sel.removeOne (obj); + g_win->R()->compileObject (obj); + obj->setSelected (false); +} + +// ============================================================================= +// +void LDDocument::clearSelection() +{ + for (LDObjectPtr obj : m_sel) + removeFromSelection (obj); + + assert (m_sel.isEmpty()); +} + +// ============================================================================= +// +const LDObjectList& LDDocument::getSelection() const +{ + return m_sel; +} + +// ============================================================================= +// +void LDDocument::swapObjects (LDObjectPtr one, LDObjectPtr other) +{ + int a = m_objects.indexOf (one); + int b = m_objects.indexOf (other); + assert (a != b and a != -1 and b != -1); + m_objects[b] = one; + m_objects[a] = other; + addToHistory (new SwapHistory (one->id(), other->id())); +} + +// ============================================================================= +// +QString LDDocument::shortenName (QString a) // [static] +{ + QString shortname = Basename (a); + QString topdirname = Basename (Dirname (a)); + + if (g_specialSubdirectories.contains (topdirname)) + shortname.prepend (topdirname + "\\"); + + return shortname; +} + +// ============================================================================= +// +QVector<Vertex> const& LDDocument::inlineVertices() +{ + initializeCachedData(); + return m_vertices; +} + +void LDDocument::redoVertices() +{ + m_verticesOutdated = true; +} + +void LDDocument::needVertexMerge() +{ + m_needVertexMerge = true; +}
--- a/src/ldObject.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,925 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "main.h" -#include "ldObject.h" -#include "ldDocument.h" -#include "miscallenous.h" -#include "mainWindow.h" -#include "editHistory.h" -#include "glRenderer.h" -#include "colors.h" -#include "glCompiler.h" - -CFGENTRY (String, DefaultName, "") -CFGENTRY (String, DefaultUser, "") -CFGENTRY (Bool, UseCALicense, true) - -// List of all LDObjects -QMap<long, LDObjectWeakPtr> g_allObjects; -static int32 g_idcursor = 1; // 0 shalt be null -static constexpr int32 g_maxID = (1 << 24); - -#define LDOBJ_DEFAULT_CTOR(T,BASE) \ - T :: T (LDObjectPtr* selfptr) : \ - BASE (selfptr) {} - -// ============================================================================= -// LDObject constructors -// -LDObject::LDObject (LDObjectPtr* selfptr) : - m_isHidden (false), - m_isSelected (false), - m_isDestructed (false), - qObjListEntry (null) -{ - *selfptr = LDObjectPtr (this, [](LDObject* obj){ obj->finalDelete(); }); - memset (m_coords, 0, sizeof m_coords); - m_self = selfptr->toWeakRef(); - chooseID(); - g_allObjects[id()] = self(); - setRandomColor (QColor::fromHsv (rand() % 360, rand() % 256, rand() % 96 + 128)); -} - -LDSubfile::LDSubfile (LDObjectPtr* selfptr) : - LDMatrixObject (selfptr) {} - -LDOBJ_DEFAULT_CTOR (LDEmpty, LDObject) -LDOBJ_DEFAULT_CTOR (LDError, LDObject) -LDOBJ_DEFAULT_CTOR (LDLine, LDObject) -LDOBJ_DEFAULT_CTOR (LDTriangle, LDObject) -LDOBJ_DEFAULT_CTOR (LDCondLine, LDLine) -LDOBJ_DEFAULT_CTOR (LDQuad, LDObject) -LDOBJ_DEFAULT_CTOR (LDOverlay, LDObject) -LDOBJ_DEFAULT_CTOR (LDBFC, LDObject) -LDOBJ_DEFAULT_CTOR (LDComment, LDObject) - -// ============================================================================= -// -void LDObject::chooseID() -{ - // If this is the first pass we can just use a global ID counter for each - // unique object. Let's hope that nobody goes to create 17 million objects - // anytime soon. - if (g_idcursor < g_maxID) - { - setID (g_idcursor++); - return; - } - - // In case someone does, we cannot really continue execution. We must abort, - // give the user a chance to save their documents though. - Critical ("Created too many objects. Execution cannot continue. You have a " - "chance to save any changes to documents, then restart."); - (void) IsSafeToCloseAll(); - Exit(); -} - -// ============================================================================= -// -void LDObject::setVertexCoord (int i, Axis ax, double value) -{ - Vertex v = vertex (i); - v.setCoordinate (ax, value); - setVertex (i, v); -} - -// ============================================================================= -// -QString LDComment::asText() const -{ - return format ("0 %1", text()); -} - -// ============================================================================= -// -QString LDSubfile::asText() const -{ - QString val = format ("1 %1 %2 ", color(), position()); - val += transform().toString(); - val += ' '; - val += fileInfo()->name(); - return val; -} - -// ============================================================================= -// -QString LDLine::asText() const -{ - QString val = format ("2 %1", color()); - - for (int i = 0; i < 2; ++i) - val += format (" %1", vertex (i)); - - return val; -} - -// ============================================================================= -// -QString LDTriangle::asText() const -{ - QString val = format ("3 %1", color()); - - for (int i = 0; i < 3; ++i) - val += format (" %1", vertex (i)); - - return val; -} - -// ============================================================================= -// -QString LDQuad::asText() const -{ - QString val = format ("4 %1", color()); - - for (int i = 0; i < 4; ++i) - val += format (" %1", vertex (i)); - - return val; -} - -// ============================================================================= -// -QString LDCondLine::asText() const -{ - QString val = format ("5 %1", color()); - - // Add the coordinates - for (int i = 0; i < 4; ++i) - val += format (" %1", vertex (i)); - - return val; -} - -// ============================================================================= -// -QString LDError::asText() const -{ - return contents(); -} - -// ============================================================================= -// -QString LDEmpty::asText() const -{ - return ""; -} - -// ============================================================================= -// -const char* LDBFC::StatementStrings[] = -{ - "CERTIFY CCW", - "CCW", - "CERTIFY CW", - "CW", - "NOCERTIFY", - "INVERTNEXT", - "CLIP", - "CLIP CCW", - "CLIP CW", - "NOCLIP", -}; - -QString LDBFC::asText() const -{ - return format ("0 BFC %1", StatementStrings[int (m_statement)]); -} - -// ============================================================================= -// -QList<LDTrianglePtr> LDQuad::splitToTriangles() -{ - // Create the two triangles based on this quadrilateral: - // 0---3 0---3 3 - // | | | / /| - // | | ==> | / / | - // | | |/ / | - // 1---2 1 1---2 - LDTrianglePtr tri1 (LDSpawn<LDTriangle> (vertex (0), vertex (1), vertex (3))); - LDTrianglePtr tri2 (LDSpawn<LDTriangle> (vertex (1), vertex (2), vertex (3))); - - // The triangles also inherit the quad's color - tri1->setColor (color()); - tri2->setColor (color()); - - return {tri1, tri2}; -} - -// ============================================================================= -// -void LDObject::replace (LDObjectPtr other) -{ - long idx = lineNumber(); - assert (idx != -1); - - // Replace the instance of the old object with the new object - document().toStrongRef()->setObject (idx, other); - - // Remove the old object - destroy(); -} - -// ============================================================================= -// -void LDObject::swap (LDObjectPtr other) -{ - assert (document() == other->document()); - document().toStrongRef()->swapObjects (self(), other); -} - -// ============================================================================= -// -LDLine::LDLine (LDObjectPtr* selfptr, Vertex v1, Vertex v2) : - LDObject (selfptr) -{ - setVertex (0, v1); - setVertex (1, v2); -} - -// ============================================================================= -// -LDTriangle::LDTriangle (LDObjectPtr* selfptr, const Vertex& v1, const Vertex& v2, const Vertex& v3) : - LDObject (selfptr) -{ - setVertex (0, v1); - setVertex (1, v2); - setVertex (2, v3); -} - -// ============================================================================= -// -LDQuad::LDQuad (LDObjectPtr* selfptr, const Vertex& v1, const Vertex& v2, - const Vertex& v3, const Vertex& v4) : - LDObject (selfptr) -{ - setVertex (0, v1); - setVertex (1, v2); - setVertex (2, v3); - setVertex (3, v4); -} - -// ============================================================================= -// -LDCondLine::LDCondLine (LDObjectPtr* selfptr, const Vertex& v0, const Vertex& v1, - const Vertex& v2, const Vertex& v3) : - LDLine (selfptr) -{ - setVertex (0, v0); - setVertex (1, v1); - setVertex (2, v2); - setVertex (3, v3); -} - -// ============================================================================= -// -LDObject::~LDObject() {} - -// ============================================================================= -// -void LDObject::destroy() -{ - // Don't bother during program termination - if (IsExiting() or isDestructed()) - return; - - // If this object was selected, unselect it now - if (isSelected() and document() != null) - deselect(); - - // If this object was associated to a file, remove it off it now - if (document() != null) - document().toStrongRef()->forgetObject (self()); - - // Delete the GL lists - if (g_win != null) - g_win->R()->forgetObject (self()); - - // Remove this object from the list of LDObjects - g_allObjects.erase (g_allObjects.find (id())); - setDestructed (true); -} - -// -// Deletes the object. Only the shared pointer is to call this! -// -void LDObject::finalDelete() -{ - if (not isDestructed()) - destroy(); - - delete this; -} - -// ============================================================================= -// -void LDObject::setDocument (const LDDocumentWeakPtr& a) -{ - m_document = a; - - if (a == null) - setSelected (false); -} - -// ============================================================================= -// -static void TransformObject (LDObjectPtr obj, Matrix transform, Vertex pos, LDColor parentcolor) -{ - switch (obj->type()) - { - case OBJ_Line: - case OBJ_CondLine: - case OBJ_Triangle: - case OBJ_Quad: - for (int i = 0; i < obj->numVertices(); ++i) - { - Vertex v = obj->vertex (i); - v.transform (transform, pos); - obj->setVertex (i, v); - } - - break; - - case OBJ_Subfile: - { - LDSubfilePtr ref = qSharedPointerCast<LDSubfile> (obj); - Matrix newMatrix = transform * ref->transform(); - Vertex newpos = ref->position(); - newpos.transform (transform, pos); - ref->setPosition (newpos); - ref->setTransform (newMatrix); - break; - } - - default: - break; - } - - if (obj->color() == MainColor()) - obj->setColor (parentcolor); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -LDObjectList LDSubfile::inlineContents (bool deep, bool render) -{ - LDObjectList objs = fileInfo()->inlineContents (deep, render); - - // Transform the objects - for (LDObjectPtr obj : objs) - { - // assert (obj->type() != OBJ_Subfile); - // Set the parent now so we know what inlined the object. - obj->setParent (self()); - TransformObject (obj, transform(), position(), color()); - } - - return objs; -} - -// ============================================================================= -// -LDPolygon* LDObject::getPolygon() -{ - LDObjectType ot = type(); - int num = (ot == OBJ_Line) ? 2 - : (ot == OBJ_Triangle) ? 3 - : (ot == OBJ_Quad) ? 4 - : (ot == OBJ_CondLine) ? 5 - : 0; - - if (num == 0) - return null; - - LDPolygon* data = new LDPolygon; - data->id = id(); - data->num = num; - data->color = color().index(); - - for (int i = 0; i < data->numVertices(); ++i) - data->vertices[i] = vertex (i); - - return data; -} - -// ============================================================================= -// -QList<LDPolygon> LDSubfile::inlinePolygons() -{ - QList<LDPolygon> data = fileInfo()->inlinePolygons(); - - for (LDPolygon& entry : data) - { - for (int i = 0; i < entry.numVertices(); ++i) - entry.vertices[i].transform (transform(), position()); - } - - return data; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -long LDObject::lineNumber() const -{ - assert (document() != null); - - for (int i = 0; i < document().toStrongRef()->getObjectCount(); ++i) - { - if (document().toStrongRef()->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. - long const start = up ? 0 : (objs.size() - 1); - long const end = up ? objs.size() : -1; - long const incr = up ? 1 : -1; - LDObjectList objsToCompile; - LDDocumentPtr file = objs[0]->document(); - - for (long i = start; i != end; i += incr) - { - LDObjectPtr obj = objs[i]; - - long const idx = obj->lineNumber(); - long const target = idx + (up ? -1 : 1); - - if ((up and idx == 0) or (not up and idx == (long) file->objects().size() - 1l)) - { - // 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 (LDObjectPtr obj : objsToCompile) - g_win->R()->compileObject (obj); -} - -// ============================================================================= -// -QString LDObject::typeName (LDObjectType type) -{ - return LDObject::getDefault (type)->typeName(); -} - -// ============================================================================= -// -QString LDObject::describeObjects (const LDObjectList& objs) -{ - QString text; - - if (objs.isEmpty()) - return "nothing"; // :) - - for (LDObjectType objType = OBJ_FirstType; objType < OBJ_NumTypes; ++objType) - { - int count = 0; - - for (LDObjectPtr obj : objs) - { - if (obj->type() == objType) - count++; - } - - if (count == 0) - continue; - - if (not text.isEmpty()) - text += ", "; - - QString noun = format ("%1%2", typeName (objType), Plural (count)); - text += format ("%1 %2", count, noun); - } - - return text; -} - -// ============================================================================= -// -LDObjectPtr LDObject::topLevelParent() -{ - if (parent() == null) - return self(); - - LDObjectWeakPtr it (self()); - - while (it.toStrongRef()->parent() != null) - it = it.toStrongRef()->parent(); - - return it.toStrongRef(); -} - -// ============================================================================= -// -LDObjectPtr LDObject::next() const -{ - long idx = lineNumber(); - assert (idx != -1); - - if (idx == (long) document().toStrongRef()->getObjectCount() - 1) - return LDObjectPtr(); - - return document().toStrongRef()->getObject (idx + 1); -} - -// ============================================================================= -// -LDObjectPtr LDObject::previous() const -{ - long idx = lineNumber(); - assert (idx != -1); - - if (idx == 0) - return LDObjectPtr(); - - return document().toStrongRef()->getObject (idx - 1); -} - -// ============================================================================= -// -bool LDObject::previousIsInvertnext (LDBFCPtr& ptr) -{ - LDObjectPtr prev (previous()); - - if (prev != null and prev->type() == OBJ_BFC and - prev.staticCast<LDBFC>()->statement() == BFCStatement::InvertNext) - { - ptr = prev.staticCast<LDBFC>(); - return true; - } - - return false; -} - -// ============================================================================= -// -void LDObject::move (Vertex vect) -{ - if (hasMatrix()) - { - LDMatrixObjectPtr mo = self().toStrongRef().dynamicCast<LDMatrixObject>(); - mo->setPosition (mo->position() + vect); - } - else - { - for (int i = 0; i < numVertices(); ++i) - setVertex (i, vertex (i) + vect); - } -} - -// ============================================================================= -// -LDObjectPtr LDObject::getDefault (const LDObjectType type) -{ - switch (type) - { - case OBJ_Comment: return LDSpawn<LDComment>(); - case OBJ_BFC: return LDSpawn<LDBFC>(); - case OBJ_Line: return LDSpawn<LDLine>(); - case OBJ_CondLine: return LDSpawn<LDCondLine>(); - case OBJ_Subfile: return LDSpawn<LDSubfile>(); - case OBJ_Triangle: return LDSpawn<LDTriangle>(); - case OBJ_Quad: return LDSpawn<LDQuad>(); - case OBJ_Empty: return LDSpawn<LDEmpty>(); - case OBJ_Error: return LDSpawn<LDError>(); - case OBJ_Overlay: return LDSpawn<LDOverlay>(); - case OBJ_NumTypes: assert (false); - } - return LDObjectPtr(); -} - -// ============================================================================= -// -void LDObject::invert() {} -void LDBFC::invert() {} -void LDEmpty::invert() {} -void LDComment::invert() {} -void LDError::invert() {} - -// ============================================================================= -// -void LDTriangle::invert() -{ - // Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1. - // Thus, we swap 1 and 2. - Vertex tmp = vertex (1); - setVertex (1, vertex (2)); - setVertex (2, tmp); - - return; -} - -// ============================================================================= -// -void LDQuad::invert() -{ - // Quad: 0 -> 1 -> 2 -> 3 - // rev: 0 -> 3 -> 2 -> 1 - // Thus, we swap 1 and 3. - Vertex tmp = vertex (1); - setVertex (1, vertex (3)); - setVertex (3, tmp); -} - -// ============================================================================= -// -void LDSubfile::invert() -{ - if (document() == null) - return; - - // Check whether subfile is flat - int axisSet = (1 << X) | (1 << Y) | (1 << Z); - LDObjectList objs = fileInfo()->inlineContents (true, false); - - for (LDObjectPtr obj : objs) - { - for (int i = 0; i < obj->numVertices(); ++i) - { - Vertex const& vrt = obj->vertex (i); - - if (axisSet & (1 << X) and vrt.x() != 0.0) - axisSet &= ~(1 << X); - - if (axisSet & (1 << Y) and vrt.y() != 0.0) - axisSet &= ~(1 << Y); - - if (axisSet & (1 << Z) and vrt.z() != 0.0) - axisSet &= ~(1 << Z); - } - - if (axisSet == 0) - break; - } - - if (axisSet != 0) - { - // Subfile has all vertices zero on one specific plane, so it is flat. - // Let's flip it. - Matrix matrixModifier = IdentityMatrix; - - if (axisSet & (1 << X)) - matrixModifier[0] = -1; - - if (axisSet & (1 << Y)) - matrixModifier[4] = -1; - - if (axisSet & (1 << Z)) - matrixModifier[8] = -1; - - setTransform (transform() * matrixModifier); - return; - } - - // Subfile is not flat. Resort to invertnext. - int idx = lineNumber(); - - if (idx > 0) - { - LDBFCPtr bfc = previous().dynamicCast<LDBFC>(); - - if (not bfc.isNull() and bfc->statement() == BFCStatement::InvertNext) - { - // This is prefixed with an invertnext, thus remove it. - bfc->destroy(); - return; - } - } - - // Not inverted, thus prefix it with a new invertnext. - document().toStrongRef()->insertObj (idx, LDSpawn<LDBFC> (BFCStatement::InvertNext)); -} - -// ============================================================================= -// -void LDLine::invert() -{ - // For lines, we swap the vertices. - Vertex tmp = vertex (0); - setVertex (0, vertex (1)); - setVertex (1, tmp); -} - -// ============================================================================= -// -void LDCondLine::invert() -{ - // I don't think that a conditional line's control points need to be - // swapped, do they? - Vertex tmp = vertex (0); - setVertex (0, vertex (1)); - setVertex (1, tmp); -} - -// ============================================================================= -// -LDLinePtr LDCondLine::toEdgeLine() -{ - LDLinePtr replacement (LDSpawn<LDLine>()); - - for (int i = 0; i < replacement->numVertices(); ++i) - replacement->setVertex (i, vertex (i)); - - replacement->setColor (color()); - - replace (replacement); - return replacement; -} - -// ============================================================================= -// -LDObjectPtr LDObject::fromID (int id) -{ - auto it = g_allObjects.find (id); - - if (it != g_allObjects.end()) - return *it; - - return LDObjectPtr(); -} - -// ============================================================================= -// -QString LDOverlay::asText() const -{ - return format ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6", - fileName(), camera(), x(), y(), width(), height()); -} - -void LDOverlay::invert() {} - -// ============================================================================= -// -// Hook the set accessors of certain properties to this changeProperty function. -// It takes care of history management so we can capture low-level changes, this -// makes history stuff work out of the box. -// -template<typename T> -static void changeProperty (LDObjectPtr obj, T* ptr, const T& val) -{ - long idx; - - if (*ptr == val) - return; - - if (obj->document() != null and (idx = obj->lineNumber()) != -1) - { - QString before = obj->asText(); - *ptr = val; - QString after = obj->asText(); - - if (before != after) - { - obj->document().toStrongRef()->addToHistory (new EditHistory (idx, before, after)); - g_win->R()->compileObject (obj); - CurrentDocument()->redoVertices(); - } - } - else - *ptr = val; -} - -// ============================================================================= -// -void LDObject::setColor (LDColor const& val) -{ - changeProperty (self(), &m_color, val); -} - -// ============================================================================= -// -const Vertex& LDObject::vertex (int i) const -{ - return m_coords[i]; -} - -// ============================================================================= -// -void LDObject::setVertex (int i, const Vertex& vert) -{ - changeProperty (self(), &m_coords[i], vert); -} - -// ============================================================================= -// -void LDMatrixObject::setPosition (const Vertex& a) -{ - changeProperty (self(), &m_position, a); -} - -// ============================================================================= -// -void LDMatrixObject::setTransform (const Matrix& val) -{ - changeProperty (self(), &m_transform, val); -} - -// ============================================================================= -// -void LDObject::select() -{ - assert (document() != null); - document().toStrongRef()->addToSelection (self()); - - // If this object is inverted with INVERTNEXT, pick the INVERTNEXT as well. - /* - LDBFCPtr invertnext; - - if (previousIsInvertnext (invertnext)) - invertnext->select(); - */ -} - -// ============================================================================= -// -void LDObject::deselect() -{ - assert (document() != null); - document().toStrongRef()->removeFromSelection (self()); - - // If this object is inverted with INVERTNEXT, deselect the INVERTNEXT as well. - LDBFCPtr invertnext; - - if (previousIsInvertnext (invertnext)) - invertnext->deselect(); -} - -// ============================================================================= -// -QString PreferredLicenseText() -{ - return (cfg::UseCALicense ? CALicenseText : ""); -} - -// ============================================================================= -// -LDObjectPtr LDObject::createCopy() const -{ - LDObjectPtr copy = ParseLine (asText()); - return copy; -} - -// ============================================================================= -// -void LDSubfile::setFileInfo (const LDDocumentPtr& a) -{ - changeProperty (self(), &m_fileInfo, a); - - // If it's an immediate subfile reference (i.e. this subfile belongs in an - // explicit file), we need to pre-compile the GL polygons for the document - // if they don't exist already. - if (a != null and - a->isImplicit() == false and - a->polygonData().isEmpty()) - { - a->initializeCachedData(); - } -}; - -void LDObject::getVertices (QVector<Vertex>& verts) const -{ - for (int i = 0; i < numVertices(); ++i) - verts << vertex (i); -} - -void LDSubfile::getVertices (QVector<Vertex>& verts) const -{ - verts << fileInfo()->inlineVertices(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldObject.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,925 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "main.h" +#include "ldObject.h" +#include "ldDocument.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "editHistory.h" +#include "glRenderer.h" +#include "colors.h" +#include "glCompiler.h" + +CFGENTRY (String, DefaultName, "") +CFGENTRY (String, DefaultUser, "") +CFGENTRY (Bool, UseCALicense, true) + +// List of all LDObjects +QMap<long, LDObjectWeakPtr> g_allObjects; +static int32 g_idcursor = 1; // 0 shalt be null +static constexpr int32 g_maxID = (1 << 24); + +#define LDOBJ_DEFAULT_CTOR(T,BASE) \ + T :: T (LDObjectPtr* selfptr) : \ + BASE (selfptr) {} + +// ============================================================================= +// LDObject constructors +// +LDObject::LDObject (LDObjectPtr* selfptr) : + m_isHidden (false), + m_isSelected (false), + m_isDestructed (false), + qObjListEntry (null) +{ + *selfptr = LDObjectPtr (this, [](LDObject* obj){ obj->finalDelete(); }); + memset (m_coords, 0, sizeof m_coords); + m_self = selfptr->toWeakRef(); + chooseID(); + g_allObjects[id()] = self(); + setRandomColor (QColor::fromHsv (rand() % 360, rand() % 256, rand() % 96 + 128)); +} + +LDSubfile::LDSubfile (LDObjectPtr* selfptr) : + LDMatrixObject (selfptr) {} + +LDOBJ_DEFAULT_CTOR (LDEmpty, LDObject) +LDOBJ_DEFAULT_CTOR (LDError, LDObject) +LDOBJ_DEFAULT_CTOR (LDLine, LDObject) +LDOBJ_DEFAULT_CTOR (LDTriangle, LDObject) +LDOBJ_DEFAULT_CTOR (LDCondLine, LDLine) +LDOBJ_DEFAULT_CTOR (LDQuad, LDObject) +LDOBJ_DEFAULT_CTOR (LDOverlay, LDObject) +LDOBJ_DEFAULT_CTOR (LDBFC, LDObject) +LDOBJ_DEFAULT_CTOR (LDComment, LDObject) + +// ============================================================================= +// +void LDObject::chooseID() +{ + // If this is the first pass we can just use a global ID counter for each + // unique object. Let's hope that nobody goes to create 17 million objects + // anytime soon. + if (g_idcursor < g_maxID) + { + setID (g_idcursor++); + return; + } + + // In case someone does, we cannot really continue execution. We must abort, + // give the user a chance to save their documents though. + Critical ("Created too many objects. Execution cannot continue. You have a " + "chance to save any changes to documents, then restart."); + (void) IsSafeToCloseAll(); + Exit(); +} + +// ============================================================================= +// +void LDObject::setVertexCoord (int i, Axis ax, double value) +{ + Vertex v = vertex (i); + v.setCoordinate (ax, value); + setVertex (i, v); +} + +// ============================================================================= +// +QString LDComment::asText() const +{ + return format ("0 %1", text()); +} + +// ============================================================================= +// +QString LDSubfile::asText() const +{ + QString val = format ("1 %1 %2 ", color(), position()); + val += transform().toString(); + val += ' '; + val += fileInfo()->name(); + return val; +} + +// ============================================================================= +// +QString LDLine::asText() const +{ + QString val = format ("2 %1", color()); + + for (int i = 0; i < 2; ++i) + val += format (" %1", vertex (i)); + + return val; +} + +// ============================================================================= +// +QString LDTriangle::asText() const +{ + QString val = format ("3 %1", color()); + + for (int i = 0; i < 3; ++i) + val += format (" %1", vertex (i)); + + return val; +} + +// ============================================================================= +// +QString LDQuad::asText() const +{ + QString val = format ("4 %1", color()); + + for (int i = 0; i < 4; ++i) + val += format (" %1", vertex (i)); + + return val; +} + +// ============================================================================= +// +QString LDCondLine::asText() const +{ + QString val = format ("5 %1", color()); + + // Add the coordinates + for (int i = 0; i < 4; ++i) + val += format (" %1", vertex (i)); + + return val; +} + +// ============================================================================= +// +QString LDError::asText() const +{ + return contents(); +} + +// ============================================================================= +// +QString LDEmpty::asText() const +{ + return ""; +} + +// ============================================================================= +// +const char* LDBFC::StatementStrings[] = +{ + "CERTIFY CCW", + "CCW", + "CERTIFY CW", + "CW", + "NOCERTIFY", + "INVERTNEXT", + "CLIP", + "CLIP CCW", + "CLIP CW", + "NOCLIP", +}; + +QString LDBFC::asText() const +{ + return format ("0 BFC %1", StatementStrings[int (m_statement)]); +} + +// ============================================================================= +// +QList<LDTrianglePtr> LDQuad::splitToTriangles() +{ + // Create the two triangles based on this quadrilateral: + // 0---3 0---3 3 + // | | | / /| + // | | ==> | / / | + // | | |/ / | + // 1---2 1 1---2 + LDTrianglePtr tri1 (LDSpawn<LDTriangle> (vertex (0), vertex (1), vertex (3))); + LDTrianglePtr tri2 (LDSpawn<LDTriangle> (vertex (1), vertex (2), vertex (3))); + + // The triangles also inherit the quad's color + tri1->setColor (color()); + tri2->setColor (color()); + + return {tri1, tri2}; +} + +// ============================================================================= +// +void LDObject::replace (LDObjectPtr other) +{ + long idx = lineNumber(); + assert (idx != -1); + + // Replace the instance of the old object with the new object + document().toStrongRef()->setObject (idx, other); + + // Remove the old object + destroy(); +} + +// ============================================================================= +// +void LDObject::swap (LDObjectPtr other) +{ + assert (document() == other->document()); + document().toStrongRef()->swapObjects (self(), other); +} + +// ============================================================================= +// +LDLine::LDLine (LDObjectPtr* selfptr, Vertex v1, Vertex v2) : + LDObject (selfptr) +{ + setVertex (0, v1); + setVertex (1, v2); +} + +// ============================================================================= +// +LDTriangle::LDTriangle (LDObjectPtr* selfptr, const Vertex& v1, const Vertex& v2, const Vertex& v3) : + LDObject (selfptr) +{ + setVertex (0, v1); + setVertex (1, v2); + setVertex (2, v3); +} + +// ============================================================================= +// +LDQuad::LDQuad (LDObjectPtr* selfptr, const Vertex& v1, const Vertex& v2, + const Vertex& v3, const Vertex& v4) : + LDObject (selfptr) +{ + setVertex (0, v1); + setVertex (1, v2); + setVertex (2, v3); + setVertex (3, v4); +} + +// ============================================================================= +// +LDCondLine::LDCondLine (LDObjectPtr* selfptr, const Vertex& v0, const Vertex& v1, + const Vertex& v2, const Vertex& v3) : + LDLine (selfptr) +{ + setVertex (0, v0); + setVertex (1, v1); + setVertex (2, v2); + setVertex (3, v3); +} + +// ============================================================================= +// +LDObject::~LDObject() {} + +// ============================================================================= +// +void LDObject::destroy() +{ + // Don't bother during program termination + if (IsExiting() or isDestructed()) + return; + + // If this object was selected, unselect it now + if (isSelected() and document() != null) + deselect(); + + // If this object was associated to a file, remove it off it now + if (document() != null) + document().toStrongRef()->forgetObject (self()); + + // Delete the GL lists + if (g_win != null) + g_win->R()->forgetObject (self()); + + // Remove this object from the list of LDObjects + g_allObjects.erase (g_allObjects.find (id())); + setDestructed (true); +} + +// +// Deletes the object. Only the shared pointer is to call this! +// +void LDObject::finalDelete() +{ + if (not isDestructed()) + destroy(); + + delete this; +} + +// ============================================================================= +// +void LDObject::setDocument (const LDDocumentWeakPtr& a) +{ + m_document = a; + + if (a == null) + setSelected (false); +} + +// ============================================================================= +// +static void TransformObject (LDObjectPtr obj, Matrix transform, Vertex pos, LDColor parentcolor) +{ + switch (obj->type()) + { + case OBJ_Line: + case OBJ_CondLine: + case OBJ_Triangle: + case OBJ_Quad: + for (int i = 0; i < obj->numVertices(); ++i) + { + Vertex v = obj->vertex (i); + v.transform (transform, pos); + obj->setVertex (i, v); + } + + break; + + case OBJ_Subfile: + { + LDSubfilePtr ref = qSharedPointerCast<LDSubfile> (obj); + Matrix newMatrix = transform * ref->transform(); + Vertex newpos = ref->position(); + newpos.transform (transform, pos); + ref->setPosition (newpos); + ref->setTransform (newMatrix); + break; + } + + default: + break; + } + + if (obj->color() == MainColor()) + obj->setColor (parentcolor); +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +LDObjectList LDSubfile::inlineContents (bool deep, bool render) +{ + LDObjectList objs = fileInfo()->inlineContents (deep, render); + + // Transform the objects + for (LDObjectPtr obj : objs) + { + // assert (obj->type() != OBJ_Subfile); + // Set the parent now so we know what inlined the object. + obj->setParent (self()); + TransformObject (obj, transform(), position(), color()); + } + + return objs; +} + +// ============================================================================= +// +LDPolygon* LDObject::getPolygon() +{ + LDObjectType ot = type(); + int num = (ot == OBJ_Line) ? 2 + : (ot == OBJ_Triangle) ? 3 + : (ot == OBJ_Quad) ? 4 + : (ot == OBJ_CondLine) ? 5 + : 0; + + if (num == 0) + return null; + + LDPolygon* data = new LDPolygon; + data->id = id(); + data->num = num; + data->color = color().index(); + + for (int i = 0; i < data->numVertices(); ++i) + data->vertices[i] = vertex (i); + + return data; +} + +// ============================================================================= +// +QList<LDPolygon> LDSubfile::inlinePolygons() +{ + QList<LDPolygon> data = fileInfo()->inlinePolygons(); + + for (LDPolygon& entry : data) + { + for (int i = 0; i < entry.numVertices(); ++i) + entry.vertices[i].transform (transform(), position()); + } + + return data; +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +long LDObject::lineNumber() const +{ + assert (document() != null); + + for (int i = 0; i < document().toStrongRef()->getObjectCount(); ++i) + { + if (document().toStrongRef()->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. + long const start = up ? 0 : (objs.size() - 1); + long const end = up ? objs.size() : -1; + long const incr = up ? 1 : -1; + LDObjectList objsToCompile; + LDDocumentPtr file = objs[0]->document(); + + for (long i = start; i != end; i += incr) + { + LDObjectPtr obj = objs[i]; + + long const idx = obj->lineNumber(); + long const target = idx + (up ? -1 : 1); + + if ((up and idx == 0) or (not up and idx == (long) file->objects().size() - 1l)) + { + // 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 (LDObjectPtr obj : objsToCompile) + g_win->R()->compileObject (obj); +} + +// ============================================================================= +// +QString LDObject::typeName (LDObjectType type) +{ + return LDObject::getDefault (type)->typeName(); +} + +// ============================================================================= +// +QString LDObject::describeObjects (const LDObjectList& objs) +{ + QString text; + + if (objs.isEmpty()) + return "nothing"; // :) + + for (LDObjectType objType = OBJ_FirstType; objType < OBJ_NumTypes; ++objType) + { + int count = 0; + + for (LDObjectPtr obj : objs) + { + if (obj->type() == objType) + count++; + } + + if (count == 0) + continue; + + if (not text.isEmpty()) + text += ", "; + + QString noun = format ("%1%2", typeName (objType), Plural (count)); + text += format ("%1 %2", count, noun); + } + + return text; +} + +// ============================================================================= +// +LDObjectPtr LDObject::topLevelParent() +{ + if (parent() == null) + return self(); + + LDObjectWeakPtr it (self()); + + while (it.toStrongRef()->parent() != null) + it = it.toStrongRef()->parent(); + + return it.toStrongRef(); +} + +// ============================================================================= +// +LDObjectPtr LDObject::next() const +{ + long idx = lineNumber(); + assert (idx != -1); + + if (idx == (long) document().toStrongRef()->getObjectCount() - 1) + return LDObjectPtr(); + + return document().toStrongRef()->getObject (idx + 1); +} + +// ============================================================================= +// +LDObjectPtr LDObject::previous() const +{ + long idx = lineNumber(); + assert (idx != -1); + + if (idx == 0) + return LDObjectPtr(); + + return document().toStrongRef()->getObject (idx - 1); +} + +// ============================================================================= +// +bool LDObject::previousIsInvertnext (LDBFCPtr& ptr) +{ + LDObjectPtr prev (previous()); + + if (prev != null and prev->type() == OBJ_BFC and + prev.staticCast<LDBFC>()->statement() == BFCStatement::InvertNext) + { + ptr = prev.staticCast<LDBFC>(); + return true; + } + + return false; +} + +// ============================================================================= +// +void LDObject::move (Vertex vect) +{ + if (hasMatrix()) + { + LDMatrixObjectPtr mo = self().toStrongRef().dynamicCast<LDMatrixObject>(); + mo->setPosition (mo->position() + vect); + } + else + { + for (int i = 0; i < numVertices(); ++i) + setVertex (i, vertex (i) + vect); + } +} + +// ============================================================================= +// +LDObjectPtr LDObject::getDefault (const LDObjectType type) +{ + switch (type) + { + case OBJ_Comment: return LDSpawn<LDComment>(); + case OBJ_BFC: return LDSpawn<LDBFC>(); + case OBJ_Line: return LDSpawn<LDLine>(); + case OBJ_CondLine: return LDSpawn<LDCondLine>(); + case OBJ_Subfile: return LDSpawn<LDSubfile>(); + case OBJ_Triangle: return LDSpawn<LDTriangle>(); + case OBJ_Quad: return LDSpawn<LDQuad>(); + case OBJ_Empty: return LDSpawn<LDEmpty>(); + case OBJ_Error: return LDSpawn<LDError>(); + case OBJ_Overlay: return LDSpawn<LDOverlay>(); + case OBJ_NumTypes: assert (false); + } + return LDObjectPtr(); +} + +// ============================================================================= +// +void LDObject::invert() {} +void LDBFC::invert() {} +void LDEmpty::invert() {} +void LDComment::invert() {} +void LDError::invert() {} + +// ============================================================================= +// +void LDTriangle::invert() +{ + // Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1. + // Thus, we swap 1 and 2. + Vertex tmp = vertex (1); + setVertex (1, vertex (2)); + setVertex (2, tmp); + + return; +} + +// ============================================================================= +// +void LDQuad::invert() +{ + // Quad: 0 -> 1 -> 2 -> 3 + // rev: 0 -> 3 -> 2 -> 1 + // Thus, we swap 1 and 3. + Vertex tmp = vertex (1); + setVertex (1, vertex (3)); + setVertex (3, tmp); +} + +// ============================================================================= +// +void LDSubfile::invert() +{ + if (document() == null) + return; + + // Check whether subfile is flat + int axisSet = (1 << X) | (1 << Y) | (1 << Z); + LDObjectList objs = fileInfo()->inlineContents (true, false); + + for (LDObjectPtr obj : objs) + { + for (int i = 0; i < obj->numVertices(); ++i) + { + Vertex const& vrt = obj->vertex (i); + + if (axisSet & (1 << X) and vrt.x() != 0.0) + axisSet &= ~(1 << X); + + if (axisSet & (1 << Y) and vrt.y() != 0.0) + axisSet &= ~(1 << Y); + + if (axisSet & (1 << Z) and vrt.z() != 0.0) + axisSet &= ~(1 << Z); + } + + if (axisSet == 0) + break; + } + + if (axisSet != 0) + { + // Subfile has all vertices zero on one specific plane, so it is flat. + // Let's flip it. + Matrix matrixModifier = IdentityMatrix; + + if (axisSet & (1 << X)) + matrixModifier[0] = -1; + + if (axisSet & (1 << Y)) + matrixModifier[4] = -1; + + if (axisSet & (1 << Z)) + matrixModifier[8] = -1; + + setTransform (transform() * matrixModifier); + return; + } + + // Subfile is not flat. Resort to invertnext. + int idx = lineNumber(); + + if (idx > 0) + { + LDBFCPtr bfc = previous().dynamicCast<LDBFC>(); + + if (not bfc.isNull() and bfc->statement() == BFCStatement::InvertNext) + { + // This is prefixed with an invertnext, thus remove it. + bfc->destroy(); + return; + } + } + + // Not inverted, thus prefix it with a new invertnext. + document().toStrongRef()->insertObj (idx, LDSpawn<LDBFC> (BFCStatement::InvertNext)); +} + +// ============================================================================= +// +void LDLine::invert() +{ + // For lines, we swap the vertices. + Vertex tmp = vertex (0); + setVertex (0, vertex (1)); + setVertex (1, tmp); +} + +// ============================================================================= +// +void LDCondLine::invert() +{ + // I don't think that a conditional line's control points need to be + // swapped, do they? + Vertex tmp = vertex (0); + setVertex (0, vertex (1)); + setVertex (1, tmp); +} + +// ============================================================================= +// +LDLinePtr LDCondLine::toEdgeLine() +{ + LDLinePtr replacement (LDSpawn<LDLine>()); + + for (int i = 0; i < replacement->numVertices(); ++i) + replacement->setVertex (i, vertex (i)); + + replacement->setColor (color()); + + replace (replacement); + return replacement; +} + +// ============================================================================= +// +LDObjectPtr LDObject::fromID (int id) +{ + auto it = g_allObjects.find (id); + + if (it != g_allObjects.end()) + return *it; + + return LDObjectPtr(); +} + +// ============================================================================= +// +QString LDOverlay::asText() const +{ + return format ("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6", + fileName(), camera(), x(), y(), width(), height()); +} + +void LDOverlay::invert() {} + +// ============================================================================= +// +// Hook the set accessors of certain properties to this changeProperty function. +// It takes care of history management so we can capture low-level changes, this +// makes history stuff work out of the box. +// +template<typename T> +static void changeProperty (LDObjectPtr obj, T* ptr, const T& val) +{ + long idx; + + if (*ptr == val) + return; + + if (obj->document() != null and (idx = obj->lineNumber()) != -1) + { + QString before = obj->asText(); + *ptr = val; + QString after = obj->asText(); + + if (before != after) + { + obj->document().toStrongRef()->addToHistory (new EditHistory (idx, before, after)); + g_win->R()->compileObject (obj); + CurrentDocument()->redoVertices(); + } + } + else + *ptr = val; +} + +// ============================================================================= +// +void LDObject::setColor (LDColor const& val) +{ + changeProperty (self(), &m_color, val); +} + +// ============================================================================= +// +const Vertex& LDObject::vertex (int i) const +{ + return m_coords[i]; +} + +// ============================================================================= +// +void LDObject::setVertex (int i, const Vertex& vert) +{ + changeProperty (self(), &m_coords[i], vert); +} + +// ============================================================================= +// +void LDMatrixObject::setPosition (const Vertex& a) +{ + changeProperty (self(), &m_position, a); +} + +// ============================================================================= +// +void LDMatrixObject::setTransform (const Matrix& val) +{ + changeProperty (self(), &m_transform, val); +} + +// ============================================================================= +// +void LDObject::select() +{ + assert (document() != null); + document().toStrongRef()->addToSelection (self()); + + // If this object is inverted with INVERTNEXT, pick the INVERTNEXT as well. + /* + LDBFCPtr invertnext; + + if (previousIsInvertnext (invertnext)) + invertnext->select(); + */ +} + +// ============================================================================= +// +void LDObject::deselect() +{ + assert (document() != null); + document().toStrongRef()->removeFromSelection (self()); + + // If this object is inverted with INVERTNEXT, deselect the INVERTNEXT as well. + LDBFCPtr invertnext; + + if (previousIsInvertnext (invertnext)) + invertnext->deselect(); +} + +// ============================================================================= +// +QString PreferredLicenseText() +{ + return (cfg::UseCALicense ? CALicenseText : ""); +} + +// ============================================================================= +// +LDObjectPtr LDObject::createCopy() const +{ + LDObjectPtr copy = ParseLine (asText()); + return copy; +} + +// ============================================================================= +// +void LDSubfile::setFileInfo (const LDDocumentPtr& a) +{ + changeProperty (self(), &m_fileInfo, a); + + // If it's an immediate subfile reference (i.e. this subfile belongs in an + // explicit file), we need to pre-compile the GL polygons for the document + // if they don't exist already. + if (a != null and + a->isImplicit() == false and + a->polygonData().isEmpty()) + { + a->initializeCachedData(); + } +}; + +void LDObject::getVertices (QVector<Vertex>& verts) const +{ + for (int i = 0; i < numVertices(); ++i) + verts << vertex (i); +} + +void LDSubfile::getVertices (QVector<Vertex>& verts) const +{ + verts << fileInfo()->inlineVertices(); +}
--- a/src/main.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QApplication> -#include <QMessageBox> -#include <QAbstractButton> -#include <QFile> -#include <QTextStream> -#include <QDir> -#include "mainWindow.h" -#include "ldDocument.h" -#include "miscallenous.h" -#include "configuration.h" -#include "colors.h" -#include "basics.h" -#include "primitives.h" -#include "glRenderer.h" -#include "configDialog.h" -#include "dialogs.h" -#include "crashCatcher.h" - -MainWindow* g_win = null; -static QString g_versionString, g_fullVersionString; -static bool g_IsExiting (false); - -const Vertex Origin (0.0f, 0.0f, 0.0f); -const Matrix IdentityMatrix ({1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}); - -CFGENTRY (Bool, FirstStart, true) - -// ============================================================================= -// -int main (int argc, char* argv[]) -{ - QApplication app (argc, argv); - app.setOrganizationName (APPNAME); - app.setApplicationName (APPNAME); - InitCrashCatcher(); - Config::Initialize(); - - // Load or create the configuration - if (not Config::Load()) - { - print ("Creating configuration file...\n"); - - if (Config::Save()) - print ("Configuration file successfully created.\n"); - else - Critical ("Failed to create configuration file!\n"); - } - - LDPaths::initPaths(); - InitColors(); - LoadPrimitives(); - MainWindow* win = new MainWindow; - newFile(); - win->show(); - - // If this is the first start, get the user to configuration. Especially point - // them to the profile tab, it's the most important form to fill in. - if (cfg::FirstStart) - { - (new ConfigDialog (ConfigDialog::ProfileTab))->exec(); - cfg::FirstStart = false; - Config::Save(); - } - - // Process the command line - for (int arg = 1; arg < argc; ++arg) - OpenMainModel (QString::fromLocal8Bit (argv[arg])); - - int result = app.exec(); - g_IsExiting = true; - return result; -} - -bool IsExiting() -{ - return g_IsExiting; -} - -void Exit() -{ - g_IsExiting = true; - exit (EXIT_SUCCESS); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,101 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QApplication> +#include <QMessageBox> +#include <QAbstractButton> +#include <QFile> +#include <QTextStream> +#include <QDir> +#include "mainWindow.h" +#include "ldDocument.h" +#include "miscallenous.h" +#include "configuration.h" +#include "colors.h" +#include "basics.h" +#include "primitives.h" +#include "glRenderer.h" +#include "configDialog.h" +#include "dialogs.h" +#include "crashCatcher.h" + +MainWindow* g_win = null; +static QString g_versionString, g_fullVersionString; +static bool g_IsExiting (false); + +const Vertex Origin (0.0f, 0.0f, 0.0f); +const Matrix IdentityMatrix ({1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}); + +CFGENTRY (Bool, FirstStart, true) + +// ============================================================================= +// +int main (int argc, char* argv[]) +{ + QApplication app (argc, argv); + app.setOrganizationName (APPNAME); + app.setApplicationName (APPNAME); + InitCrashCatcher(); + Config::Initialize(); + + // Load or create the configuration + if (not Config::Load()) + { + print ("Creating configuration file...\n"); + + if (Config::Save()) + print ("Configuration file successfully created.\n"); + else + Critical ("Failed to create configuration file!\n"); + } + + LDPaths::initPaths(); + InitColors(); + LoadPrimitives(); + MainWindow* win = new MainWindow; + newFile(); + win->show(); + + // If this is the first start, get the user to configuration. Especially point + // them to the profile tab, it's the most important form to fill in. + if (cfg::FirstStart) + { + (new ConfigDialog (ConfigDialog::ProfileTab))->exec(); + cfg::FirstStart = false; + Config::Save(); + } + + // Process the command line + for (int arg = 1; arg < argc; ++arg) + OpenMainModel (QString::fromLocal8Bit (argv[arg])); + + int result = app.exec(); + g_IsExiting = true; + return result; +} + +bool IsExiting() +{ + return g_IsExiting; +} + +void Exit() +{ + g_IsExiting = true; + exit (EXIT_SUCCESS); +}
--- a/src/mainWindow.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1191 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <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 <QSettings> -#include "main.h" -#include "glRenderer.h" -#include "mainWindow.h" -#include "ldDocument.h" -#include "configuration.h" -#include "miscallenous.h" -#include "colors.h" -#include "editHistory.h" -#include "radioGroup.h" -#include "addObjectDialog.h" -#include "messageLog.h" -#include "configuration.h" -#include "ui_ldforge.h" -#include "primitives.h" -#include "editmodes/abstractEditMode.h" - -static bool g_isSelectionLocked = false; -static QMap<QAction*, QKeySequence> g_defaultShortcuts; - -CFGENTRY (Bool, ColorizeObjectsList, true) -CFGENTRY (String, QuickColorToolbar, "4:25:14:27:2:3:11:1:22:|:0:72:71:15") -CFGENTRY (Bool, ListImplicitFiles, false) -CFGENTRY (List, HiddenToolbars, {}) -EXTERN_CFGENTRY (List, RecentFiles) -EXTERN_CFGENTRY (Bool, DrawAxes) -EXTERN_CFGENTRY (String, MainColor) -EXTERN_CFGENTRY (Float, MainColorAlpha) -EXTERN_CFGENTRY (Bool, DrawWireframe) -EXTERN_CFGENTRY (Bool, BFCRedGreenView) -EXTERN_CFGENTRY (Bool, DrawAngles) -EXTERN_CFGENTRY (Bool, RandomColors) -EXTERN_CFGENTRY (Bool, DrawSurfaces) -EXTERN_CFGENTRY (Bool, DrawEdgeLines) -EXTERN_CFGENTRY (Bool, DrawConditionalLines) - -// ============================================================================= -// -MainWindow::MainWindow (QWidget* parent, Qt::WindowFlags flags) : - QMainWindow (parent, flags) -{ - g_win = this; - ui = new Ui_LDForgeUI; - ui->setupUi (this); - m_isUpdatingTabs = false; - m_renderer = new GLRenderer (this); - m_tabBar = new QTabBar; - m_tabBar->setTabsClosable (true); - ui->verticalLayout->insertWidget (0, m_tabBar); - - // Stuff the renderer into its frame - QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame); - rendererLayout->addWidget (R()); - - connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged())); - connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*))); - connect (m_tabBar, SIGNAL (currentChanged(int)), this, SLOT (changeCurrentFile())); - connect (m_tabBar, SIGNAL (tabCloseRequested (int)), this, SLOT (closeTab (int))); - - if (ActivePrimitiveScanner() != null) - connect (ActivePrimitiveScanner(), SIGNAL (workDone()), this, SLOT (updatePrimitives())); - else - updatePrimitives(); - - m_messageLog = new MessageManager; - m_messageLog->setRenderer (R()); - m_renderer->setMessageLog (m_messageLog); - m_quickColors = LoadQuickColorList(); - slot_selectionChanged(); - setStatusBar (new QStatusBar); - updateActions(); - - // Connect all actions and save default sequences - applyToActions ([&](QAction* act) - { - connect (act, SIGNAL (triggered()), this, SLOT (actionTriggered())); - g_defaultShortcuts[act] = act->shortcut(); - }); - - updateGridToolBar(); - updateEditModeActions(); - updateRecentFilesMenu(); - updateColorToolbar(); - updateTitle(); - loadShortcuts (Config::SettingsObject()); - setMinimumSize (300, 200); - connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup())); - connect (ui->ringToolHiRes, SIGNAL (clicked (bool)), this, SLOT (ringToolHiResClicked (bool))); - connect (ui->ringToolSegments, SIGNAL (valueChanged (int)), - this, SLOT (circleToolSegmentsChanged())); - circleToolSegmentsChanged(); // invoke it manually for initial label text - - for (QVariant const& toolbarname : cfg::HiddenToolbars) - { - QToolBar* toolbar = findChild<QToolBar*> (toolbarname.toString()); - - if (toolbar != null) - toolbar->hide(); - } -} - -MainWindow::~MainWindow() -{ - g_win = null; -} - -// ============================================================================= -// -void MainWindow::actionTriggered() -{ - // Get the name of the sender object and use it to compose the slot name, - // then invoke this slot to call the action. - QMetaObject::invokeMethod (this, - qPrintable (format ("slot_%1", sender()->objectName())), Qt::DirectConnection); - endAction(); -} - -// ============================================================================= -// -void MainWindow::endAction() -{ - // Add a step in the history now. - CurrentDocument()->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 (CurrentDocument()); - refresh(); -} - -// ============================================================================= -// -void MainWindow::slot_lastSecondCleanup() -{ - delete m_renderer; - delete ui; -} - -// ============================================================================= -// -void MainWindow::updateRecentFilesMenu() -{ - // First, clear any items in the recent files menu -for (QAction * recent : m_recentFiles) - delete recent; - - m_recentFiles.clear(); - - QAction* first = null; - - for (const QVariant& it : cfg::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> LoadQuickColorList() -{ - QList<LDQuickColor> colors; - - for (QString colorname : cfg::QuickColorToolbar.split (":")) - { - if (colorname == "|") - colors << LDQuickColor::getSeparator(); - else - { - LDColor col = LDColor::fromIndex (colorname.toLong()); - - if (col != null) - colors << LDQuickColor (col, null); - } - } - - return colors; -} - -// ============================================================================= -// -void MainWindow::updateColorToolbar() -{ - m_colorButtons.clear(); - ui->toolBarColors->clear(); - ui->toolBarColors->addAction (ui->actionUncolor); - ui->toolBarColors->addSeparator(); - - for (LDQuickColor& entry : m_quickColors) - { - if (entry.isSeparator()) - { - ui->toolBarColors->addSeparator(); - } - else - { - QToolButton* colorButton = new QToolButton; - colorButton->setIcon (MakeColorIcon (entry.color(), 16)); - colorButton->setIconSize (QSize (16, 16)); - colorButton->setToolTip (entry.color().name()); - - connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor())); - ui->toolBarColors->addWidget (colorButton); - m_colorButtons << colorButton; - - entry.setToolButton (colorButton); - } - } - - updateGridToolBar(); -} - -// ============================================================================= -// -void MainWindow::updateGridToolBar() -{ - // Ensure that the current grid - and only the current grid - is selected. - ui->actionGridCoarse->setChecked (cfg::Grid == Grid::Coarse); - ui->actionGridMedium->setChecked (cfg::Grid == Grid::Medium); - ui->actionGridFine->setChecked (cfg::Grid == Grid::Fine); -} - -// ============================================================================= -// -void MainWindow::updateTitle() -{ - QString title = format (APPNAME " %1", VersionString()); - - // Append our current file if we have one - if (CurrentDocument()) - { - title += ": "; - title += CurrentDocument()->getDisplayName(); - - if (CurrentDocument()->getObjectCount() > 0 and - CurrentDocument()->getObject (0)->type() == OBJ_Comment) - { - // Append title - LDCommentPtr comm = CurrentDocument()->getObject (0).staticCast<LDComment>(); - title += format (": %1", comm->text()); - } - - if (CurrentDocument()->hasUnsavedChanges()) - title += '*'; - } - -#ifdef DEBUG - title += " [debug build]"; -#elif BUILD_ID != BUILD_RELEASE - title += " [pre-release build]"; -#endif // DEBUG - - if (CommitTimeString()[0] != '\0') - title += format (" (%1)", QString::fromUtf8 (CommitTimeString())); - - setWindowTitle (title); -} - -// ============================================================================= -// -int MainWindow::deleteSelection() -{ - if (Selection().isEmpty()) - return 0; - - LDObjectList selCopy = Selection(); - - // Delete the objects that were being selected - for (LDObjectPtr obj : selCopy) - obj->destroy(); - - refresh(); - return selCopy.size(); -} - -// ============================================================================= -// -void MainWindow::buildObjList() -{ - if (not CurrentDocument()) - 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 (LDObjectPtr obj : CurrentDocument()->objects()) - { - QString descr; - - switch (obj->type()) - { - case OBJ_Comment: - { - descr = obj.staticCast<LDComment>()->text(); - - // Remove leading whitespace - while (descr[0] == ' ') - descr.remove (0, 1); - - break; - } - - case OBJ_Empty: - break; // leave it empty - - case OBJ_Line: - case OBJ_Triangle: - case OBJ_Quad: - case OBJ_CondLine: - { - for (int i = 0; i < obj->numVertices(); ++i) - { - if (i != 0) - descr += ", "; - - descr += obj->vertex (i).toString (true); - } - break; - } - - case OBJ_Error: - { - descr = format ("ERROR: %1", obj->asText()); - break; - } - - case OBJ_Subfile: - { - LDSubfilePtr ref = obj.staticCast<LDSubfile>(); - - descr = format ("%1 %2, (", ref->fileInfo()->getDisplayName(), ref->position().toString (true)); - - for (int i = 0; i < 9; ++i) - descr += format ("%1%2", ref->transform()[i], (i != 8) ? " " : ""); - - descr += ')'; - break; - } - - case OBJ_BFC: - { - descr = LDBFC::StatementStrings[int (obj.staticCast<LDBFC>()->statement())]; - break; - } - - case OBJ_Overlay: - { - LDOverlayPtr ovl = obj.staticCast<LDOverlay>(); - descr = format ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->camera()], - Basename (ovl->fileName()), ovl->x(), ovl->y(), - ovl->width(), ovl->height()); - break; - } - - default: - { - descr = obj->typeName(); - break; - } - } - - QListWidgetItem* item = new QListWidgetItem (descr); - item->setIcon (GetIcon (obj->typeName())); - - // Use italic font if hidden - if (obj->isHidden()) - { - QFont font = item->font(); - font.setItalic (true); - item->setFont (font); - } - - // Color gibberish orange on red so it stands out. - if (obj->type() == OBJ_Error) - { - item->setBackground (QColor ("#AA0000")); - item->setForeground (QColor ("#FFAA00")); - } - elif (cfg::ColorizeObjectsList and obj->isColored() and - obj->color() != null and obj->color() != MainColor() and obj->color() != EdgeColor()) - { - // If the object isn't in the main or edge color, draw this - // list entry in said color. - item->setForeground (obj->color().faceColor()); - } - - obj->qObjListEntry = item; - ui->objectList->insertItem (ui->objectList->count(), item); - } - - g_isSelectionLocked = false; - updateSelection(); - scrollToSelection(); -} - -// ============================================================================= -// -void MainWindow::scrollToSelection() -{ - if (Selection().isEmpty()) - return; - - LDObjectPtr obj = Selection().last(); - ui->objectList->scrollToItem (obj->qObjListEntry); -} - -// ============================================================================= -// -void MainWindow::slot_selectionChanged() -{ - if (g_isSelectionLocked == true or CurrentDocument() == null) - return; - - LDObjectList priorSelection = Selection(); - - // Get the objects from the object list selection - CurrentDocument()->clearSelection(); - const QList<QListWidgetItem*> items = ui->objectList->selectedItems(); - - for (LDObjectPtr obj : CurrentDocument()->objects()) - { - for (QListWidgetItem* item : items) - { - if (item == obj->qObjListEntry) - { - obj->select(); - break; - } - } - } - - // The select() method calls may have selected additional items (i.e. invertnexts) - // Update it all now. - updateSelection(); - - // Update the GL renderer - LDObjectList compound = priorSelection + Selection(); - RemoveDuplicates (compound); - - for (LDObjectPtr obj : compound) - R()->compileObject (obj); - - R()->update(); -} - -// ============================================================================= -// -void MainWindow::slot_recentFile() -{ - QAction* qAct = static_cast<QAction*> (sender()); - OpenMainModel (qAct->text()); -} - -// ============================================================================= -// -void MainWindow::slot_quickColor() -{ - QToolButton* button = static_cast<QToolButton*> (sender()); - LDColor col = null; - - for (const LDQuickColor& entry : m_quickColors) - { - if (entry.toolButton() == button) - { - col = entry.color(); - break; - } - } - - if (col == null) - return; - - for (LDObjectPtr obj : Selection()) - { - if (not obj->isColored()) - continue; // uncolored object - - obj->setColor (col); - R()->compileObject (obj); - } - - endAction(); - refresh(); -} - -// ============================================================================= -// -int MainWindow::getInsertionPoint() -{ - // If we have a selection, put the item after it. - if (not Selection().isEmpty()) - return Selection().last()->lineNumber() + 1; - - // Otherwise place the object at the end. - return CurrentDocument()->getObjectCount(); -} - -// ============================================================================= -// -void MainWindow::doFullRefresh() -{ - buildObjList(); - m_renderer->hardRefresh(); -} - -// ============================================================================= -// -void MainWindow::refresh() -{ - buildObjList(); - m_renderer->update(); -} - -// ============================================================================= -// -void MainWindow::updateSelection() -{ - g_isSelectionLocked = true; - QItemSelection itemselect; - int top = -1; - int bottom = -1; - - for (LDObjectPtr obj : Selection()) - { - if (obj->qObjListEntry == null) - continue; - - int row = ui->objectList->row (obj->qObjListEntry); - - if (top == -1) - { - top = bottom = row; - } - else - { - if (row != bottom + 1) - { - itemselect.select (ui->objectList->model()->index (top, 0), - ui->objectList->model()->index (bottom, 0)); - top = -1; - } - - bottom = row; - } - } - - if (top != -1) - { - itemselect.select (ui->objectList->model()->index (top, 0), - ui->objectList->model()->index (bottom, 0)); - } - - ui->objectList->selectionModel()->select (itemselect, QItemSelectionModel::ClearAndSelect); - g_isSelectionLocked = false; -} - -// ============================================================================= -// -LDColor MainWindow::getSelectedColor() -{ - LDColor result; - - for (LDObjectPtr obj : Selection()) - { - if (not obj->isColored()) - continue; // doesn't use color - - if (result != null and obj->color() != result) - return null; // No consensus in object color - - if (result == null) - result = obj->color(); - } - - return result; -} - -// ============================================================================= -// -void MainWindow::closeEvent (QCloseEvent* ev) -{ - // Check whether it's safe to close all files. - if (not IsSafeToCloseAll()) - { - ev->ignore(); - return; - } - - // Save the toolbar set - cfg::HiddenToolbars.clear(); - - for (QToolBar* toolbar : findChildren<QToolBar*>()) - { - if (toolbar->isHidden()) - cfg::HiddenToolbars << toolbar->objectName(); - } - - // Save the configuration before leaving. - Config::Save(); - ev->accept(); -} - -// ============================================================================= -// -void MainWindow::spawnContextMenu (const QPoint pos) -{ - const bool single = (Selection().size() == 1); - LDObjectPtr singleObj = single ? Selection().first() : LDObjectPtr(); - - bool hasSubfiles = false; - - for (LDObjectPtr obj : Selection()) - { - if (obj->type() == OBJ_Subfile) - { - hasSubfiles = true; - break; - } - } - - QMenu* contextMenu = new QMenu; - - if (single and singleObj->type() != OBJ_Empty) - { - contextMenu->addAction (ui->actionEdit); - contextMenu->addSeparator(); - } - - contextMenu->addAction (ui->actionCut); - contextMenu->addAction (ui->actionCopy); - contextMenu->addAction (ui->actionPaste); - contextMenu->addAction (ui->actionDelete); - contextMenu->addSeparator(); - contextMenu->addAction (ui->actionSetColor); - - if (single) - contextMenu->addAction (ui->actionEditRaw); - - contextMenu->addAction (ui->actionBorders); - contextMenu->addAction (ui->actionSetOverlay); - contextMenu->addAction (ui->actionClearOverlay); - - if (hasSubfiles) - { - contextMenu->addSeparator(); - contextMenu->addAction (ui->actionOpenSubfiles); - } - - contextMenu->addSeparator(); - contextMenu->addAction (ui->actionModeSelect); - contextMenu->addAction (ui->actionModeDraw); - contextMenu->addAction (ui->actionModeCircle); - - if (not Selection().isEmpty()) - { - contextMenu->addSeparator(); - contextMenu->addAction (ui->actionSubfileSelection); - } - - if (R()->camera() != EFreeCamera) - { - contextMenu->addSeparator(); - contextMenu->addAction (ui->actionSetDrawDepth); - } - - contextMenu->exec (pos); -} - -// ============================================================================= -// -void MainWindow::deleteByColor (LDColor color) -{ - LDObjectList objs; - - for (LDObjectPtr obj : CurrentDocument()->objects()) - { - if (not obj->isColored() or obj->color() != color) - continue; - - objs << obj; - } - - for (LDObjectPtr obj : objs) - obj->destroy(); -} - -// ============================================================================= -// -void MainWindow::updateEditModeActions() -{ - const EditModeType mode = R()->currentEditModeType(); - ui->actionModeSelect->setChecked (mode == EditModeType::Select); - ui->actionModeDraw->setChecked (mode == EditModeType::Draw); - ui->actionModeRectangle->setChecked (mode == EditModeType::Rectangle); - ui->actionModeCircle->setChecked (mode == EditModeType::Circle); - ui->actionModeMagicWand->setChecked (mode == EditModeType::MagicWand); - ui->actionModeLinePath->setChecked (mode == EditModeType::LinePath); -} - -// ============================================================================= -// -void MainWindow::slot_editObject (QListWidgetItem* listitem) -{ - for (LDObjectPtr it : CurrentDocument()->objects()) - { - if (it->qObjListEntry == listitem) - { - AddObjectDialog::staticDialog (it->type(), it); - break; - } - } -} - -// ============================================================================= -// -bool MainWindow::save (LDDocumentPtr doc, bool saveAs) -{ - QString path = doc->fullPath(); - int64 savesize; - - if (saveAs or path.isEmpty()) - { - QString name = doc->defaultName(); - - if (not doc->fullPath().isEmpty()) - name = doc->fullPath(); - elif (not doc->name().isEmpty()) - name = doc->name(); - - name.replace ("\\", "/"); - path = QFileDialog::getSaveFileName (g_win, tr ("Save As"), - name, tr ("LDraw files (*.dat *.ldr)")); - - if (path.isEmpty()) - { - // User didn't give a file name, abort. - return false; - } - } - - if (doc->save (path, &savesize)) - { - if (doc == CurrentDocument()) - updateTitle(); - - print ("Saved to %1 (%2)", path, MakePrettyFileSize (savesize)); - - // Add it to recent files - AddRecentFile (path); - return true; - } - - QString message = format (tr ("Failed to save to %1: %2"), path, strerror (errno)); - - // Tell the user the save failed, and give the option for saving as with it. - QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win); - - // Add a save-as button - QPushButton* saveAsBtn = new QPushButton (tr ("Save As")); - saveAsBtn->setIcon (GetIcon ("file-save-as")); - dlg.addButton (saveAsBtn, QMessageBox::ActionRole); - dlg.setDefaultButton (QMessageBox::Close); - dlg.exec(); - - if (dlg.clickedButton() == saveAsBtn) - return save (doc, true); // yay recursion! - - return false; -} - -void MainWindow::addMessage (QString msg) -{ - m_messageLog->addLine (msg); -} - -// ============================================================================ -void ObjectList::contextMenuEvent (QContextMenuEvent* ev) -{ - g_win->spawnContextMenu (ev->globalPos()); -} - -// ============================================================================= -// -QPixmap GetIcon (QString iconName) -{ - return (QPixmap (format (":/icons/%1.png", iconName))); -} - -// ============================================================================= -// -bool Confirm (const QString& message) -{ - return Confirm (MainWindow::tr ("Confirm"), message); -} - -// ============================================================================= -// -bool Confirm (const QString& title, const QString& message) -{ - return QMessageBox::question (g_win, title, message, - (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes; -} - -// ============================================================================= -// -void Critical (const QString& message) -{ - QMessageBox::critical (g_win, MainWindow::tr ("Error"), message, - (QMessageBox::Close), QMessageBox::Close); -} - -// ============================================================================= -// -QIcon MakeColorIcon (LDColor colinfo, const int size) -{ - // Create an image object and link a painter to it. - QImage img (size, size, QImage::Format_ARGB32); - QPainter paint (&img); - QColor col = colinfo.faceColor(); - - if (colinfo == MainColor()) - { - // Use the user preferences for main color here - col = cfg::MainColor; - col.setAlphaF (cfg::MainColorAlpha); - } - - // 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<LDColor, int> counts; - - for (LDObjectPtr obj : CurrentDocument()->objects()) - { - if (not obj->isColored() or obj->color() == null) - 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) - { - QIcon ico = MakeColorIcon (pair.first, 16); - box->addItem (ico, format ("[%1] %2 (%3 object%4)", - pair.first, pair.first.name(), pair.second, Plural (pair.second))); - box->setItemData (row, pair.first.index()); - - ++row; - } -} - -// ============================================================================= -// -void MainWindow::updateDocumentList() -{ - m_isUpdatingTabs = true; - - while (m_tabBar->count() > 0) - m_tabBar->removeTab (0); - - for (LDDocumentPtr f : LDDocument::explicitDocuments()) - { - // Add an item to the list for this file and store the tab index - // in the document so we can find documents by tab index. - f->setTabIndex (m_tabBar->addTab ("")); - updateDocumentListItem (f); - } - - m_isUpdatingTabs = false; -} - -// ============================================================================= -// -void MainWindow::updateDocumentListItem (LDDocumentPtr doc) -{ - bool oldUpdatingTabs = m_isUpdatingTabs; - m_isUpdatingTabs = true; - - if (doc->tabIndex() == -1) - { - // We don't have a list item for this file, so the list either doesn't - // exist yet or is out of date. Build the list now. - updateDocumentList(); - return; - } - - // If this is the current file, it also needs to be the selected item on - // the list. - if (doc == CurrentDocument()) - m_tabBar->setCurrentIndex (doc->tabIndex()); - - m_tabBar->setTabText (doc->tabIndex(), doc->getDisplayName()); - - // If the document.has unsaved changes, draw a little icon next to it to mark that. - m_tabBar->setTabIcon (doc->tabIndex(), doc->hasUnsavedChanges() ? GetIcon ("file-save") : QIcon()); - m_tabBar->setTabData (doc->tabIndex(), doc->name()); - m_isUpdatingTabs = oldUpdatingTabs; -} - -// ============================================================================= -// -// A file is selected from the list of files on the left of the screen. Find out -// which file was picked and change to it. -// -void MainWindow::changeCurrentFile() -{ - if (m_isUpdatingTabs) - return; - - LDDocumentPtr f; - int tabIndex = m_tabBar->currentIndex(); - - // Find the file pointer of the item that was selected. - for (LDDocumentPtr it : LDDocument::explicitDocuments()) - { - if (it->tabIndex() == tabIndex) - { - f = it; - break; - } - } - - // If we picked the same file we're currently on, we don't need to do - // anything. - if (f == null or f == CurrentDocument()) - return; - - LDDocument::setCurrent (f); -} - -// ============================================================================= -// -void MainWindow::refreshObjectList() -{ -#if 0 - ui->objectList->clear(); - LDDocumentPtr f = getCurrentDocument(); - -for (LDObjectPtr obj : *f) - ui->objectList->addItem (obj->qObjListEntry); - -#endif - - buildObjList(); -} - -// ============================================================================= -// -void MainWindow::updateActions() -{ - if (CurrentDocument() != null and CurrentDocument()->history() != null) - { - History* his = CurrentDocument()->history(); - int pos = his->position(); - ui->actionUndo->setEnabled (pos != -1); - ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1); - } - - ui->actionWireframe->setChecked (cfg::DrawWireframe); - ui->actionAxes->setChecked (cfg::DrawAxes); - ui->actionBFCView->setChecked (cfg::BFCRedGreenView); - ui->actionRandomColors->setChecked (cfg::RandomColors); - ui->actionDrawAngles->setChecked (cfg::DrawAngles); - ui->actionDrawSurfaces->setChecked (cfg::DrawSurfaces); - ui->actionDrawEdgeLines->setChecked (cfg::DrawEdgeLines); - ui->actionDrawConditionalLines->setChecked (cfg::DrawConditionalLines); -} - -// ============================================================================= -// -void MainWindow::updatePrimitives() -{ - PopulatePrimitives (ui->primitives); -} - -// ============================================================================= -// -void MainWindow::closeTab (int tabindex) -{ - LDDocumentPtr doc = FindDocument (m_tabBar->tabData (tabindex).toString()); - - if (doc == null) - return; - - doc->dismiss(); -} - -// ============================================================================= -// -void MainWindow::loadShortcuts (QSettings const* settings) -{ - for (QAction* act : findChildren<QAction*>()) - { - QKeySequence seq = settings->value ("shortcut_" + act->objectName(), act->shortcut()).value<QKeySequence>(); - act->setShortcut (seq); - } -} - -// ============================================================================= -// -void MainWindow::saveShortcuts (QSettings* settings) -{ - applyToActions ([&](QAction* act) - { - QString const key = "shortcut_" + act->objectName(); - - if (g_defaultShortcuts[act] != act->shortcut()) - settings->setValue (key, act->shortcut()); - else - settings->remove (key); - }); -} - -// ============================================================================= -// -void MainWindow::applyToActions (std::function<void(QAction*)> function) -{ - for (QAction* act : findChildren<QAction*>()) - { - if (not act->objectName().isEmpty()) - function (act); - } -} - -// ============================================================================= -// -QKeySequence MainWindow::defaultShortcut (QAction* act) // [static] -{ - return g_defaultShortcuts[act]; -} - -// ============================================================================= -// -bool MainWindow::ringToolHiRes() const -{ - return ui->ringToolHiRes->isChecked(); -} - -// ============================================================================= -// -int MainWindow::ringToolSegments() const -{ - return ui->ringToolSegments->value(); -} - -// ============================================================================= -// -void MainWindow::ringToolHiResClicked (bool checked) -{ - if (checked) - { - ui->ringToolSegments->setMaximum (HighResolution); - ui->ringToolSegments->setValue (ui->ringToolSegments->value() * 3); - } - else - { - ui->ringToolSegments->setValue (ui->ringToolSegments->value() / 3); - ui->ringToolSegments->setMaximum (LowResolution); - } -} - -// ============================================================================= -// -void MainWindow::circleToolSegmentsChanged() -{ - int numerator (ui->ringToolSegments->value()); - int denominator (ui->ringToolHiRes->isChecked() ? HighResolution : LowResolution); - Simplify (numerator, denominator); - ui->ringToolSegmentsLabel->setText (format ("%1 / %2", numerator, denominator)); -} - -// ============================================================================= -// -QImage GetImageFromScreencap (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; -} - -void PopulatePrimitives (QTreeWidget* tw, QString const& selectByDefault) -{ - tw->clear(); - - for (PrimitiveCategory* cat : g_PrimitiveCategories) - { - SubfileListItem* parentItem = new SubfileListItem (tw, null); - parentItem->setText (0, cat->name()); - QList<QTreeWidgetItem*> subfileItems; - - for (Primitive& prim : cat->prims) - { - SubfileListItem* item = new SubfileListItem (parentItem, &prim); - item->setText (0, format ("%1 - %2", prim.name, prim.title)); - subfileItems << item; - - // If this primitive is the one the current object points to, - // select it by default - if (selectByDefault == prim.name) - tw->setCurrentItem (item); - } - - tw->addTopLevelItem (parentItem); - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mainWindow.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,1191 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <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 <QSettings> +#include "main.h" +#include "glRenderer.h" +#include "mainWindow.h" +#include "ldDocument.h" +#include "configuration.h" +#include "miscallenous.h" +#include "colors.h" +#include "editHistory.h" +#include "radioGroup.h" +#include "addObjectDialog.h" +#include "messageLog.h" +#include "configuration.h" +#include "ui_ldforge.h" +#include "primitives.h" +#include "editmodes/abstractEditMode.h" + +static bool g_isSelectionLocked = false; +static QMap<QAction*, QKeySequence> g_defaultShortcuts; + +CFGENTRY (Bool, ColorizeObjectsList, true) +CFGENTRY (String, QuickColorToolbar, "4:25:14:27:2:3:11:1:22:|:0:72:71:15") +CFGENTRY (Bool, ListImplicitFiles, false) +CFGENTRY (List, HiddenToolbars, {}) +EXTERN_CFGENTRY (List, RecentFiles) +EXTERN_CFGENTRY (Bool, DrawAxes) +EXTERN_CFGENTRY (String, MainColor) +EXTERN_CFGENTRY (Float, MainColorAlpha) +EXTERN_CFGENTRY (Bool, DrawWireframe) +EXTERN_CFGENTRY (Bool, BFCRedGreenView) +EXTERN_CFGENTRY (Bool, DrawAngles) +EXTERN_CFGENTRY (Bool, RandomColors) +EXTERN_CFGENTRY (Bool, DrawSurfaces) +EXTERN_CFGENTRY (Bool, DrawEdgeLines) +EXTERN_CFGENTRY (Bool, DrawConditionalLines) + +// ============================================================================= +// +MainWindow::MainWindow (QWidget* parent, Qt::WindowFlags flags) : + QMainWindow (parent, flags) +{ + g_win = this; + ui = new Ui_LDForgeUI; + ui->setupUi (this); + m_isUpdatingTabs = false; + m_renderer = new GLRenderer (this); + m_tabBar = new QTabBar; + m_tabBar->setTabsClosable (true); + ui->verticalLayout->insertWidget (0, m_tabBar); + + // Stuff the renderer into its frame + QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame); + rendererLayout->addWidget (R()); + + connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged())); + connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*))); + connect (m_tabBar, SIGNAL (currentChanged(int)), this, SLOT (changeCurrentFile())); + connect (m_tabBar, SIGNAL (tabCloseRequested (int)), this, SLOT (closeTab (int))); + + if (ActivePrimitiveScanner() != null) + connect (ActivePrimitiveScanner(), SIGNAL (workDone()), this, SLOT (updatePrimitives())); + else + updatePrimitives(); + + m_messageLog = new MessageManager; + m_messageLog->setRenderer (R()); + m_renderer->setMessageLog (m_messageLog); + m_quickColors = LoadQuickColorList(); + slot_selectionChanged(); + setStatusBar (new QStatusBar); + updateActions(); + + // Connect all actions and save default sequences + applyToActions ([&](QAction* act) + { + connect (act, SIGNAL (triggered()), this, SLOT (actionTriggered())); + g_defaultShortcuts[act] = act->shortcut(); + }); + + updateGridToolBar(); + updateEditModeActions(); + updateRecentFilesMenu(); + updateColorToolbar(); + updateTitle(); + loadShortcuts (Config::SettingsObject()); + setMinimumSize (300, 200); + connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup())); + connect (ui->ringToolHiRes, SIGNAL (clicked (bool)), this, SLOT (ringToolHiResClicked (bool))); + connect (ui->ringToolSegments, SIGNAL (valueChanged (int)), + this, SLOT (circleToolSegmentsChanged())); + circleToolSegmentsChanged(); // invoke it manually for initial label text + + for (QVariant const& toolbarname : cfg::HiddenToolbars) + { + QToolBar* toolbar = findChild<QToolBar*> (toolbarname.toString()); + + if (toolbar != null) + toolbar->hide(); + } +} + +MainWindow::~MainWindow() +{ + g_win = null; +} + +// ============================================================================= +// +void MainWindow::actionTriggered() +{ + // Get the name of the sender object and use it to compose the slot name, + // then invoke this slot to call the action. + QMetaObject::invokeMethod (this, + qPrintable (format ("slot_%1", sender()->objectName())), Qt::DirectConnection); + endAction(); +} + +// ============================================================================= +// +void MainWindow::endAction() +{ + // Add a step in the history now. + CurrentDocument()->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 (CurrentDocument()); + refresh(); +} + +// ============================================================================= +// +void MainWindow::slot_lastSecondCleanup() +{ + delete m_renderer; + delete ui; +} + +// ============================================================================= +// +void MainWindow::updateRecentFilesMenu() +{ + // First, clear any items in the recent files menu +for (QAction * recent : m_recentFiles) + delete recent; + + m_recentFiles.clear(); + + QAction* first = null; + + for (const QVariant& it : cfg::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> LoadQuickColorList() +{ + QList<LDQuickColor> colors; + + for (QString colorname : cfg::QuickColorToolbar.split (":")) + { + if (colorname == "|") + colors << LDQuickColor::getSeparator(); + else + { + LDColor col = LDColor::fromIndex (colorname.toLong()); + + if (col != null) + colors << LDQuickColor (col, null); + } + } + + return colors; +} + +// ============================================================================= +// +void MainWindow::updateColorToolbar() +{ + m_colorButtons.clear(); + ui->toolBarColors->clear(); + ui->toolBarColors->addAction (ui->actionUncolor); + ui->toolBarColors->addSeparator(); + + for (LDQuickColor& entry : m_quickColors) + { + if (entry.isSeparator()) + { + ui->toolBarColors->addSeparator(); + } + else + { + QToolButton* colorButton = new QToolButton; + colorButton->setIcon (MakeColorIcon (entry.color(), 16)); + colorButton->setIconSize (QSize (16, 16)); + colorButton->setToolTip (entry.color().name()); + + connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor())); + ui->toolBarColors->addWidget (colorButton); + m_colorButtons << colorButton; + + entry.setToolButton (colorButton); + } + } + + updateGridToolBar(); +} + +// ============================================================================= +// +void MainWindow::updateGridToolBar() +{ + // Ensure that the current grid - and only the current grid - is selected. + ui->actionGridCoarse->setChecked (cfg::Grid == Grid::Coarse); + ui->actionGridMedium->setChecked (cfg::Grid == Grid::Medium); + ui->actionGridFine->setChecked (cfg::Grid == Grid::Fine); +} + +// ============================================================================= +// +void MainWindow::updateTitle() +{ + QString title = format (APPNAME " %1", VersionString()); + + // Append our current file if we have one + if (CurrentDocument()) + { + title += ": "; + title += CurrentDocument()->getDisplayName(); + + if (CurrentDocument()->getObjectCount() > 0 and + CurrentDocument()->getObject (0)->type() == OBJ_Comment) + { + // Append title + LDCommentPtr comm = CurrentDocument()->getObject (0).staticCast<LDComment>(); + title += format (": %1", comm->text()); + } + + if (CurrentDocument()->hasUnsavedChanges()) + title += '*'; + } + +#ifdef DEBUG + title += " [debug build]"; +#elif BUILD_ID != BUILD_RELEASE + title += " [pre-release build]"; +#endif // DEBUG + + if (CommitTimeString()[0] != '\0') + title += format (" (%1)", QString::fromUtf8 (CommitTimeString())); + + setWindowTitle (title); +} + +// ============================================================================= +// +int MainWindow::deleteSelection() +{ + if (Selection().isEmpty()) + return 0; + + LDObjectList selCopy = Selection(); + + // Delete the objects that were being selected + for (LDObjectPtr obj : selCopy) + obj->destroy(); + + refresh(); + return selCopy.size(); +} + +// ============================================================================= +// +void MainWindow::buildObjList() +{ + if (not CurrentDocument()) + 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 (LDObjectPtr obj : CurrentDocument()->objects()) + { + QString descr; + + switch (obj->type()) + { + case OBJ_Comment: + { + descr = obj.staticCast<LDComment>()->text(); + + // Remove leading whitespace + while (descr[0] == ' ') + descr.remove (0, 1); + + break; + } + + case OBJ_Empty: + break; // leave it empty + + case OBJ_Line: + case OBJ_Triangle: + case OBJ_Quad: + case OBJ_CondLine: + { + for (int i = 0; i < obj->numVertices(); ++i) + { + if (i != 0) + descr += ", "; + + descr += obj->vertex (i).toString (true); + } + break; + } + + case OBJ_Error: + { + descr = format ("ERROR: %1", obj->asText()); + break; + } + + case OBJ_Subfile: + { + LDSubfilePtr ref = obj.staticCast<LDSubfile>(); + + descr = format ("%1 %2, (", ref->fileInfo()->getDisplayName(), ref->position().toString (true)); + + for (int i = 0; i < 9; ++i) + descr += format ("%1%2", ref->transform()[i], (i != 8) ? " " : ""); + + descr += ')'; + break; + } + + case OBJ_BFC: + { + descr = LDBFC::StatementStrings[int (obj.staticCast<LDBFC>()->statement())]; + break; + } + + case OBJ_Overlay: + { + LDOverlayPtr ovl = obj.staticCast<LDOverlay>(); + descr = format ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->camera()], + Basename (ovl->fileName()), ovl->x(), ovl->y(), + ovl->width(), ovl->height()); + break; + } + + default: + { + descr = obj->typeName(); + break; + } + } + + QListWidgetItem* item = new QListWidgetItem (descr); + item->setIcon (GetIcon (obj->typeName())); + + // Use italic font if hidden + if (obj->isHidden()) + { + QFont font = item->font(); + font.setItalic (true); + item->setFont (font); + } + + // Color gibberish orange on red so it stands out. + if (obj->type() == OBJ_Error) + { + item->setBackground (QColor ("#AA0000")); + item->setForeground (QColor ("#FFAA00")); + } + elif (cfg::ColorizeObjectsList and obj->isColored() and + obj->color() != null and obj->color() != MainColor() and obj->color() != EdgeColor()) + { + // If the object isn't in the main or edge color, draw this + // list entry in said color. + item->setForeground (obj->color().faceColor()); + } + + obj->qObjListEntry = item; + ui->objectList->insertItem (ui->objectList->count(), item); + } + + g_isSelectionLocked = false; + updateSelection(); + scrollToSelection(); +} + +// ============================================================================= +// +void MainWindow::scrollToSelection() +{ + if (Selection().isEmpty()) + return; + + LDObjectPtr obj = Selection().last(); + ui->objectList->scrollToItem (obj->qObjListEntry); +} + +// ============================================================================= +// +void MainWindow::slot_selectionChanged() +{ + if (g_isSelectionLocked == true or CurrentDocument() == null) + return; + + LDObjectList priorSelection = Selection(); + + // Get the objects from the object list selection + CurrentDocument()->clearSelection(); + const QList<QListWidgetItem*> items = ui->objectList->selectedItems(); + + for (LDObjectPtr obj : CurrentDocument()->objects()) + { + for (QListWidgetItem* item : items) + { + if (item == obj->qObjListEntry) + { + obj->select(); + break; + } + } + } + + // The select() method calls may have selected additional items (i.e. invertnexts) + // Update it all now. + updateSelection(); + + // Update the GL renderer + LDObjectList compound = priorSelection + Selection(); + RemoveDuplicates (compound); + + for (LDObjectPtr obj : compound) + R()->compileObject (obj); + + R()->update(); +} + +// ============================================================================= +// +void MainWindow::slot_recentFile() +{ + QAction* qAct = static_cast<QAction*> (sender()); + OpenMainModel (qAct->text()); +} + +// ============================================================================= +// +void MainWindow::slot_quickColor() +{ + QToolButton* button = static_cast<QToolButton*> (sender()); + LDColor col = null; + + for (const LDQuickColor& entry : m_quickColors) + { + if (entry.toolButton() == button) + { + col = entry.color(); + break; + } + } + + if (col == null) + return; + + for (LDObjectPtr obj : Selection()) + { + if (not obj->isColored()) + continue; // uncolored object + + obj->setColor (col); + R()->compileObject (obj); + } + + endAction(); + refresh(); +} + +// ============================================================================= +// +int MainWindow::getInsertionPoint() +{ + // If we have a selection, put the item after it. + if (not Selection().isEmpty()) + return Selection().last()->lineNumber() + 1; + + // Otherwise place the object at the end. + return CurrentDocument()->getObjectCount(); +} + +// ============================================================================= +// +void MainWindow::doFullRefresh() +{ + buildObjList(); + m_renderer->hardRefresh(); +} + +// ============================================================================= +// +void MainWindow::refresh() +{ + buildObjList(); + m_renderer->update(); +} + +// ============================================================================= +// +void MainWindow::updateSelection() +{ + g_isSelectionLocked = true; + QItemSelection itemselect; + int top = -1; + int bottom = -1; + + for (LDObjectPtr obj : Selection()) + { + if (obj->qObjListEntry == null) + continue; + + int row = ui->objectList->row (obj->qObjListEntry); + + if (top == -1) + { + top = bottom = row; + } + else + { + if (row != bottom + 1) + { + itemselect.select (ui->objectList->model()->index (top, 0), + ui->objectList->model()->index (bottom, 0)); + top = -1; + } + + bottom = row; + } + } + + if (top != -1) + { + itemselect.select (ui->objectList->model()->index (top, 0), + ui->objectList->model()->index (bottom, 0)); + } + + ui->objectList->selectionModel()->select (itemselect, QItemSelectionModel::ClearAndSelect); + g_isSelectionLocked = false; +} + +// ============================================================================= +// +LDColor MainWindow::getSelectedColor() +{ + LDColor result; + + for (LDObjectPtr obj : Selection()) + { + if (not obj->isColored()) + continue; // doesn't use color + + if (result != null and obj->color() != result) + return null; // No consensus in object color + + if (result == null) + result = obj->color(); + } + + return result; +} + +// ============================================================================= +// +void MainWindow::closeEvent (QCloseEvent* ev) +{ + // Check whether it's safe to close all files. + if (not IsSafeToCloseAll()) + { + ev->ignore(); + return; + } + + // Save the toolbar set + cfg::HiddenToolbars.clear(); + + for (QToolBar* toolbar : findChildren<QToolBar*>()) + { + if (toolbar->isHidden()) + cfg::HiddenToolbars << toolbar->objectName(); + } + + // Save the configuration before leaving. + Config::Save(); + ev->accept(); +} + +// ============================================================================= +// +void MainWindow::spawnContextMenu (const QPoint pos) +{ + const bool single = (Selection().size() == 1); + LDObjectPtr singleObj = single ? Selection().first() : LDObjectPtr(); + + bool hasSubfiles = false; + + for (LDObjectPtr obj : Selection()) + { + if (obj->type() == OBJ_Subfile) + { + hasSubfiles = true; + break; + } + } + + QMenu* contextMenu = new QMenu; + + if (single and singleObj->type() != OBJ_Empty) + { + contextMenu->addAction (ui->actionEdit); + contextMenu->addSeparator(); + } + + contextMenu->addAction (ui->actionCut); + contextMenu->addAction (ui->actionCopy); + contextMenu->addAction (ui->actionPaste); + contextMenu->addAction (ui->actionDelete); + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionSetColor); + + if (single) + contextMenu->addAction (ui->actionEditRaw); + + contextMenu->addAction (ui->actionBorders); + contextMenu->addAction (ui->actionSetOverlay); + contextMenu->addAction (ui->actionClearOverlay); + + if (hasSubfiles) + { + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionOpenSubfiles); + } + + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionModeSelect); + contextMenu->addAction (ui->actionModeDraw); + contextMenu->addAction (ui->actionModeCircle); + + if (not Selection().isEmpty()) + { + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionSubfileSelection); + } + + if (R()->camera() != EFreeCamera) + { + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionSetDrawDepth); + } + + contextMenu->exec (pos); +} + +// ============================================================================= +// +void MainWindow::deleteByColor (LDColor color) +{ + LDObjectList objs; + + for (LDObjectPtr obj : CurrentDocument()->objects()) + { + if (not obj->isColored() or obj->color() != color) + continue; + + objs << obj; + } + + for (LDObjectPtr obj : objs) + obj->destroy(); +} + +// ============================================================================= +// +void MainWindow::updateEditModeActions() +{ + const EditModeType mode = R()->currentEditModeType(); + ui->actionModeSelect->setChecked (mode == EditModeType::Select); + ui->actionModeDraw->setChecked (mode == EditModeType::Draw); + ui->actionModeRectangle->setChecked (mode == EditModeType::Rectangle); + ui->actionModeCircle->setChecked (mode == EditModeType::Circle); + ui->actionModeMagicWand->setChecked (mode == EditModeType::MagicWand); + ui->actionModeLinePath->setChecked (mode == EditModeType::LinePath); +} + +// ============================================================================= +// +void MainWindow::slot_editObject (QListWidgetItem* listitem) +{ + for (LDObjectPtr it : CurrentDocument()->objects()) + { + if (it->qObjListEntry == listitem) + { + AddObjectDialog::staticDialog (it->type(), it); + break; + } + } +} + +// ============================================================================= +// +bool MainWindow::save (LDDocumentPtr doc, bool saveAs) +{ + QString path = doc->fullPath(); + int64 savesize; + + if (saveAs or path.isEmpty()) + { + QString name = doc->defaultName(); + + if (not doc->fullPath().isEmpty()) + name = doc->fullPath(); + elif (not doc->name().isEmpty()) + name = doc->name(); + + name.replace ("\\", "/"); + path = QFileDialog::getSaveFileName (g_win, tr ("Save As"), + name, tr ("LDraw files (*.dat *.ldr)")); + + if (path.isEmpty()) + { + // User didn't give a file name, abort. + return false; + } + } + + if (doc->save (path, &savesize)) + { + if (doc == CurrentDocument()) + updateTitle(); + + print ("Saved to %1 (%2)", path, MakePrettyFileSize (savesize)); + + // Add it to recent files + AddRecentFile (path); + return true; + } + + QString message = format (tr ("Failed to save to %1: %2"), path, strerror (errno)); + + // Tell the user the save failed, and give the option for saving as with it. + QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win); + + // Add a save-as button + QPushButton* saveAsBtn = new QPushButton (tr ("Save As")); + saveAsBtn->setIcon (GetIcon ("file-save-as")); + dlg.addButton (saveAsBtn, QMessageBox::ActionRole); + dlg.setDefaultButton (QMessageBox::Close); + dlg.exec(); + + if (dlg.clickedButton() == saveAsBtn) + return save (doc, true); // yay recursion! + + return false; +} + +void MainWindow::addMessage (QString msg) +{ + m_messageLog->addLine (msg); +} + +// ============================================================================ +void ObjectList::contextMenuEvent (QContextMenuEvent* ev) +{ + g_win->spawnContextMenu (ev->globalPos()); +} + +// ============================================================================= +// +QPixmap GetIcon (QString iconName) +{ + return (QPixmap (format (":/icons/%1.png", iconName))); +} + +// ============================================================================= +// +bool Confirm (const QString& message) +{ + return Confirm (MainWindow::tr ("Confirm"), message); +} + +// ============================================================================= +// +bool Confirm (const QString& title, const QString& message) +{ + return QMessageBox::question (g_win, title, message, + (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes; +} + +// ============================================================================= +// +void Critical (const QString& message) +{ + QMessageBox::critical (g_win, MainWindow::tr ("Error"), message, + (QMessageBox::Close), QMessageBox::Close); +} + +// ============================================================================= +// +QIcon MakeColorIcon (LDColor colinfo, const int size) +{ + // Create an image object and link a painter to it. + QImage img (size, size, QImage::Format_ARGB32); + QPainter paint (&img); + QColor col = colinfo.faceColor(); + + if (colinfo == MainColor()) + { + // Use the user preferences for main color here + col = cfg::MainColor; + col.setAlphaF (cfg::MainColorAlpha); + } + + // 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<LDColor, int> counts; + + for (LDObjectPtr obj : CurrentDocument()->objects()) + { + if (not obj->isColored() or obj->color() == null) + 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) + { + QIcon ico = MakeColorIcon (pair.first, 16); + box->addItem (ico, format ("[%1] %2 (%3 object%4)", + pair.first, pair.first.name(), pair.second, Plural (pair.second))); + box->setItemData (row, pair.first.index()); + + ++row; + } +} + +// ============================================================================= +// +void MainWindow::updateDocumentList() +{ + m_isUpdatingTabs = true; + + while (m_tabBar->count() > 0) + m_tabBar->removeTab (0); + + for (LDDocumentPtr f : LDDocument::explicitDocuments()) + { + // Add an item to the list for this file and store the tab index + // in the document so we can find documents by tab index. + f->setTabIndex (m_tabBar->addTab ("")); + updateDocumentListItem (f); + } + + m_isUpdatingTabs = false; +} + +// ============================================================================= +// +void MainWindow::updateDocumentListItem (LDDocumentPtr doc) +{ + bool oldUpdatingTabs = m_isUpdatingTabs; + m_isUpdatingTabs = true; + + if (doc->tabIndex() == -1) + { + // We don't have a list item for this file, so the list either doesn't + // exist yet or is out of date. Build the list now. + updateDocumentList(); + return; + } + + // If this is the current file, it also needs to be the selected item on + // the list. + if (doc == CurrentDocument()) + m_tabBar->setCurrentIndex (doc->tabIndex()); + + m_tabBar->setTabText (doc->tabIndex(), doc->getDisplayName()); + + // If the document.has unsaved changes, draw a little icon next to it to mark that. + m_tabBar->setTabIcon (doc->tabIndex(), doc->hasUnsavedChanges() ? GetIcon ("file-save") : QIcon()); + m_tabBar->setTabData (doc->tabIndex(), doc->name()); + m_isUpdatingTabs = oldUpdatingTabs; +} + +// ============================================================================= +// +// A file is selected from the list of files on the left of the screen. Find out +// which file was picked and change to it. +// +void MainWindow::changeCurrentFile() +{ + if (m_isUpdatingTabs) + return; + + LDDocumentPtr f; + int tabIndex = m_tabBar->currentIndex(); + + // Find the file pointer of the item that was selected. + for (LDDocumentPtr it : LDDocument::explicitDocuments()) + { + if (it->tabIndex() == tabIndex) + { + f = it; + break; + } + } + + // If we picked the same file we're currently on, we don't need to do + // anything. + if (f == null or f == CurrentDocument()) + return; + + LDDocument::setCurrent (f); +} + +// ============================================================================= +// +void MainWindow::refreshObjectList() +{ +#if 0 + ui->objectList->clear(); + LDDocumentPtr f = getCurrentDocument(); + +for (LDObjectPtr obj : *f) + ui->objectList->addItem (obj->qObjListEntry); + +#endif + + buildObjList(); +} + +// ============================================================================= +// +void MainWindow::updateActions() +{ + if (CurrentDocument() != null and CurrentDocument()->history() != null) + { + History* his = CurrentDocument()->history(); + int pos = his->position(); + ui->actionUndo->setEnabled (pos != -1); + ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1); + } + + ui->actionWireframe->setChecked (cfg::DrawWireframe); + ui->actionAxes->setChecked (cfg::DrawAxes); + ui->actionBFCView->setChecked (cfg::BFCRedGreenView); + ui->actionRandomColors->setChecked (cfg::RandomColors); + ui->actionDrawAngles->setChecked (cfg::DrawAngles); + ui->actionDrawSurfaces->setChecked (cfg::DrawSurfaces); + ui->actionDrawEdgeLines->setChecked (cfg::DrawEdgeLines); + ui->actionDrawConditionalLines->setChecked (cfg::DrawConditionalLines); +} + +// ============================================================================= +// +void MainWindow::updatePrimitives() +{ + PopulatePrimitives (ui->primitives); +} + +// ============================================================================= +// +void MainWindow::closeTab (int tabindex) +{ + LDDocumentPtr doc = FindDocument (m_tabBar->tabData (tabindex).toString()); + + if (doc == null) + return; + + doc->dismiss(); +} + +// ============================================================================= +// +void MainWindow::loadShortcuts (QSettings const* settings) +{ + for (QAction* act : findChildren<QAction*>()) + { + QKeySequence seq = settings->value ("shortcut_" + act->objectName(), act->shortcut()).value<QKeySequence>(); + act->setShortcut (seq); + } +} + +// ============================================================================= +// +void MainWindow::saveShortcuts (QSettings* settings) +{ + applyToActions ([&](QAction* act) + { + QString const key = "shortcut_" + act->objectName(); + + if (g_defaultShortcuts[act] != act->shortcut()) + settings->setValue (key, act->shortcut()); + else + settings->remove (key); + }); +} + +// ============================================================================= +// +void MainWindow::applyToActions (std::function<void(QAction*)> function) +{ + for (QAction* act : findChildren<QAction*>()) + { + if (not act->objectName().isEmpty()) + function (act); + } +} + +// ============================================================================= +// +QKeySequence MainWindow::defaultShortcut (QAction* act) // [static] +{ + return g_defaultShortcuts[act]; +} + +// ============================================================================= +// +bool MainWindow::ringToolHiRes() const +{ + return ui->ringToolHiRes->isChecked(); +} + +// ============================================================================= +// +int MainWindow::ringToolSegments() const +{ + return ui->ringToolSegments->value(); +} + +// ============================================================================= +// +void MainWindow::ringToolHiResClicked (bool checked) +{ + if (checked) + { + ui->ringToolSegments->setMaximum (HighResolution); + ui->ringToolSegments->setValue (ui->ringToolSegments->value() * 3); + } + else + { + ui->ringToolSegments->setValue (ui->ringToolSegments->value() / 3); + ui->ringToolSegments->setMaximum (LowResolution); + } +} + +// ============================================================================= +// +void MainWindow::circleToolSegmentsChanged() +{ + int numerator (ui->ringToolSegments->value()); + int denominator (ui->ringToolHiRes->isChecked() ? HighResolution : LowResolution); + Simplify (numerator, denominator); + ui->ringToolSegmentsLabel->setText (format ("%1 / %2", numerator, denominator)); +} + +// ============================================================================= +// +QImage GetImageFromScreencap (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; +} + +void PopulatePrimitives (QTreeWidget* tw, QString const& selectByDefault) +{ + tw->clear(); + + for (PrimitiveCategory* cat : g_PrimitiveCategories) + { + SubfileListItem* parentItem = new SubfileListItem (tw, null); + parentItem->setText (0, cat->name()); + QList<QTreeWidgetItem*> subfileItems; + + for (Primitive& prim : cat->prims) + { + SubfileListItem* item = new SubfileListItem (parentItem, &prim); + item->setText (0, format ("%1 - %2", prim.name, prim.title)); + subfileItems << item; + + // If this primitive is the one the current object points to, + // select it by default + if (selectByDefault == prim.name) + tw->setCurrentItem (item); + } + + tw->addTopLevelItem (parentItem); + } +}
--- a/src/messageLog.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QTimer> -#include <QDate> -#include "messageLog.h" -#include "glRenderer.h" -#include "mainWindow.h" - -enum -{ - MaxMessages = 5, - ExpireTime = 5000, - FadeTime = 500 -}; - -// ------------------------------------------------------------------------------------------------- -// -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().addMSecs (ExpireTime)) {} - -// ------------------------------------------------------------------------------------------------- -// -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 <= FadeTime) - { - // Message line has not expired but is fading out - alpha = ( (float) msec) / 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() >= 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 (not m_lines[i].update (lineChanged)) - m_lines.removeAt (i--); - - changed |= lineChanged; - } - - if (changed and renderer()) - renderer()->update(); -} - -// ============================================================================= -// -const QList<MessageManager::Line>& MessageManager::getLines() const -{ - return m_lines; -} - -// ============================================================================= -// -void PrintToLog (const QString& msg) -{ - for (QString& a : msg.split ("\n", QString::SkipEmptyParts)) - { - if (g_win != null) - g_win->addMessage (a); - - // Also print it to stdout - fprint (stdout, "%1\n", a); - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/messageLog.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,136 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QTimer> +#include <QDate> +#include "messageLog.h" +#include "glRenderer.h" +#include "mainWindow.h" + +enum +{ + MaxMessages = 5, + ExpireTime = 5000, + FadeTime = 500 +}; + +// ------------------------------------------------------------------------------------------------- +// +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().addMSecs (ExpireTime)) {} + +// ------------------------------------------------------------------------------------------------- +// +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 <= FadeTime) + { + // Message line has not expired but is fading out + alpha = ( (float) msec) / 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() >= 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 (not m_lines[i].update (lineChanged)) + m_lines.removeAt (i--); + + changed |= lineChanged; + } + + if (changed and renderer()) + renderer()->update(); +} + +// ============================================================================= +// +const QList<MessageManager::Line>& MessageManager::getLines() const +{ + return m_lines; +} + +// ============================================================================= +// +void PrintToLog (const QString& msg) +{ + for (QString& a : msg.split ("\n", QString::SkipEmptyParts)) + { + if (g_win != null) + g_win->addMessage (a); + + // Also print it to stdout + fprint (stdout, "%1\n", a); + } +}
--- a/src/miscallenous.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,306 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <math.h> -#include <locale.h> -#include <QColor> -#include "main.h" -#include "miscallenous.h" -#include "mainWindow.h" -#include "dialogs.h" -#include "ldDocument.h" -#include "ui_rotpoint.h" - -// Prime number table. -static const int PrimeNumbers[] = -{ - 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 E10[] = -{ - 1l, - 10l, - 100l, - 1000l, - 10000l, - 100000l, - 1000000l, - 10000000l, - 100000000l, - 1000000000l, -}; - -// ============================================================================= -// -// Grid stuff -// -CFGENTRY (Int, Grid, Grid::Medium) -CFGENTRY (Float, GridCoarseCoordinateSnap, 5.0f) -CFGENTRY (Float, GridCoarseAngleSnap, 45.0f) -CFGENTRY (Float, GridMediumCoordinateSnap, 1.0f) -CFGENTRY (Float, GridMediumAngleSnap, 22.5f) -CFGENTRY (Float, GridFineCoordinateSnap, 0.1f) -CFGENTRY (Float, GridFineAngleSnap, 7.5f) -CFGENTRY (Int, RotationPointType, 0) -CFGENTRY (Vertex, CustomRotationPoint, Origin) - -const GridData Grids[3] = -{ - { "Coarse", &cfg::GridCoarseCoordinateSnap, &cfg::GridCoarseAngleSnap }, - { "Medium", &cfg::GridMediumCoordinateSnap, &cfg::GridMediumAngleSnap }, - { "Fine", &cfg::GridFineCoordinateSnap, &cfg::GridFineAngleSnap }, -}; - -// ============================================================================= -// -// Snap the given coordinate value on the current grid's given axis. -// -double Grid::Snap (double value, const Grid::Config type) -{ - double snapvalue = (type == Coordinate) ? *CurrentGrid().coordinateSnap : *CurrentGrid().angleSnap; - double mult = floor (Abs<double> (value / snapvalue)); - double out = mult * snapvalue; - - if (Abs (value) - (mult * snapvalue) > snapvalue / 2) - out += snapvalue; - - if (value < 0) - out = -out; - - return out; -} - -// ============================================================================= -// -void Simplify (int& numer, int& denom) -{ - bool repeat; - - do - { - repeat = false; - - for (int x = 0; x < countof (PrimeNumbers); x++) - { - int const prime = PrimeNumbers[x]; - - if (numer < prime and denom < prime) - break; - - if ((numer % prime == 0) and (denom % prime == 0)) - { - numer /= prime; - denom /= prime; - repeat = true; - break; - } - } - } while (repeat); -} - -// ============================================================================= -// -Vertex GetRotationPoint (const LDObjectList& objs) -{ - switch (RotationPoint (cfg::RotationPointType)) - { - case RotationPoint::ObjectOrigin: - { - LDBoundingBox box; - - // Calculate center vertex - for (LDObjectPtr obj : objs) - { - if (obj->hasMatrix()) - box << obj.dynamicCast<LDMatrixObject>()->position(); - else - box << obj; - } - - return box.center(); - } - - case RotationPoint::WorldOrigin: - return Origin; - - case RotationPoint::CustomPoint: - return cfg::CustomRotationPoint; - - case RotationPoint::NumValues: break; - } - - return Vertex(); -} - -// ============================================================================= -// -void ConfigureRotationPoint() -{ - QDialog* dlg = new QDialog; - Ui::RotPointUI ui; - ui.setupUi (dlg); - - switch (RotationPoint (cfg::RotationPointType)) - { - case RotationPoint::ObjectOrigin: - ui.objectPoint->setChecked (true); - break; - - case RotationPoint::WorldOrigin: - ui.worldPoint->setChecked (true); - break; - - case RotationPoint::CustomPoint: - ui.customPoint->setChecked (true); - break; - - case RotationPoint::NumValues: break; - } - - ui.customX->setValue (cfg::CustomRotationPoint.x()); - ui.customY->setValue (cfg::CustomRotationPoint.y()); - ui.customZ->setValue (cfg::CustomRotationPoint.z()); - - if (not dlg->exec()) - return; - - cfg::RotationPointType = int ( - (ui.objectPoint->isChecked()) ? RotationPoint::ObjectOrigin : - (ui.worldPoint->isChecked()) ? RotationPoint::WorldOrigin : - RotationPoint::CustomPoint); - - cfg::CustomRotationPoint.setX (ui.customX->value()); - cfg::CustomRotationPoint.setY (ui.customY->value()); - cfg::CustomRotationPoint.setZ (ui.customZ->value()); -} - -// ============================================================================= -// -QString Join (QList<StringFormatArg> vals, QString delim) -{ - QStringList list; - - for (const StringFormatArg& arg : vals) - list << arg.text(); - - return list.join (delim); -} - -// ============================================================================= -// -void RoundToDecimals (double& a, int decimals) -{ - assert (decimals >= 0 and decimals < countof (E10)); - a = round (a * E10[decimals]) / E10[decimals]; -} - -// ============================================================================= -// -void ApplyToMatrix (Matrix& a, ApplyToMatrixFunction func) -{ - for (int i = 0; i < 9; ++i) - func (i, a[i]); -} - -// ============================================================================= -// -void ApplyToMatrix (const Matrix& a, ApplyToMatrixConstFunction func) -{ - for (int i = 0; i < 9; ++i) - func (i, a[i]); -} - -// ============================================================================= -// -double GetCoordinateOf (const Vertex& a, Axis ax) -{ - switch (ax) - { - case X: return a.x(); - case Y: return a.y(); - case Z: return a.z(); - } - - assert (false); - return 0.0; -} - - -// ============================================================================= -// -QString MakePrettyFileSize (qint64 size) -{ - if (size < 1024LL) - return QString::number (size) + " bytes"; - else if (size < (1024LL * 1024LL)) - return QString::number (double (size) / 1024LL, 'f', 1) + " Kb"; - else if (size < (1024LL * 1024LL * 1024LL)) - return QString::number (double (size) / (1024LL * 1024LL), 'f', 1) + " Mb"; - else - return QString::number (double (size) / (1024LL * 1024LL * 1024LL), 'f', 1) + " Gb"; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/miscallenous.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,306 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include <locale.h> +#include <QColor> +#include "main.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "dialogs.h" +#include "ldDocument.h" +#include "ui_rotpoint.h" + +// Prime number table. +static const int PrimeNumbers[] = +{ + 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 E10[] = +{ + 1l, + 10l, + 100l, + 1000l, + 10000l, + 100000l, + 1000000l, + 10000000l, + 100000000l, + 1000000000l, +}; + +// ============================================================================= +// +// Grid stuff +// +CFGENTRY (Int, Grid, Grid::Medium) +CFGENTRY (Float, GridCoarseCoordinateSnap, 5.0f) +CFGENTRY (Float, GridCoarseAngleSnap, 45.0f) +CFGENTRY (Float, GridMediumCoordinateSnap, 1.0f) +CFGENTRY (Float, GridMediumAngleSnap, 22.5f) +CFGENTRY (Float, GridFineCoordinateSnap, 0.1f) +CFGENTRY (Float, GridFineAngleSnap, 7.5f) +CFGENTRY (Int, RotationPointType, 0) +CFGENTRY (Vertex, CustomRotationPoint, Origin) + +const GridData Grids[3] = +{ + { "Coarse", &cfg::GridCoarseCoordinateSnap, &cfg::GridCoarseAngleSnap }, + { "Medium", &cfg::GridMediumCoordinateSnap, &cfg::GridMediumAngleSnap }, + { "Fine", &cfg::GridFineCoordinateSnap, &cfg::GridFineAngleSnap }, +}; + +// ============================================================================= +// +// Snap the given coordinate value on the current grid's given axis. +// +double Grid::Snap (double value, const Grid::Config type) +{ + double snapvalue = (type == Coordinate) ? *CurrentGrid().coordinateSnap : *CurrentGrid().angleSnap; + double mult = floor (Abs<double> (value / snapvalue)); + double out = mult * snapvalue; + + if (Abs (value) - (mult * snapvalue) > snapvalue / 2) + out += snapvalue; + + if (value < 0) + out = -out; + + return out; +} + +// ============================================================================= +// +void Simplify (int& numer, int& denom) +{ + bool repeat; + + do + { + repeat = false; + + for (int x = 0; x < countof (PrimeNumbers); x++) + { + int const prime = PrimeNumbers[x]; + + if (numer < prime and denom < prime) + break; + + if ((numer % prime == 0) and (denom % prime == 0)) + { + numer /= prime; + denom /= prime; + repeat = true; + break; + } + } + } while (repeat); +} + +// ============================================================================= +// +Vertex GetRotationPoint (const LDObjectList& objs) +{ + switch (RotationPoint (cfg::RotationPointType)) + { + case RotationPoint::ObjectOrigin: + { + LDBoundingBox box; + + // Calculate center vertex + for (LDObjectPtr obj : objs) + { + if (obj->hasMatrix()) + box << obj.dynamicCast<LDMatrixObject>()->position(); + else + box << obj; + } + + return box.center(); + } + + case RotationPoint::WorldOrigin: + return Origin; + + case RotationPoint::CustomPoint: + return cfg::CustomRotationPoint; + + case RotationPoint::NumValues: break; + } + + return Vertex(); +} + +// ============================================================================= +// +void ConfigureRotationPoint() +{ + QDialog* dlg = new QDialog; + Ui::RotPointUI ui; + ui.setupUi (dlg); + + switch (RotationPoint (cfg::RotationPointType)) + { + case RotationPoint::ObjectOrigin: + ui.objectPoint->setChecked (true); + break; + + case RotationPoint::WorldOrigin: + ui.worldPoint->setChecked (true); + break; + + case RotationPoint::CustomPoint: + ui.customPoint->setChecked (true); + break; + + case RotationPoint::NumValues: break; + } + + ui.customX->setValue (cfg::CustomRotationPoint.x()); + ui.customY->setValue (cfg::CustomRotationPoint.y()); + ui.customZ->setValue (cfg::CustomRotationPoint.z()); + + if (not dlg->exec()) + return; + + cfg::RotationPointType = int ( + (ui.objectPoint->isChecked()) ? RotationPoint::ObjectOrigin : + (ui.worldPoint->isChecked()) ? RotationPoint::WorldOrigin : + RotationPoint::CustomPoint); + + cfg::CustomRotationPoint.setX (ui.customX->value()); + cfg::CustomRotationPoint.setY (ui.customY->value()); + cfg::CustomRotationPoint.setZ (ui.customZ->value()); +} + +// ============================================================================= +// +QString Join (QList<StringFormatArg> vals, QString delim) +{ + QStringList list; + + for (const StringFormatArg& arg : vals) + list << arg.text(); + + return list.join (delim); +} + +// ============================================================================= +// +void RoundToDecimals (double& a, int decimals) +{ + assert (decimals >= 0 and decimals < countof (E10)); + a = round (a * E10[decimals]) / E10[decimals]; +} + +// ============================================================================= +// +void ApplyToMatrix (Matrix& a, ApplyToMatrixFunction func) +{ + for (int i = 0; i < 9; ++i) + func (i, a[i]); +} + +// ============================================================================= +// +void ApplyToMatrix (const Matrix& a, ApplyToMatrixConstFunction func) +{ + for (int i = 0; i < 9; ++i) + func (i, a[i]); +} + +// ============================================================================= +// +double GetCoordinateOf (const Vertex& a, Axis ax) +{ + switch (ax) + { + case X: return a.x(); + case Y: return a.y(); + case Z: return a.z(); + } + + assert (false); + return 0.0; +} + + +// ============================================================================= +// +QString MakePrettyFileSize (qint64 size) +{ + if (size < 1024LL) + return QString::number (size) + " bytes"; + else if (size < (1024LL * 1024LL)) + return QString::number (double (size) / 1024LL, 'f', 1) + " Kb"; + else if (size < (1024LL * 1024LL * 1024LL)) + return QString::number (double (size) / (1024LL * 1024LL), 'f', 1) + " Mb"; + else + return QString::number (double (size) / (1024LL * 1024LL * 1024LL), 'f', 1) + " Gb"; +}
--- a/src/partDownloader.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,591 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QNetworkAccessManager> -#include <QNetworkRequest> -#include <QNetworkReply> -#include <QDir> -#include <QProgressBar> -#include <QPushButton> -#include <QFileDialog> -#include <QMessageBox> -#include "partDownloader.h" -#include "ui_downloadfrom.h" -#include "basics.h" -#include "mainWindow.h" -#include "ldDocument.h" -#include "glRenderer.h" - -CFGENTRY (String, DownloadFilePath, "") -CFGENTRY (Bool, GuessDownloadPaths, true) -CFGENTRY (Bool, AutoCloseDownloadDialog, true) - -const QString g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/"); - -// ============================================================================= -// -void PartDownloader::staticBegin() -{ - PartDownloader dlg; - - if (not dlg.checkValidPath()) - return; - - dlg.exec(); -} - -// ============================================================================= -// -QString PartDownloader::getDownloadPath() -{ - QString path = cfg::DownloadFilePath; - - if (DIRSLASH[0] != '/') - path.replace (DIRSLASH, "/"); - - return path; -} - -// ============================================================================= -// -PartDownloader::PartDownloader (QWidget* parent) : - QDialog (parent), - m_source (Source (0)) -{ - setForm (new Ui_DownloadFrom); - form()->setupUi (this); - form()->fname->setFocus(); - -#ifdef USE_QT5 - form()->progress->horizontalHeader()->setSectionResizeMode (QHeaderView::Stretch); -#else - form()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); -#endif - - setDownloadButton (new QPushButton (tr ("Download"))); - form()->buttonBox->addButton (downloadButton(), QDialogButtonBox::ActionRole); - getButton (Abort)->setEnabled (false); - - connect (form()->source, SIGNAL (currentIndexChanged (int)), - this, SLOT (sourceChanged (int))); - connect (form()->buttonBox, SIGNAL (clicked (QAbstractButton*)), - this, SLOT (buttonClicked (QAbstractButton*))); -} - -// ============================================================================= -// -PartDownloader::~PartDownloader() -{ - delete form(); -} - -// ============================================================================= -// -bool PartDownloader::checkValidPath() -{ - QString path = getDownloadPath(); - - if (path.isEmpty() or not QDir (path).exists()) - { - QMessageBox::information(this, "Notice", "Please input a path for files to download."); - path = QFileDialog::getExistingDirectory (this, "Path for downloaded files:"); - - if (path.isEmpty()) - return false; - - cfg::DownloadFilePath = path; - } - - return true; -} - -// ============================================================================= -// -QString PartDownloader::getURL() -{ - const Source src = getSource(); - QString dest; - - switch (src) - { - case PartsTracker: - dest = form()->fname->text(); - modifyDestination (dest); - form()->fname->setText (dest); - return g_unofficialLibraryURL + dest; - - case CustomURL: - return form()->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 (not cfg::GuessDownloadPaths) - 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) and (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 (Eq (dest.left (2), "s\\", "s/")) - { - dest.remove (0, 2); - dest.prepend ("parts/s/"); - } - elif (Eq (dest.left (3), "48\\", "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 (not dest.startsWith ("parts/") and not dest.startsWith ("p/")) - dest.prepend ("p/"); -} - -// ============================================================================= -// -PartDownloader::Source PartDownloader::getSource() const -{ - return m_source; -} - -// ============================================================================= -// -void PartDownloader::setSource (Source src) -{ - m_source = src; - form()->source->setCurrentIndex (int (src)); -} - -// ============================================================================= -// -void PartDownloader::sourceChanged (int i) -{ - if (i == CustomURL) - form()->fileNameLabel->setText (tr ("URL:")); - else - form()->fileNameLabel->setText (tr ("File name:")); - - m_source = Source (i); -} - -// ============================================================================= -// -void PartDownloader::buttonClicked (QAbstractButton* btn) -{ - if (btn == getButton (Close)) - { - reject(); - } - elif (btn == getButton (Abort)) - { - setAborted (true); - - for (PartDownloadRequest* req : requests()) - req->abort(); - } - elif (btn == getButton (Download)) - { - QString dest = form()->fname->text(); - setPrimaryFile (LDDocumentPtr()); - setAborted (false); - - if (getSource() == CustomURL) - dest = Basename (getURL()); - - modifyDestination (dest); - - if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest)) - { - const QString overwritemsg = format (tr ("%1 already exists in download directory. Overwrite?"), dest); - if (not Confirm (tr ("Overwrite?"), overwritemsg)) - return; - } - - downloadFile (dest, getURL(), true); - } -} - -// ============================================================================= -// -void PartDownloader::downloadFile (QString dest, QString url, bool primary) -{ - const int row = form()->progress->rowCount(); - - // Don't download files repeadetly. - if (filesToDownload().indexOf (dest) != -1) - return; - - print ("Downloading %1 from %2\n", dest, url); - modifyDestination (dest); - PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); - m_filesToDownload << dest; - m_requests << req; - form()->progress->insertRow (row); - req->setTableRow (row); - req->updateToTable(); - downloadButton()->setEnabled (false); - form()->progress->setEnabled (true); - form()->fname->setEnabled (false); - form()->source->setEnabled (false); - getButton (Close)->setEnabled (false); - getButton (Abort)->setEnabled (true); - getButton (Download)->setEnabled (false); -} - -// ============================================================================= -// -void PartDownloader::downloadFromPartsTracker (QString file) -{ - modifyDestination (file); - downloadFile (file, g_unofficialLibraryURL + file, false); -} - -// ============================================================================= -// -void PartDownloader::checkIfFinished() -{ - bool failed = isAborted(); - - // If there is some download still working, we're not finished. - for (PartDownloadRequest* req : requests()) - { - if (not req->isFinished()) - return; - - if (req->state() == PartDownloadRequest::State::Failed) - failed = true; - } - - for (PartDownloadRequest* req : requests()) - delete req; - - m_requests.clear(); - - // Update everything now - if (primaryFile() != null) - { - LDDocument::setCurrent (primaryFile()); - g_win->doFullRefresh(); - g_win->R()->resetAngles(); - } - - for (LDDocumentPtr f : m_files) - f->reloadAllSubfiles(); - - if (cfg::AutoCloseDownloadDialog and not failed) - { - // Close automatically if desired. - accept(); - } - else - { - // Allow the prompt be closed now. - getButton (Abort)->setEnabled (false); - getButton (Close)->setEnabled (true); - } -} - -// ============================================================================= -// -QPushButton* PartDownloader::getButton (PartDownloader::Button i) -{ - switch (i) - { - case Download: - return downloadButton(); - - case Abort: - return qobject_cast<QPushButton*> (form()->buttonBox->button (QDialogButtonBox::Abort)); - - case Close: - return qobject_cast<QPushButton*> (form()->buttonBox->button (QDialogButtonBox::Close)); - } - - return null; -} - -// ============================================================================= -// -PartDownloadRequest::PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent) : - QObject (parent), - m_state (State::Requesting), - m_prompt (parent), - m_url (url), - m_destinaton (dest), - m_filePath (PartDownloader::getDownloadPath() + DIRSLASH + dest), - m_networkManager (new QNetworkAccessManager), - m_isFirstUpdate (true), - m_isPrimary (primary), - m_filePointer (null) -{ - // Make sure that we have a valid destination. - QString dirpath = Dirname (filePath()); - - QDir dir (dirpath); - - if (not dir.exists()) - { - print ("Creating %1...\n", dirpath); - - if (not dir.mkpath (dirpath)) - Critical (format (tr ("Couldn't create the directory %1!"), dirpath)); - } - - setNetworkReply (networkManager()->get (QNetworkRequest (QUrl (url)))); - connect (networkReply(), SIGNAL (finished()), this, SLOT (downloadFinished())); - connect (networkReply(), SIGNAL (readyRead()), this, SLOT (readyRead())); - connect (networkReply(), SIGNAL (downloadProgress (qint64, qint64)), - this, SLOT (downloadProgress (qint64, qint64))); -} - -// ============================================================================= -// -PartDownloadRequest::~PartDownloadRequest() {} - -// ============================================================================= -// -void PartDownloadRequest::updateToTable() -{ - int const labelcol = PartDownloader::PartLabelColumn; - int const progcol = PartDownloader::ProgressColumn; - QTableWidget* table = prompt()->form()->progress; - QProgressBar* prog; - - switch (state()) - { - case State::Requesting: - case State::Downloading: - { - prog = qobject_cast<QProgressBar*> (table->cellWidget (tableRow(), progcol)); - - if (not prog) - { - prog = new QProgressBar; - table->setCellWidget (tableRow(), progcol, prog); - } - - prog->setRange (0, numBytesTotal()); - prog->setValue (numBytesRead()); - } break; - - case State::Finished: - case State::Failed: - { - const QString text = (state() == State::Finished) - ? "<b><span style=\"color: #080\">FINISHED</span></b>" - : "<b><span style=\"color: #800\">FAILED</span></b>"; - - QLabel* lb = new QLabel (text); - lb->setAlignment (Qt::AlignCenter); - table->setCellWidget (tableRow(), progcol, lb); - } break; - } - - QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (tableRow(), labelcol)); - - if (isFirstUpdate()) - { - lb = new QLabel (format ("<b>%1</b>", destinaton()), table); - table->setCellWidget (tableRow(), labelcol, lb); - } - - // Make sure that the cell is big enough to contain the label - if (table->columnWidth (labelcol) < lb->width()) - table->setColumnWidth (labelcol, lb->width()); - - setFirstUpdate (true); -} - -// ============================================================================= -// -void PartDownloadRequest::downloadFinished() -{ - if (networkReply()->error() != QNetworkReply::NoError) - { - if (isPrimary() and not prompt()->isAborted()) - Critical (networkReply()->errorString()); - - print ("Unable to download %1: %2\n", m_destinaton, networkReply()->errorString()); - setState (State::Failed); - } - elif (state() != State::Failed) - { - setState (State::Finished); - } - - setNumBytesRead (numBytesTotal()); - updateToTable(); - - if (filePointer()) - { - filePointer()->close(); - delete filePointer(); - setFilePointer (null); - - if (state() == State::Failed) - QFile::remove (filePath()); - } - - if (state() != State::Finished) - { - prompt()->checkIfFinished(); - return; - } - - // Try to load this file now. - LDDocumentPtr f = OpenDocument (filePath(), false, not isPrimary()); - - if (f == null) - return; - - // 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 (LDObjectPtr obj : f->objects()) - { - LDErrorPtr err = obj.dynamicCast<LDError>(); - - if ((err == null) or (err->fileReferenced().isEmpty())) - continue; - - QString dest = err->fileReferenced(); - prompt()->downloadFromPartsTracker (dest); - } - - prompt()->addFile (f); - - if (isPrimary()) - { - AddRecentFile (filePath()); - prompt()->setPrimaryFile (f); - } - - prompt()->checkIfFinished(); -} - -// ============================================================================= -// -void PartDownloader::addFile (LDDocumentPtr f) -{ - m_files << f; -} - -// ============================================================================= -// -void PartDownloadRequest::downloadProgress (int64 recv, int64 total) -{ - setNumBytesRead (recv); - setNumBytesTotal (total); - setState (State::Downloading); - updateToTable(); -} - -// ============================================================================= -// -void PartDownloadRequest::readyRead() -{ - if (state() == State::Failed) - return; - - if (filePointer() == null) - { - m_filePath.replace ("\\", "/"); - - // We have already asked the user whether we can overwrite so we're good - // to go here. - setFilePointer (new QFile (filePath().toLocal8Bit())); - - if (not filePointer()->open (QIODevice::WriteOnly)) - { - Critical (format (tr ("Couldn't open %1 for writing: %2"), filePath(), strerror (errno))); - setState (State::Failed); - networkReply()->abort(); - updateToTable(); - prompt()->checkIfFinished(); - return; - } - } - - filePointer()->write (networkReply()->readAll()); -} - -// ============================================================================= -// -bool PartDownloadRequest::isFinished() const -{ - return Eq (state(), State::Finished, State::Failed); -} - -// ============================================================================= -// -void PartDownloadRequest::abort() -{ - networkReply()->abort(); -} - -// ============================================================================= -// -void MainWindow::actionDownloadFrom() -{ - PartDownloader::staticBegin(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/partDownloader.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,591 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QNetworkAccessManager> +#include <QNetworkRequest> +#include <QNetworkReply> +#include <QDir> +#include <QProgressBar> +#include <QPushButton> +#include <QFileDialog> +#include <QMessageBox> +#include "partDownloader.h" +#include "ui_downloadfrom.h" +#include "basics.h" +#include "mainWindow.h" +#include "ldDocument.h" +#include "glRenderer.h" + +CFGENTRY (String, DownloadFilePath, "") +CFGENTRY (Bool, GuessDownloadPaths, true) +CFGENTRY (Bool, AutoCloseDownloadDialog, true) + +const QString g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/"); + +// ============================================================================= +// +void PartDownloader::staticBegin() +{ + PartDownloader dlg; + + if (not dlg.checkValidPath()) + return; + + dlg.exec(); +} + +// ============================================================================= +// +QString PartDownloader::getDownloadPath() +{ + QString path = cfg::DownloadFilePath; + + if (DIRSLASH[0] != '/') + path.replace (DIRSLASH, "/"); + + return path; +} + +// ============================================================================= +// +PartDownloader::PartDownloader (QWidget* parent) : + QDialog (parent), + m_source (Source (0)) +{ + setForm (new Ui_DownloadFrom); + form()->setupUi (this); + form()->fname->setFocus(); + +#ifdef USE_QT5 + form()->progress->horizontalHeader()->setSectionResizeMode (QHeaderView::Stretch); +#else + form()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); +#endif + + setDownloadButton (new QPushButton (tr ("Download"))); + form()->buttonBox->addButton (downloadButton(), QDialogButtonBox::ActionRole); + getButton (Abort)->setEnabled (false); + + connect (form()->source, SIGNAL (currentIndexChanged (int)), + this, SLOT (sourceChanged (int))); + connect (form()->buttonBox, SIGNAL (clicked (QAbstractButton*)), + this, SLOT (buttonClicked (QAbstractButton*))); +} + +// ============================================================================= +// +PartDownloader::~PartDownloader() +{ + delete form(); +} + +// ============================================================================= +// +bool PartDownloader::checkValidPath() +{ + QString path = getDownloadPath(); + + if (path.isEmpty() or not QDir (path).exists()) + { + QMessageBox::information(this, "Notice", "Please input a path for files to download."); + path = QFileDialog::getExistingDirectory (this, "Path for downloaded files:"); + + if (path.isEmpty()) + return false; + + cfg::DownloadFilePath = path; + } + + return true; +} + +// ============================================================================= +// +QString PartDownloader::getURL() +{ + const Source src = getSource(); + QString dest; + + switch (src) + { + case PartsTracker: + dest = form()->fname->text(); + modifyDestination (dest); + form()->fname->setText (dest); + return g_unofficialLibraryURL + dest; + + case CustomURL: + return form()->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 (not cfg::GuessDownloadPaths) + 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) and (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 (Eq (dest.left (2), "s\\", "s/")) + { + dest.remove (0, 2); + dest.prepend ("parts/s/"); + } + elif (Eq (dest.left (3), "48\\", "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 (not dest.startsWith ("parts/") and not dest.startsWith ("p/")) + dest.prepend ("p/"); +} + +// ============================================================================= +// +PartDownloader::Source PartDownloader::getSource() const +{ + return m_source; +} + +// ============================================================================= +// +void PartDownloader::setSource (Source src) +{ + m_source = src; + form()->source->setCurrentIndex (int (src)); +} + +// ============================================================================= +// +void PartDownloader::sourceChanged (int i) +{ + if (i == CustomURL) + form()->fileNameLabel->setText (tr ("URL:")); + else + form()->fileNameLabel->setText (tr ("File name:")); + + m_source = Source (i); +} + +// ============================================================================= +// +void PartDownloader::buttonClicked (QAbstractButton* btn) +{ + if (btn == getButton (Close)) + { + reject(); + } + elif (btn == getButton (Abort)) + { + setAborted (true); + + for (PartDownloadRequest* req : requests()) + req->abort(); + } + elif (btn == getButton (Download)) + { + QString dest = form()->fname->text(); + setPrimaryFile (LDDocumentPtr()); + setAborted (false); + + if (getSource() == CustomURL) + dest = Basename (getURL()); + + modifyDestination (dest); + + if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest)) + { + const QString overwritemsg = format (tr ("%1 already exists in download directory. Overwrite?"), dest); + if (not Confirm (tr ("Overwrite?"), overwritemsg)) + return; + } + + downloadFile (dest, getURL(), true); + } +} + +// ============================================================================= +// +void PartDownloader::downloadFile (QString dest, QString url, bool primary) +{ + const int row = form()->progress->rowCount(); + + // Don't download files repeadetly. + if (filesToDownload().indexOf (dest) != -1) + return; + + print ("Downloading %1 from %2\n", dest, url); + modifyDestination (dest); + PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); + m_filesToDownload << dest; + m_requests << req; + form()->progress->insertRow (row); + req->setTableRow (row); + req->updateToTable(); + downloadButton()->setEnabled (false); + form()->progress->setEnabled (true); + form()->fname->setEnabled (false); + form()->source->setEnabled (false); + getButton (Close)->setEnabled (false); + getButton (Abort)->setEnabled (true); + getButton (Download)->setEnabled (false); +} + +// ============================================================================= +// +void PartDownloader::downloadFromPartsTracker (QString file) +{ + modifyDestination (file); + downloadFile (file, g_unofficialLibraryURL + file, false); +} + +// ============================================================================= +// +void PartDownloader::checkIfFinished() +{ + bool failed = isAborted(); + + // If there is some download still working, we're not finished. + for (PartDownloadRequest* req : requests()) + { + if (not req->isFinished()) + return; + + if (req->state() == PartDownloadRequest::State::Failed) + failed = true; + } + + for (PartDownloadRequest* req : requests()) + delete req; + + m_requests.clear(); + + // Update everything now + if (primaryFile() != null) + { + LDDocument::setCurrent (primaryFile()); + g_win->doFullRefresh(); + g_win->R()->resetAngles(); + } + + for (LDDocumentPtr f : m_files) + f->reloadAllSubfiles(); + + if (cfg::AutoCloseDownloadDialog and not failed) + { + // Close automatically if desired. + accept(); + } + else + { + // Allow the prompt be closed now. + getButton (Abort)->setEnabled (false); + getButton (Close)->setEnabled (true); + } +} + +// ============================================================================= +// +QPushButton* PartDownloader::getButton (PartDownloader::Button i) +{ + switch (i) + { + case Download: + return downloadButton(); + + case Abort: + return qobject_cast<QPushButton*> (form()->buttonBox->button (QDialogButtonBox::Abort)); + + case Close: + return qobject_cast<QPushButton*> (form()->buttonBox->button (QDialogButtonBox::Close)); + } + + return null; +} + +// ============================================================================= +// +PartDownloadRequest::PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent) : + QObject (parent), + m_state (State::Requesting), + m_prompt (parent), + m_url (url), + m_destinaton (dest), + m_filePath (PartDownloader::getDownloadPath() + DIRSLASH + dest), + m_networkManager (new QNetworkAccessManager), + m_isFirstUpdate (true), + m_isPrimary (primary), + m_filePointer (null) +{ + // Make sure that we have a valid destination. + QString dirpath = Dirname (filePath()); + + QDir dir (dirpath); + + if (not dir.exists()) + { + print ("Creating %1...\n", dirpath); + + if (not dir.mkpath (dirpath)) + Critical (format (tr ("Couldn't create the directory %1!"), dirpath)); + } + + setNetworkReply (networkManager()->get (QNetworkRequest (QUrl (url)))); + connect (networkReply(), SIGNAL (finished()), this, SLOT (downloadFinished())); + connect (networkReply(), SIGNAL (readyRead()), this, SLOT (readyRead())); + connect (networkReply(), SIGNAL (downloadProgress (qint64, qint64)), + this, SLOT (downloadProgress (qint64, qint64))); +} + +// ============================================================================= +// +PartDownloadRequest::~PartDownloadRequest() {} + +// ============================================================================= +// +void PartDownloadRequest::updateToTable() +{ + int const labelcol = PartDownloader::PartLabelColumn; + int const progcol = PartDownloader::ProgressColumn; + QTableWidget* table = prompt()->form()->progress; + QProgressBar* prog; + + switch (state()) + { + case State::Requesting: + case State::Downloading: + { + prog = qobject_cast<QProgressBar*> (table->cellWidget (tableRow(), progcol)); + + if (not prog) + { + prog = new QProgressBar; + table->setCellWidget (tableRow(), progcol, prog); + } + + prog->setRange (0, numBytesTotal()); + prog->setValue (numBytesRead()); + } break; + + case State::Finished: + case State::Failed: + { + const QString text = (state() == State::Finished) + ? "<b><span style=\"color: #080\">FINISHED</span></b>" + : "<b><span style=\"color: #800\">FAILED</span></b>"; + + QLabel* lb = new QLabel (text); + lb->setAlignment (Qt::AlignCenter); + table->setCellWidget (tableRow(), progcol, lb); + } break; + } + + QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (tableRow(), labelcol)); + + if (isFirstUpdate()) + { + lb = new QLabel (format ("<b>%1</b>", destinaton()), table); + table->setCellWidget (tableRow(), labelcol, lb); + } + + // Make sure that the cell is big enough to contain the label + if (table->columnWidth (labelcol) < lb->width()) + table->setColumnWidth (labelcol, lb->width()); + + setFirstUpdate (true); +} + +// ============================================================================= +// +void PartDownloadRequest::downloadFinished() +{ + if (networkReply()->error() != QNetworkReply::NoError) + { + if (isPrimary() and not prompt()->isAborted()) + Critical (networkReply()->errorString()); + + print ("Unable to download %1: %2\n", m_destinaton, networkReply()->errorString()); + setState (State::Failed); + } + elif (state() != State::Failed) + { + setState (State::Finished); + } + + setNumBytesRead (numBytesTotal()); + updateToTable(); + + if (filePointer()) + { + filePointer()->close(); + delete filePointer(); + setFilePointer (null); + + if (state() == State::Failed) + QFile::remove (filePath()); + } + + if (state() != State::Finished) + { + prompt()->checkIfFinished(); + return; + } + + // Try to load this file now. + LDDocumentPtr f = OpenDocument (filePath(), false, not isPrimary()); + + if (f == null) + return; + + // 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 (LDObjectPtr obj : f->objects()) + { + LDErrorPtr err = obj.dynamicCast<LDError>(); + + if ((err == null) or (err->fileReferenced().isEmpty())) + continue; + + QString dest = err->fileReferenced(); + prompt()->downloadFromPartsTracker (dest); + } + + prompt()->addFile (f); + + if (isPrimary()) + { + AddRecentFile (filePath()); + prompt()->setPrimaryFile (f); + } + + prompt()->checkIfFinished(); +} + +// ============================================================================= +// +void PartDownloader::addFile (LDDocumentPtr f) +{ + m_files << f; +} + +// ============================================================================= +// +void PartDownloadRequest::downloadProgress (int64 recv, int64 total) +{ + setNumBytesRead (recv); + setNumBytesTotal (total); + setState (State::Downloading); + updateToTable(); +} + +// ============================================================================= +// +void PartDownloadRequest::readyRead() +{ + if (state() == State::Failed) + return; + + if (filePointer() == null) + { + m_filePath.replace ("\\", "/"); + + // We have already asked the user whether we can overwrite so we're good + // to go here. + setFilePointer (new QFile (filePath().toLocal8Bit())); + + if (not filePointer()->open (QIODevice::WriteOnly)) + { + Critical (format (tr ("Couldn't open %1 for writing: %2"), filePath(), strerror (errno))); + setState (State::Failed); + networkReply()->abort(); + updateToTable(); + prompt()->checkIfFinished(); + return; + } + } + + filePointer()->write (networkReply()->readAll()); +} + +// ============================================================================= +// +bool PartDownloadRequest::isFinished() const +{ + return Eq (state(), State::Finished, State::Failed); +} + +// ============================================================================= +// +void PartDownloadRequest::abort() +{ + networkReply()->abort(); +} + +// ============================================================================= +// +void MainWindow::actionDownloadFrom() +{ + PartDownloader::staticBegin(); +}
--- a/src/primitives.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,715 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QDir> -#include <QRegExp> -#include <QFileDialog> -#include "ldDocument.h" -#include "mainWindow.h" -#include "primitives.h" -#include "ui_makeprim.h" -#include "miscallenous.h" -#include "colors.h" - -QList<PrimitiveCategory*> g_PrimitiveCategories; -QList<Primitive> g_primitives; -static PrimitiveScanner* g_activeScanner = null; -PrimitiveCategory* g_unmatched = null; - -EXTERN_CFGENTRY (String, DefaultName) -EXTERN_CFGENTRY (String, DefaultUser) -EXTERN_CFGENTRY (Int, DefaultLicense) - -static const QStringList g_radialNameRoots = -{ - "edge", - "cyli", - "disc", - "ndis", - "ring", - "con" -}; - -PrimitiveScanner* ActivePrimitiveScanner() -{ - return g_activeScanner; -} - -// ============================================================================= -// -void LoadPrimitives() -{ - // Try to load prims.cfg - QFile conf (Config::FilePath ("prims.cfg")); - - if (not conf.open (QIODevice::ReadOnly)) - { - // No prims.cfg, build it - PrimitiveScanner::start(); - } - else - { - while (not conf.atEnd()) - { - QString line = conf.readLine(); - - if (line.endsWith ("\n")) - line.chop (1); - - if (line.endsWith ("\r")) - line.chop (1); - - int space = line.indexOf (" "); - - if (space == -1) - continue; - - Primitive info; - info.name = line.left (space); - info.title = line.mid (space + 1); - g_primitives << info; - } - - PrimitiveCategory::populateCategories(); - print ("%1 primitives loaded.\n", g_primitives.size()); - } -} - -// ============================================================================= -// -static void GetRecursiveFilenames (QDir dir, QList<QString>& fnames) -{ - QFileInfoList flist = dir.entryInfoList (QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); - - for (const QFileInfo& info : flist) - { - if (info.isDir()) - GetRecursiveFilenames (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(); - GetRecursiveFilenames (dir, m_files); - emit starting (m_files.size()); - print ("Scanning primitives..."); -} - -// ============================================================================= -// -PrimitiveScanner::~PrimitiveScanner() -{ - g_activeScanner = null; -} - -// ============================================================================= -// -void PrimitiveScanner::work() -{ - int j = Min (m_i + 100, m_files.size()); - - for (; m_i < j; ++m_i) - { - QString fname = m_files[m_i]; - QFile f (fname); - - if (not f.open (QIODevice::ReadOnly)) - continue; - - Primitive info; - info.name = fname.mid (m_baselen + 1); // make full path relative - info.name.replace ('/', '\\'); // use DOS backslashes, they're expected - info.category = null; - QByteArray titledata = f.readLine(); - - if (titledata != QByteArray()) - info.title = QString::fromUtf8 (titledata); - - info.title = info.title.simplified(); - - if (Q_LIKELY (info.title[0] == '0')) - { - info.title.remove (0, 1); // remove 0 - info.title = info.title.simplified(); - } - - m_prims << info; - } - - if (m_i == m_files.size()) - { - // Done with primitives, now save to a config file - QString path = Config::FilePath ("prims.cfg"); - QFile conf (path); - - if (not conf.open (QIODevice::WriteOnly | QIODevice::Text)) - Critical (format ("Couldn't write primitive list %1: %2", - path, conf.errorString())); - else - { - for (Primitive& info : m_prims) - fprint (conf, "%1 %2\r\n", info.name, info.title); - - conf.close(); - } - - g_primitives = m_prims; - PrimitiveCategory::populateCategories(); - print ("%1 primitives scanned", g_primitives.size()); - g_activeScanner = null; - emit workDone(); - deleteLater(); - } - else - { - // Defer to event loop, pick up the work later - emit update (m_i); - QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection); - } -} - -// ============================================================================= -// -void PrimitiveScanner::start() -{ - if (g_activeScanner) - return; - - PrimitiveCategory::loadCategories(); - PrimitiveScanner* scanner = new PrimitiveScanner; - scanner->work(); -} - -// ============================================================================= -// -PrimitiveCategory::PrimitiveCategory (QString name, QObject* parent) : - QObject (parent), - m_name (name) {} - -// ============================================================================= -// -void PrimitiveCategory::populateCategories() -{ - loadCategories(); - - for (PrimitiveCategory* cat : g_PrimitiveCategories) - cat->prims.clear(); - - for (Primitive& prim : g_primitives) - { - bool matched = false; - prim.category = null; - - // Go over the categories and their regexes, if and when there's a match, - // the primitive's category is set to the category the regex beloings to. - for (PrimitiveCategory* cat : g_PrimitiveCategories) - { - for (RegexEntry& entry : cat->regexes) - { - switch (entry.type) - { - case EFilenameRegex: - { - // f-regex, check against filename - matched = entry.regex.exactMatch (prim.name); - } break; - - case ETitleRegex: - { - // t-regex, check against title - matched = entry.regex.exactMatch (prim.title); - } break; - } - - if (matched) - { - prim.category = cat; - break; - } - } - - // Drop out if a category was decided on. - if (prim.category != null) - break; - } - - // If there was a match, add the primitive to the category. - // Otherwise, add it to the list of unmatched primitives. - if (prim.category != null) - prim.category->prims << prim; - else - g_unmatched->prims << prim; - } - - // Sort the categories. Note that we do this here because we need the existing - // order for regex matching. - qSort (g_PrimitiveCategories.begin(), g_PrimitiveCategories.end(), - [](PrimitiveCategory* const& a, PrimitiveCategory* const& b) -> bool - { - return a->name() < b->name(); - }); -} - -// ============================================================================= -// -void PrimitiveCategory::loadCategories() -{ - for (PrimitiveCategory* cat : g_PrimitiveCategories) - delete cat; - - g_PrimitiveCategories.clear(); - QString path = Config::DirectoryPath() + "primregexps.cfg"; - - if (not QFile::exists (path)) - path = ":/data/primitive-categories.cfg"; - - QFile f (path); - - if (not f.open (QIODevice::ReadOnly)) - { - Critical (format (QObject::tr ("Failed to open primitive categories: %1"), f.errorString())); - return; - } - - PrimitiveCategory* cat = null; - - while (not f.atEnd()) - { - QString line = f.readLine(); - int colon; - - if (line.endsWith ("\n")) - line.chop (1); - - if (line.length() == 0 or line[0] == '#') - continue; - - if ((colon = line.indexOf (":")) == -1) - { - if (cat and cat->isValidToInclude()) - g_PrimitiveCategories << cat; - - cat = new PrimitiveCategory (line); - } - elif (cat != null) - { - QString cmd = line.left (colon); - RegexType type = EFilenameRegex; - - if (cmd == "f") - type = EFilenameRegex; - elif (cmd == "t") - type = ETitleRegex; - else - { - print (tr ("Warning: unknown command \"%1\" on line \"%2\""), cmd, line); - continue; - } - - QRegExp regex (line.mid (colon + 1)); - RegexEntry entry = { regex, type }; - cat->regexes << entry; - } - else - print ("Warning: Rules given before the first category name"); - } - - if (cat->isValidToInclude()) - g_PrimitiveCategories << cat; - - // Add a category for unmatched primitives. - // Note: if this function is called the second time, g_unmatched has been - // deleted at the beginning of the function and is dangling at this point. - g_unmatched = new PrimitiveCategory (tr ("Other")); - g_PrimitiveCategories << g_unmatched; - f.close(); -} - -// ============================================================================= -// -bool PrimitiveCategory::isValidToInclude() -{ - if (regexes.isEmpty()) - { - print (tr ("Warning: category \"%1\" left without patterns"), name()); - deleteLater(); - return false; - } - - return true; -} - -// ============================================================================= -// -bool IsPrimitiveLoaderBusy() -{ - return g_activeScanner != null; -} - -// ============================================================================= -// -static double GetRadialPoint (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 * GetRadialPoint (i, divs, cos), - x1 = radius * GetRadialPoint (i + 1, divs, cos), - z0 = radius * GetRadialPoint (i, divs, sin), - z1 = radius * GetRadialPoint (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); - - LDLinePtr line (LDSpawn<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); - - LDQuadPtr quad (LDSpawn<LDQuad> (v0, v1, v2, v3)); - quad->setColor (MainColor()); - - if (type == Cylinder) - quad->invert(); - - objs << quad; - - if (type == Cylinder or 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. - LDTrianglePtr seg (LDSpawn<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 and condLineSegs.size() != 0) - condLineSegs << segs; - - for (int i : condLineSegs) - { - Vertex v0 (GetRadialPoint (i, divs, cos), 0.0f, GetRadialPoint (i, divs, sin)), - v1, - v2 (GetRadialPoint (i + 1, divs, cos), 0.0f, GetRadialPoint (i + 1, divs, sin)), - v3 (GetRadialPoint (i - 1, divs, cos), 0.0f, GetRadialPoint (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.setX (v0.x() * num); - v0.setY (1.0); - v0.setZ (v0.z() * num); - } - - LDCondLinePtr line = (LDSpawn<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 MakeRadialFileName (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 == LowResolution) ? "" : format ("%1/", divs); - QString frac = format ("%1-%2", numer, denom); - QString root = g_radialNameRoots[type]; - QString numstr = (type == Ring or type == Cone) ? format ("%1", num) : ""; - - // Truncate the root if necessary (7-16rin4.dat for instance). - // However, always keep the root at least 2 characters. - int extra = (frac.length() + numstr.length() + root.length()) - 8; - root.chop (Clamp (extra, 0, 2)); - - // Stick them all together and return the result. - return prefix + frac + root + numstr + ".dat"; -} - -// ============================================================================= -// -LDDocumentPtr GeneratePrimitive (PrimitiveType type, int segs, int divs, int num) -{ - // Make the description - QString frac = QString::number ((float) segs / divs); - QString name = MakeRadialFileName (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 or type == Cone) - { - QString spacing = - (num < 10) ? " " : - (num < 100) ? " " : ""; - - descr = format ("%1 %2%3 x %4", PrimitiveTypeName (type), spacing, num, frac); - } - else - descr = format ("%1 %2", PrimitiveTypeName (type), frac); - - // Prepend "Hi-Res" if 48/ primitive. - if (divs == HighResolution) - descr.insert (0, "Hi-Res "); - - LDDocumentPtr f = LDDocument::createNew(); - f->setDefaultName (name); - - QString author = APPNAME; - QString license = ""; - - if (not cfg::DefaultName.isEmpty()) - { - license = PreferredLicenseText(); - author = format ("%1 [%2]", cfg::DefaultName, cfg::DefaultUser); - } - - LDObjectList objs; - - objs << LDSpawn<LDComment> (descr) - << LDSpawn<LDComment> (format ("Name: %1", name)) - << LDSpawn<LDComment> (format ("Author: %1", author)) - << LDSpawn<LDComment> (format ("!LDRAW_ORG Unofficial_%1Primitive", - divs == HighResolution ? "48_" : "")) - << LDSpawn<LDComment> (license) - << LDSpawn<LDEmpty>() - << LDSpawn<LDBFC> (BFCStatement::CertifyCCW) - << LDSpawn<LDEmpty>(); - - f->setImplicit (false); - f->history()->setIgnoring (false); - f->addObjects (objs); - f->addObjects (MakePrimitive (type, segs, divs, num)); - f->addHistoryStep(); - return f; -} - -// ============================================================================= -// -LDDocumentPtr GetPrimitive (PrimitiveType type, int segs, int divs, int num) -{ - QString name = MakeRadialFileName (type, segs, divs, num); - LDDocumentPtr 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 ? HighResolution : LowResolution); - - // If the current value is 16 and we switch to hi-res, default the - // spinbox to 48. - if (on and ui->sb_segs->value() == LowResolution) - ui->sb_segs->setValue (HighResolution); -} - -// ============================================================================= -// -void MainWindow::actionMakePrimitive() -{ - PrimitivePrompt* dlg = new PrimitivePrompt (g_win); - - if (not dlg->exec()) - return; - - int segs = dlg->ui->sb_segs->value(); - int divs = dlg->ui->cb_hires->isChecked() ? HighResolution : LowResolution; - 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; - - LDDocumentPtr f = GeneratePrimitive (type, segs, divs, num); - f->setImplicit (false); - g_win->save (f, false); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/primitives.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,715 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QDir> +#include <QRegExp> +#include <QFileDialog> +#include "ldDocument.h" +#include "mainWindow.h" +#include "primitives.h" +#include "ui_makeprim.h" +#include "miscallenous.h" +#include "colors.h" + +QList<PrimitiveCategory*> g_PrimitiveCategories; +QList<Primitive> g_primitives; +static PrimitiveScanner* g_activeScanner = null; +PrimitiveCategory* g_unmatched = null; + +EXTERN_CFGENTRY (String, DefaultName) +EXTERN_CFGENTRY (String, DefaultUser) +EXTERN_CFGENTRY (Int, DefaultLicense) + +static const QStringList g_radialNameRoots = +{ + "edge", + "cyli", + "disc", + "ndis", + "ring", + "con" +}; + +PrimitiveScanner* ActivePrimitiveScanner() +{ + return g_activeScanner; +} + +// ============================================================================= +// +void LoadPrimitives() +{ + // Try to load prims.cfg + QFile conf (Config::FilePath ("prims.cfg")); + + if (not conf.open (QIODevice::ReadOnly)) + { + // No prims.cfg, build it + PrimitiveScanner::start(); + } + else + { + while (not conf.atEnd()) + { + QString line = conf.readLine(); + + if (line.endsWith ("\n")) + line.chop (1); + + if (line.endsWith ("\r")) + line.chop (1); + + int space = line.indexOf (" "); + + if (space == -1) + continue; + + Primitive info; + info.name = line.left (space); + info.title = line.mid (space + 1); + g_primitives << info; + } + + PrimitiveCategory::populateCategories(); + print ("%1 primitives loaded.\n", g_primitives.size()); + } +} + +// ============================================================================= +// +static void GetRecursiveFilenames (QDir dir, QList<QString>& fnames) +{ + QFileInfoList flist = dir.entryInfoList (QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + + for (const QFileInfo& info : flist) + { + if (info.isDir()) + GetRecursiveFilenames (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(); + GetRecursiveFilenames (dir, m_files); + emit starting (m_files.size()); + print ("Scanning primitives..."); +} + +// ============================================================================= +// +PrimitiveScanner::~PrimitiveScanner() +{ + g_activeScanner = null; +} + +// ============================================================================= +// +void PrimitiveScanner::work() +{ + int j = Min (m_i + 100, m_files.size()); + + for (; m_i < j; ++m_i) + { + QString fname = m_files[m_i]; + QFile f (fname); + + if (not f.open (QIODevice::ReadOnly)) + continue; + + Primitive info; + info.name = fname.mid (m_baselen + 1); // make full path relative + info.name.replace ('/', '\\'); // use DOS backslashes, they're expected + info.category = null; + QByteArray titledata = f.readLine(); + + if (titledata != QByteArray()) + info.title = QString::fromUtf8 (titledata); + + info.title = info.title.simplified(); + + if (Q_LIKELY (info.title[0] == '0')) + { + info.title.remove (0, 1); // remove 0 + info.title = info.title.simplified(); + } + + m_prims << info; + } + + if (m_i == m_files.size()) + { + // Done with primitives, now save to a config file + QString path = Config::FilePath ("prims.cfg"); + QFile conf (path); + + if (not conf.open (QIODevice::WriteOnly | QIODevice::Text)) + Critical (format ("Couldn't write primitive list %1: %2", + path, conf.errorString())); + else + { + for (Primitive& info : m_prims) + fprint (conf, "%1 %2\r\n", info.name, info.title); + + conf.close(); + } + + g_primitives = m_prims; + PrimitiveCategory::populateCategories(); + print ("%1 primitives scanned", g_primitives.size()); + g_activeScanner = null; + emit workDone(); + deleteLater(); + } + else + { + // Defer to event loop, pick up the work later + emit update (m_i); + QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection); + } +} + +// ============================================================================= +// +void PrimitiveScanner::start() +{ + if (g_activeScanner) + return; + + PrimitiveCategory::loadCategories(); + PrimitiveScanner* scanner = new PrimitiveScanner; + scanner->work(); +} + +// ============================================================================= +// +PrimitiveCategory::PrimitiveCategory (QString name, QObject* parent) : + QObject (parent), + m_name (name) {} + +// ============================================================================= +// +void PrimitiveCategory::populateCategories() +{ + loadCategories(); + + for (PrimitiveCategory* cat : g_PrimitiveCategories) + cat->prims.clear(); + + for (Primitive& prim : g_primitives) + { + bool matched = false; + prim.category = null; + + // Go over the categories and their regexes, if and when there's a match, + // the primitive's category is set to the category the regex beloings to. + for (PrimitiveCategory* cat : g_PrimitiveCategories) + { + for (RegexEntry& entry : cat->regexes) + { + switch (entry.type) + { + case EFilenameRegex: + { + // f-regex, check against filename + matched = entry.regex.exactMatch (prim.name); + } break; + + case ETitleRegex: + { + // t-regex, check against title + matched = entry.regex.exactMatch (prim.title); + } break; + } + + if (matched) + { + prim.category = cat; + break; + } + } + + // Drop out if a category was decided on. + if (prim.category != null) + break; + } + + // If there was a match, add the primitive to the category. + // Otherwise, add it to the list of unmatched primitives. + if (prim.category != null) + prim.category->prims << prim; + else + g_unmatched->prims << prim; + } + + // Sort the categories. Note that we do this here because we need the existing + // order for regex matching. + qSort (g_PrimitiveCategories.begin(), g_PrimitiveCategories.end(), + [](PrimitiveCategory* const& a, PrimitiveCategory* const& b) -> bool + { + return a->name() < b->name(); + }); +} + +// ============================================================================= +// +void PrimitiveCategory::loadCategories() +{ + for (PrimitiveCategory* cat : g_PrimitiveCategories) + delete cat; + + g_PrimitiveCategories.clear(); + QString path = Config::DirectoryPath() + "primregexps.cfg"; + + if (not QFile::exists (path)) + path = ":/data/primitive-categories.cfg"; + + QFile f (path); + + if (not f.open (QIODevice::ReadOnly)) + { + Critical (format (QObject::tr ("Failed to open primitive categories: %1"), f.errorString())); + return; + } + + PrimitiveCategory* cat = null; + + while (not f.atEnd()) + { + QString line = f.readLine(); + int colon; + + if (line.endsWith ("\n")) + line.chop (1); + + if (line.length() == 0 or line[0] == '#') + continue; + + if ((colon = line.indexOf (":")) == -1) + { + if (cat and cat->isValidToInclude()) + g_PrimitiveCategories << cat; + + cat = new PrimitiveCategory (line); + } + elif (cat != null) + { + QString cmd = line.left (colon); + RegexType type = EFilenameRegex; + + if (cmd == "f") + type = EFilenameRegex; + elif (cmd == "t") + type = ETitleRegex; + else + { + print (tr ("Warning: unknown command \"%1\" on line \"%2\""), cmd, line); + continue; + } + + QRegExp regex (line.mid (colon + 1)); + RegexEntry entry = { regex, type }; + cat->regexes << entry; + } + else + print ("Warning: Rules given before the first category name"); + } + + if (cat->isValidToInclude()) + g_PrimitiveCategories << cat; + + // Add a category for unmatched primitives. + // Note: if this function is called the second time, g_unmatched has been + // deleted at the beginning of the function and is dangling at this point. + g_unmatched = new PrimitiveCategory (tr ("Other")); + g_PrimitiveCategories << g_unmatched; + f.close(); +} + +// ============================================================================= +// +bool PrimitiveCategory::isValidToInclude() +{ + if (regexes.isEmpty()) + { + print (tr ("Warning: category \"%1\" left without patterns"), name()); + deleteLater(); + return false; + } + + return true; +} + +// ============================================================================= +// +bool IsPrimitiveLoaderBusy() +{ + return g_activeScanner != null; +} + +// ============================================================================= +// +static double GetRadialPoint (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 * GetRadialPoint (i, divs, cos), + x1 = radius * GetRadialPoint (i + 1, divs, cos), + z0 = radius * GetRadialPoint (i, divs, sin), + z1 = radius * GetRadialPoint (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); + + LDLinePtr line (LDSpawn<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); + + LDQuadPtr quad (LDSpawn<LDQuad> (v0, v1, v2, v3)); + quad->setColor (MainColor()); + + if (type == Cylinder) + quad->invert(); + + objs << quad; + + if (type == Cylinder or 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. + LDTrianglePtr seg (LDSpawn<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 and condLineSegs.size() != 0) + condLineSegs << segs; + + for (int i : condLineSegs) + { + Vertex v0 (GetRadialPoint (i, divs, cos), 0.0f, GetRadialPoint (i, divs, sin)), + v1, + v2 (GetRadialPoint (i + 1, divs, cos), 0.0f, GetRadialPoint (i + 1, divs, sin)), + v3 (GetRadialPoint (i - 1, divs, cos), 0.0f, GetRadialPoint (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.setX (v0.x() * num); + v0.setY (1.0); + v0.setZ (v0.z() * num); + } + + LDCondLinePtr line = (LDSpawn<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 MakeRadialFileName (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 == LowResolution) ? "" : format ("%1/", divs); + QString frac = format ("%1-%2", numer, denom); + QString root = g_radialNameRoots[type]; + QString numstr = (type == Ring or type == Cone) ? format ("%1", num) : ""; + + // Truncate the root if necessary (7-16rin4.dat for instance). + // However, always keep the root at least 2 characters. + int extra = (frac.length() + numstr.length() + root.length()) - 8; + root.chop (Clamp (extra, 0, 2)); + + // Stick them all together and return the result. + return prefix + frac + root + numstr + ".dat"; +} + +// ============================================================================= +// +LDDocumentPtr GeneratePrimitive (PrimitiveType type, int segs, int divs, int num) +{ + // Make the description + QString frac = QString::number ((float) segs / divs); + QString name = MakeRadialFileName (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 or type == Cone) + { + QString spacing = + (num < 10) ? " " : + (num < 100) ? " " : ""; + + descr = format ("%1 %2%3 x %4", PrimitiveTypeName (type), spacing, num, frac); + } + else + descr = format ("%1 %2", PrimitiveTypeName (type), frac); + + // Prepend "Hi-Res" if 48/ primitive. + if (divs == HighResolution) + descr.insert (0, "Hi-Res "); + + LDDocumentPtr f = LDDocument::createNew(); + f->setDefaultName (name); + + QString author = APPNAME; + QString license = ""; + + if (not cfg::DefaultName.isEmpty()) + { + license = PreferredLicenseText(); + author = format ("%1 [%2]", cfg::DefaultName, cfg::DefaultUser); + } + + LDObjectList objs; + + objs << LDSpawn<LDComment> (descr) + << LDSpawn<LDComment> (format ("Name: %1", name)) + << LDSpawn<LDComment> (format ("Author: %1", author)) + << LDSpawn<LDComment> (format ("!LDRAW_ORG Unofficial_%1Primitive", + divs == HighResolution ? "48_" : "")) + << LDSpawn<LDComment> (license) + << LDSpawn<LDEmpty>() + << LDSpawn<LDBFC> (BFCStatement::CertifyCCW) + << LDSpawn<LDEmpty>(); + + f->setImplicit (false); + f->history()->setIgnoring (false); + f->addObjects (objs); + f->addObjects (MakePrimitive (type, segs, divs, num)); + f->addHistoryStep(); + return f; +} + +// ============================================================================= +// +LDDocumentPtr GetPrimitive (PrimitiveType type, int segs, int divs, int num) +{ + QString name = MakeRadialFileName (type, segs, divs, num); + LDDocumentPtr 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 ? HighResolution : LowResolution); + + // If the current value is 16 and we switch to hi-res, default the + // spinbox to 48. + if (on and ui->sb_segs->value() == LowResolution) + ui->sb_segs->setValue (HighResolution); +} + +// ============================================================================= +// +void MainWindow::actionMakePrimitive() +{ + PrimitivePrompt* dlg = new PrimitivePrompt (g_win); + + if (not dlg->exec()) + return; + + int segs = dlg->ui->sb_segs->value(); + int divs = dlg->ui->cb_hires->isChecked() ? HighResolution : LowResolution; + 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; + + LDDocumentPtr f = GeneratePrimitive (type, segs, divs, num); + f->setImplicit (false); + g_win->save (f, false); +}
--- a/src/radioGroup.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -// I still find the radio group useful... find a way to use this in Designer. -// I probably need to look into how to make Designer plugins. -// TODO: try make this usable in Designer - -#include <QBoxLayout> -#include <QRadioButton> -#include <QButtonGroup> -#include <QCheckBox> -#include <map> - -#include "radioGroup.h" - -// ============================================================================= -// -RadioGroup::RadioGroup (const QString& title, QWidget* parent) : QGroupBox (title, parent) -{ - init (Qt::Vertical); -} - -// ============================================================================= -// -QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false) -{ - return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom; -} - -// ============================================================================= -// -bool RadioGroup::isChecked (int n) const -{ - return m_buttonGroup->checkedId() == n; -} - -// ============================================================================= -// -void RadioGroup::init (Qt::Orientation orient) -{ - m_vert = orient == Qt::Vertical; - - m_buttonGroup = new QButtonGroup; - m_oldId = m_curId = 0; - m_coreLayout = null; - - m_coreLayout = new QBoxLayout ( (orient == Qt::Vertical) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom); - setLayout (m_coreLayout); - - // Init the first row with a break - rowBreak(); - - connect (m_buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int))); - connect (m_buttonGroup, SIGNAL (buttonReleased (int)), this, SLOT (slot_buttonReleased (int))); -} - -// ============================================================================= -// -RadioGroup::RadioGroup (const QString& title, QList<char const*> entries, int const defaultId, const Qt::Orientation orient, QWidget* parent) : - QGroupBox (title, parent), - m_defId (defaultId) -{ - init (orient); - m_oldId = m_defId; - - for (const char* entry : entries) - addButton (entry); -} - -// ============================================================================= -// -void RadioGroup::rowBreak() -{ - QBoxLayout* newLayout = new QBoxLayout (m_vert ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); - m_currentLayout = newLayout; - m_layouts << newLayout; - - m_coreLayout->addLayout (newLayout); -} - -// ============================================================================= -// -void RadioGroup::addButton (const char* entry) -{ - QRadioButton* button = new QRadioButton (entry); - addButton (button); -} - -// ============================================================================= -// -void RadioGroup::addButton (QRadioButton* button) -{ - bool const selectThis = (m_curId == m_defId); - - m_objects << button; - m_buttonGroup->addButton (button, m_curId++); - m_currentLayout->addWidget (button); - - if (selectThis) - button->setChecked (true); -} - -// ============================================================================= -// -RadioGroup& RadioGroup::operator<< (QRadioButton* button) -{ - addButton (button); - return *this; -} - -// ============================================================================= -// -RadioGroup& RadioGroup::operator<< (const char* entry) -{ - addButton (entry); - return *this; -} - -// ============================================================================= -// -void RadioGroup::setCurrentRow (int row) -{ - m_currentLayout = m_layouts[row]; -} - -// ============================================================================= -// -int RadioGroup::value() const -{ - return m_buttonGroup->checkedId(); -} - -// ============================================================================= -// -void RadioGroup::setValue (int val) -{ - m_buttonGroup->button (val)->setChecked (true); -} - -// ============================================================================= -// -QRadioButton* RadioGroup::operator[] (int n) const -{ - return m_objects[n]; -} - -// ============================================================================= -// -void RadioGroup::slot_buttonPressed (int btn) -{ - emit buttonPressed (btn); - - m_oldId = m_buttonGroup->checkedId(); -} - -// ============================================================================= -// -void RadioGroup::slot_buttonReleased (int btn) -{ - emit buttonReleased (btn); - int newid = m_buttonGroup->checkedId(); - - if (m_oldId != newid) - emit valueChanged (newid); -} - -// ============================================================================= -// -RadioGroup::Iterator RadioGroup::begin() -{ - return m_objects.begin(); -} - -// ============================================================================= -// -RadioGroup::Iterator RadioGroup::end() -{ - return m_objects.end(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/radioGroup.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,194 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +// I still find the radio group useful... find a way to use this in Designer. +// I probably need to look into how to make Designer plugins. +// TODO: try make this usable in Designer + +#include <QBoxLayout> +#include <QRadioButton> +#include <QButtonGroup> +#include <QCheckBox> +#include <map> + +#include "radioGroup.h" + +// ============================================================================= +// +RadioGroup::RadioGroup (const QString& title, QWidget* parent) : QGroupBox (title, parent) +{ + init (Qt::Vertical); +} + +// ============================================================================= +// +QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false) +{ + return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom; +} + +// ============================================================================= +// +bool RadioGroup::isChecked (int n) const +{ + return m_buttonGroup->checkedId() == n; +} + +// ============================================================================= +// +void RadioGroup::init (Qt::Orientation orient) +{ + m_vert = orient == Qt::Vertical; + + m_buttonGroup = new QButtonGroup; + m_oldId = m_curId = 0; + m_coreLayout = null; + + m_coreLayout = new QBoxLayout ( (orient == Qt::Vertical) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom); + setLayout (m_coreLayout); + + // Init the first row with a break + rowBreak(); + + connect (m_buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int))); + connect (m_buttonGroup, SIGNAL (buttonReleased (int)), this, SLOT (slot_buttonReleased (int))); +} + +// ============================================================================= +// +RadioGroup::RadioGroup (const QString& title, QList<char const*> entries, int const defaultId, const Qt::Orientation orient, QWidget* parent) : + QGroupBox (title, parent), + m_defId (defaultId) +{ + init (orient); + m_oldId = m_defId; + + for (const char* entry : entries) + addButton (entry); +} + +// ============================================================================= +// +void RadioGroup::rowBreak() +{ + QBoxLayout* newLayout = new QBoxLayout (m_vert ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); + m_currentLayout = newLayout; + m_layouts << newLayout; + + m_coreLayout->addLayout (newLayout); +} + +// ============================================================================= +// +void RadioGroup::addButton (const char* entry) +{ + QRadioButton* button = new QRadioButton (entry); + addButton (button); +} + +// ============================================================================= +// +void RadioGroup::addButton (QRadioButton* button) +{ + bool const selectThis = (m_curId == m_defId); + + m_objects << button; + m_buttonGroup->addButton (button, m_curId++); + m_currentLayout->addWidget (button); + + if (selectThis) + button->setChecked (true); +} + +// ============================================================================= +// +RadioGroup& RadioGroup::operator<< (QRadioButton* button) +{ + addButton (button); + return *this; +} + +// ============================================================================= +// +RadioGroup& RadioGroup::operator<< (const char* entry) +{ + addButton (entry); + return *this; +} + +// ============================================================================= +// +void RadioGroup::setCurrentRow (int row) +{ + m_currentLayout = m_layouts[row]; +} + +// ============================================================================= +// +int RadioGroup::value() const +{ + return m_buttonGroup->checkedId(); +} + +// ============================================================================= +// +void RadioGroup::setValue (int val) +{ + m_buttonGroup->button (val)->setChecked (true); +} + +// ============================================================================= +// +QRadioButton* RadioGroup::operator[] (int n) const +{ + return m_objects[n]; +} + +// ============================================================================= +// +void RadioGroup::slot_buttonPressed (int btn) +{ + emit buttonPressed (btn); + + m_oldId = m_buttonGroup->checkedId(); +} + +// ============================================================================= +// +void RadioGroup::slot_buttonReleased (int btn) +{ + emit buttonReleased (btn); + int newid = m_buttonGroup->checkedId(); + + if (m_oldId != newid) + emit valueChanged (newid); +} + +// ============================================================================= +// +RadioGroup::Iterator RadioGroup::begin() +{ + return m_objects.begin(); +} + +// ============================================================================= +// +RadioGroup::Iterator RadioGroup::end() +{ + return m_objects.end(); +}
--- a/src/ringFinder.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,224 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "ringFinder.h" -#include "miscallenous.h" - -RingFinder g_RingFinder; - -RingFinder::RingFinder() {} - -// ============================================================================= -// -bool RingFinder::findRingsRecursor (double r0, double r1, Solution& currentSolution) -{ - // Don't recurse too deep. - if (m_stack >= 5) - return false; - - // Find the scale and number of a ring between r1 and r0. - assert (r1 >= r0); - double scale = r1 - r0; - double num = r0 / scale; - - // If the ring number is integral, we have found a fitting ring to r0 -> r1! - if (IsIntegral (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) and 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; -} - -// -// 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::findRings (double r0, double r1) -{ - m_solutions.clear(); - Solution sol; - - // If we're dealing with fractional radii, try upscale them into integral - // ones. This should yield in more reliable and more optimized results. - // For instance, using r0=1.5, r1=3.5 causes the algorithm to fail but - // r0=3, r1=7 (scaled up by 2) yields a 2-component solution. We can then - // downscale the radii back by dividing the scale fields of the solution - // components. - double scale = 1.0; - - if (not IsZero (scale = r0 - floor (r0)) or not IsZero (scale = r1 - floor (r1))) - { - double r0f = r0 / scale; - double r1f = r1 / scale; - - if (IsIntegral (r0f) and IsIntegral (r1f)) - { - r0 = r0f; - r1 = r1f; - } - // If the numbers are both at most one-decimal fractions, we can use a scale of 10 - elif (IsIntegral (r0 * 10) and IsIntegral (r1 * 10)) - { - scale = 0.1; - r0 *= 10; - r1 *= 10; - } - } - else - { - scale = 1.0; - } - - // Recurse in and try find solutions. - findRingsRecursor (r0, r1, sol); - - // If we had upscaled our radii, downscale back now. - if (scale != 1.0) - { - for (Solution& sol : m_solutions) - sol.scaleComponents (scale); - } - - // Compare the solutions and find the best one. The solution class has an operator> - // overload to compare two solutions. - m_bestSolution = null; - - for (Solution const& sol : m_solutions) - { - if (m_bestSolution == null or sol.isSuperiorTo (m_bestSolution)) - m_bestSolution = / - } - - return (m_bestSolution != null); -} - -// -// Compares this solution with @other and determines which -// one is superior. -// -// A solution is considered superior if solution has less -// components than the other one. If both solution have an -// equal amount components, the solution with a lesser maximum -// ring number is found superior, as such solutions should -// yield less new primitives and cleaner definitions. -// -// The solution which is found superior to every other solution -// will be the one returned by RingFinder::bestSolution(). -// -bool RingFinder::Solution::isSuperiorTo (const Solution* other) const -{ - // If one solution has less components than the other one, it is definitely - // better. - if (getComponents().size() != other->getComponents().size()) - return getComponents().size() < other->getComponents().size(); - - // 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 maxA < maxB; - - // Solutions have equal rings and equal maximum ring numbers. Let's - // just say this one is better, at this point it does not matter which - // one is chosen. - return true; -} - -void RingFinder::Solution::scaleComponents (double scale) -{ - for (Component& cmp : m_components) - cmp.scale *= scale; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ringFinder.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,224 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ringFinder.h" +#include "miscallenous.h" + +RingFinder g_RingFinder; + +RingFinder::RingFinder() {} + +// ============================================================================= +// +bool RingFinder::findRingsRecursor (double r0, double r1, Solution& currentSolution) +{ + // Don't recurse too deep. + if (m_stack >= 5) + return false; + + // Find the scale and number of a ring between r1 and r0. + assert (r1 >= r0); + double scale = r1 - r0; + double num = r0 / scale; + + // If the ring number is integral, we have found a fitting ring to r0 -> r1! + if (IsIntegral (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) and 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; +} + +// +// 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::findRings (double r0, double r1) +{ + m_solutions.clear(); + Solution sol; + + // If we're dealing with fractional radii, try upscale them into integral + // ones. This should yield in more reliable and more optimized results. + // For instance, using r0=1.5, r1=3.5 causes the algorithm to fail but + // r0=3, r1=7 (scaled up by 2) yields a 2-component solution. We can then + // downscale the radii back by dividing the scale fields of the solution + // components. + double scale = 1.0; + + if (not IsZero (scale = r0 - floor (r0)) or not IsZero (scale = r1 - floor (r1))) + { + double r0f = r0 / scale; + double r1f = r1 / scale; + + if (IsIntegral (r0f) and IsIntegral (r1f)) + { + r0 = r0f; + r1 = r1f; + } + // If the numbers are both at most one-decimal fractions, we can use a scale of 10 + elif (IsIntegral (r0 * 10) and IsIntegral (r1 * 10)) + { + scale = 0.1; + r0 *= 10; + r1 *= 10; + } + } + else + { + scale = 1.0; + } + + // Recurse in and try find solutions. + findRingsRecursor (r0, r1, sol); + + // If we had upscaled our radii, downscale back now. + if (scale != 1.0) + { + for (Solution& sol : m_solutions) + sol.scaleComponents (scale); + } + + // Compare the solutions and find the best one. The solution class has an operator> + // overload to compare two solutions. + m_bestSolution = null; + + for (Solution const& sol : m_solutions) + { + if (m_bestSolution == null or sol.isSuperiorTo (m_bestSolution)) + m_bestSolution = / + } + + return (m_bestSolution != null); +} + +// +// Compares this solution with @other and determines which +// one is superior. +// +// A solution is considered superior if solution has less +// components than the other one. If both solution have an +// equal amount components, the solution with a lesser maximum +// ring number is found superior, as such solutions should +// yield less new primitives and cleaner definitions. +// +// The solution which is found superior to every other solution +// will be the one returned by RingFinder::bestSolution(). +// +bool RingFinder::Solution::isSuperiorTo (const Solution* other) const +{ + // If one solution has less components than the other one, it is definitely + // better. + if (getComponents().size() != other->getComponents().size()) + return getComponents().size() < other->getComponents().size(); + + // 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 maxA < maxB; + + // Solutions have equal rings and equal maximum ring numbers. Let's + // just say this one is better, at this point it does not matter which + // one is chosen. + return true; +} + +void RingFinder::Solution::scaleComponents (double scale) +{ + for (Component& cmp : m_components) + cmp.scale *= scale; +}
--- a/src/version.cc Sat Feb 21 20:23:34 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -// Note: only using stock C stuff in this file, keeping the STL/Qt stuff out -// makes this simple file very fast to compile, which is nice since this file -// must be compiled every time I commit something. - -#include <stdio.h> -#include <string.h> -#include <time.h> -#include "version.h" -#include "hginfo.h" - -// ============================================================================= -// -const char* VersionString() -{ - static char result[64] = {'\0'}; - - if (result[0] == '\0') - { -#if VERSION_PATCH == 0 - sprintf (result, "%d.%d", VERSION_MAJOR, VERSION_MINOR); -#else - sprintf (g_versionString, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); -#endif // VERSION_PATCH - } - - return result; -} - -// ============================================================================= -// -const char* FullVersionString() -{ - static char result[256] = {'\0'}; - - if (result[0] == '\0') - { -#if BUILD_ID != BUILD_RELEASE and defined (HG_NODE) - sprintf (result, "%s-" HG_NODE, VersionString()); -#else - sprintf (g_fullVersionString, "%s", VersionString()); -#endif - } - - return result; -} - -// ============================================================================= -// -const char* CommitTimeString() -{ - static char result[256] = {'\0'}; - -#ifdef HG_DATE_TIME - if (result[0] == '\0') - { - time_t timestamp = HG_DATE_TIME; - strftime (result, sizeof result, "%d %b %Y", localtime (×tamp)); - } -#endif - - return result; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/version.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,80 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +// Note: only using stock C stuff in this file, keeping the STL/Qt stuff out +// makes this simple file very fast to compile, which is nice since this file +// must be compiled every time I commit something. + +#include <stdio.h> +#include <string.h> +#include <time.h> +#include "version.h" +#include "hginfo.h" + +// ============================================================================= +// +const char* VersionString() +{ + static char result[64] = {'\0'}; + + if (result[0] == '\0') + { +#if VERSION_PATCH == 0 + sprintf (result, "%d.%d", VERSION_MAJOR, VERSION_MINOR); +#else + sprintf (g_versionString, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); +#endif // VERSION_PATCH + } + + return result; +} + +// ============================================================================= +// +const char* FullVersionString() +{ + static char result[256] = {'\0'}; + + if (result[0] == '\0') + { +#if BUILD_ID != BUILD_RELEASE and defined (HG_NODE) + sprintf (result, "%s-" HG_NODE, VersionString()); +#else + sprintf (g_fullVersionString, "%s", VersionString()); +#endif + } + + return result; +} + +// ============================================================================= +// +const char* CommitTimeString() +{ + static char result[256] = {'\0'}; + +#ifdef HG_DATE_TIME + if (result[0] == '\0') + { + time_t timestamp = HG_DATE_TIME; + strftime (result, sizeof result, "%d %b %Y", localtime (×tamp)); + } +#endif + + return result; +}