# HG changeset patch # User Teemu Piippo # Date 1425414567 -7200 # Node ID 8d98ee0dc9172bd78e8af00e636bae646cf5602b # Parent ab77deb851fa6a19aa3a2b5c0bd57674c0da9cd9# Parent be8128aff73985e2b0c5753a2fa370d22de826cd - merged with default diff -r ab77deb851fa -r 8d98ee0dc917 CMakeLists.txt --- a/CMakeLists.txt Tue Mar 03 16:50:39 2015 +0200 +++ b/CMakeLists.txt Tue Mar 03 22:29:27 2015 +0200 @@ -6,8 +6,8 @@ ###################################################################### project (ldforge) -add_subdirectory (codegen) cmake_minimum_required (VERSION 2.6) +cmake_policy (SET CMP0020 OLD) option (TRANSPARENT_DIRECT_COLORS "Enables non-standard transparent direct colors" OFF) option (USE_QT5 "Use Qt5 instead of Qt4" OFF) @@ -24,50 +24,48 @@ find_package (Libarchive) find_package (OpenGL REQUIRED) - if (NOT LibArchive_FOUND) message (SEND_ERROR "Could not find Libarchive") endif() -get_target_property (CODEGEN_EXE codegen LOCATION) 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 @@ -107,36 +105,31 @@ ) set (LDFORGE_FORMS - ui/about.ui - ui/addhistoryline.ui - ui/bombbox.ui - ui/colorsel.ui - ui/config.ui - ui/coverer.ui - ui/downloadfrom.ui - ui/edger2.ui - ui/editraw.ui - ui/extprogpath.ui - ui/flip.ui - ui/intersector.ui - ui/isecalc.ui - ui/ldforge.ui - ui/ldrawpath.ui - ui/makeprim.ui - ui/newpart.ui - ui/openprogress.ui - ui/overlay.ui - ui/rectifier.ui - ui/replcoords.ui - ui/rotpoint.ui - ui/ytruder.ui + src/about.ui + src/addhistoryline.ui + src/bombbox.ui + src/colorsel.ui + src/config.ui + src/coverer.ui + src/downloadfrom.ui + src/edger2.ui + src/editraw.ui + src/extprogpath.ui + src/flip.ui + src/intersector.ui + src/isecalc.ui + src/ldforge.ui + src/ldrawpath.ui + src/makeprim.ui + src/newpart.ui + src/openprogress.ui + src/overlay.ui + src/rectifier.ui + src/replcoords.ui + src/rotpoint.ui + src/ytruder.ui ) -add_custom_target (codegeneration ALL - COMMAND ${CODEGEN_EXE} ${LDFORGE_SOURCES} ${CMAKE_BINARY_DIR}/configuration.inc - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - DEPENDS codegen) - set (LDFORGE_RESOURCES ldforge.qrc) # set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lGLU") @@ -192,11 +185,23 @@ ) endif() -add_dependencies (${PROJECT_NAME} revision_check codegeneration) install (TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) +# +# Code generators +# + add_custom_target (make_hginfo_h COMMAND python - "${CMAKE_SOURCE_DIR}/updaterevision.py" - "${CMAKE_CURRENT_BINARY_DIR}/hginfo.h") -add_dependencies (${PROJECT_NAME} make_hginfo_h) + "${CMAKE_SOURCE_DIR}/codegen.py" "hginfo" + "${CMAKE_CURRENT_BINARY_DIR}/hginfo.h" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + +add_custom_target (make_config_aux + COMMAND python + "${CMAKE_SOURCE_DIR}/codegen.py" "configaux" + "${CMAKE_CURRENT_BINARY_DIR}/config.aux" + ${LDFORGE_SOURCES} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + +add_dependencies (${PROJECT_NAME} make_hginfo_h make_config_aux) diff -r ab77deb851fa -r 8d98ee0dc917 codegen.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/codegen.py Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,154 @@ +# +# Copyright 2015 Teemu Piippo +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER +# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +import md5 +import re +import sys +import os + +class OutputFile: + def __init__(self, filename): + self.filename = filename + try: + with open (self.filename, "r") as fp: + self.oldsum = fp.readline().replace ('\n', '').replace ('// ', '') + except IOError: + self.oldsum = '' + + self.body = '' + + def write(self, a): + self.body += a + + def save(self): + checksum = md5.new (self.body).hexdigest() + + if checksum == self.oldsum: + print '%s is up to date' % self.filename + return False + + with open (self.filename, "w") as fp: + fp.write ('// %s\n' % checksum) + fp.write (self.body) + return True + +def prog_configaux(): + class CfgEntry: + def __init__(self, type, name, defvalue): + self.type = type + self.name = name + self.defvalue = defvalue + + if len (sys.argv) < 3: + print 'Usage: %s [input2] [input3] ... [input-n]' % sys.argv[0] + quit(1) + + f = OutputFile (sys.argv[1]) + f.write ('#pragma once\n') + + entries = [] + + for inputname in sys.argv[2:]: + fp = open (inputname, 'r') + + for line in fp.readlines(): + match = re.match (r'CFGENTRY\s*\(([^,]+),\s*([^,]+),\s*([^)]+)\)', line) + if match: + entries.append (CfgEntry (match.group (1), match.group (2), match.group (3))) + + for e in entries: + f.write ('EXTERN_CFGENTRY (%s, %s)\n' % (e.type, e.name)) + + f.write ('\n') + f.write ('static void InitConfigurationEntry (class AbstractConfigEntry* entry);\n') + f.write ('static void SetupConfigurationLists()\n') + f.write ('{\n') + + for e in entries: + f.write ('\tInitConfigurationEntry (new %sConfigEntry (&cfg::%s, \"%s\", %s));\n' + % (e.type, e.name, e.name, e.defvalue)) + + f.write ('}\n') + + if f.save(): + print 'Wrote configuration aux code to %s' % f.filename + +def prog_hginfo(): + import subprocess + from datetime import datetime + + if len (sys.argv) != 2: + print 'usage: %s ' % sys.argv[0] + quit (1) + + f = OutputFile (sys.argv[1]) + data = subprocess.check_output (['hg', 'log', '-r.', '--template', + '{node|short} {branch} {date|hgdate}']).replace ('\n', '').split (' ') + + rev = data[0] + branch = data[1] + timestamp = int (data[2]) + date = datetime.utcfromtimestamp (timestamp) + datestring = date.strftime ('%y%m%d-%H%M') if date.year >= 2000 else '000000-0000' + + if len(rev) > 7: + rev = rev[0:7] + + if subprocess.check_output (['hg', 'id', '-n']).replace ('\n', '')[-1] == '+': + rev += '+' + + f.write ('#define HG_NODE "%s"\n' % rev) + f.write ('#define HG_BRANCH "%s"\n' % branch) + f.write ('#define HG_DATE_VERSION "%s"\n' % datestring) + f.write ('#define HG_DATE_STRING "%s"\n' % date.strftime ('%d %b %Y')) + f.write ('#define HG_DATE_TIME %d\n' % int (timestamp)) + if f.save(): + print '%s updated to %s' % (f.filename, rev) + +def main(): + if len(sys.argv) < 2: + print 'You must give a program name' + quit(1) + + progname = sys.argv[1] + sys.argv[0] = '%s %s' % (sys.argv[0], sys.argv[1]) + sys.argv.pop (1) + + impl = globals().copy() + impl.update (locals()) + method = impl.get ('prog_' + progname) + + if method: + method() + else: + print 'no such program %s' % progname + +if __name__ == '__main__': + main() \ No newline at end of file diff -r ab77deb851fa -r 8d98ee0dc917 codegen/CMakeLists.txt --- a/codegen/CMakeLists.txt Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -cmake_minimum_required (VERSION 2.4) -add_executable (codegen codegen.cpp) - -# -# LDForge uses alternative operators. GCC and Clang use these by default but MSVC does not. -# So we'll have to tell MSVC to use these alternative operators -# -if (MSVC) - set_target_properties (codegen PROPERTIES COMPILE_FLAGS "/Za") -endif() diff -r ab77deb851fa -r 8d98ee0dc917 codegen/codegen.cpp --- a/codegen/codegen.cpp Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,181 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 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 . - */ - -#include -#include -#include -#include -#include -#include -#include - -using std::string; -using std::vector; -using std::ifstream; -using std::size_t; -using std::strncmp; -using std::getline; -using std::cout; -using std::endl; - -struct EntryType -{ - string name; - string type; - string defvalue; - - inline bool operator< (EntryType const& other) const - { - return name < other.name; - } - - inline bool operator== (EntryType const& other) const - { - return name == other.name and type == other.type; - } - - inline bool operator!= (EntryType const& other) const - { - return not operator== (other); - } -}; - -char AdvancePointer (char const*& ptr) -{ - char a = *ptr++; - - if (*ptr == '\0') - throw false; - - return a; -} - -void ReadConfigEntries (string const& filename, vector& entries, const char* macroname) -{ - ifstream is (filename.c_str()); - string line; - size_t const macrolen = strlen (macroname); - - while (getline (is, line)) - { - try - { - if (strncmp (line.c_str(), macroname, macrolen) != 0) - continue; - - char const* ptr = &line[macrolen]; - EntryType entry; - - // Skip to paren - while (*ptr != '(') - AdvancePointer (ptr); - - // Skip whitespace - while (isspace (*ptr)) - AdvancePointer (ptr); - - // Skip paren - AdvancePointer (ptr); - - // Read type - while (*ptr != ',') - entry.type += AdvancePointer (ptr); - - // Skip comma and whitespace - for (AdvancePointer (ptr); isspace (*ptr); AdvancePointer (ptr)) - ; - - // Read name - while (*ptr != ',') - entry.name += AdvancePointer (ptr); - - // Skip comma and whitespace - for (AdvancePointer (ptr); isspace (*ptr); AdvancePointer (ptr)) - ; - - // Read default - while (*ptr != ')') - entry.defvalue += AdvancePointer (ptr); - - entries.push_back (entry); - } - catch (bool) {} - } -} - -bool CheckEquality (vector a, vector b) -{ - if (a.size() != b.size()) - return false; - - std::sort (a.begin(), a.end()); - std::sort (b.begin(), b.end()); - - for (size_t i = 0; i < a.size(); ++i) - { - if (a[i] != b[i]) - return false; - } - - return true; -} - -int main (int argc, char* argv[]) -{ - vector entries; - vector oldentries; - ReadConfigEntries (argv[argc - 1], oldentries, "CODEGEN_CACHE"); - - for (int arg = 1; arg < argc - 1; ++arg) - ReadConfigEntries (argv[arg], entries, "CFGENTRY"); - - if (CheckEquality (entries, oldentries)) - { - cout << "Configuration options unchanged" << endl; - return 0; - } - - std::ofstream os (argv[argc - 1]); - os << "#pragma once" << endl; - os << "#define CODEGEN_CACHE(A,B,C)" << endl; - - for (vector::const_iterator it = entries.begin(); it != entries.end(); ++it) - { - os << "CODEGEN_CACHE (" << it->type << ", " << it->name << ", " << - it->defvalue << ")" << endl; - } - - os << endl; - for (vector::const_iterator it = entries.begin(); it != entries.end(); ++it) - os << "EXTERN_CFGENTRY (" << it->type << ", " << it->name << ")" << endl; - - os << endl; - os << "static void InitConfigurationEntry (AbstractConfigEntry* entry);" << endl; - os << "static void SetupConfigurationLists()" << endl; - os << "{" << endl; - - for (vector::const_iterator it = entries.begin(); it != entries.end(); ++it) - { - os << "\tInitConfigurationEntry (new " << it->type << "ConfigEntry (&cfg::" << - it->name << ", \"" << it->name << "\", " << it->defvalue << "));" << endl; - } - - os << "}" << endl; - cout << "Wrote configuration options list to " << argv[argc - 1] << "." << endl; - return 0; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/about.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/about.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,140 @@ + + + AboutUI + + + + 0 + 0 + 320 + 400 + + + + + 320 + 400 + + + + + 16777215 + 16777215 + + + + About LDForge + + + + + + + 16777215 + 16777215 + + + + + + + :/icons/ldforge.png + + + false + + + Qt::AlignCenter + + + + + + + font-weight: bold + + + [[ VERSION INFO HERE]] + + + Qt::AlignCenter + + + + + + + Copyright (C) 2013, 2014 Teemu Piippo + + + Qt::AlignCenter + + + + + + + + 16777215 + 16777215 + + + + QFrame::NoFrame + + + <html><head/><body><p>This software is intended for usage as a parts authoring tool for the <a href="http://ldraw.org/"><span style=" text-decoration: underline; color:#0057ae;">LDraw</span></a> parts library.</p><p>LDForge is free software, and you are welcome to redistribute it under the terms of GPL v3. See the LICENSE text file for details. If the license text is not available for some reason, see <a href="http://www.gnu.org/licenses/"><span style=" text-decoration: underline; color:#0057ae;">http://www.gnu.org/licenses/</span></a> for the license terms.</p><p>The graphical assets of LDForge are licensed under the <a href="http://creativecommons.org/licenses/by-sa/3.0/"><span style=" text-decoration: underline; color:#0057ae;">CC Attribution-ShareAlike 3.0 Unported license</span></a>. The GNU GPL applies to the source code of the program. The application icon is derived from <a href="http://en.wikipedia.org/wiki/File:Anvil,_labelled_en.svg"><span style=" text-decoration: underline; color:#0057ae;">this image on Wikipedia</span></a>. The linked image (retrieved 22 May 2013) was released into the public domain.</p></body></html> + + + Qt::AlignCenter + + + true + + + + + + + In living memory of James Jessiman. + + + Qt::AlignCenter + + + + + + + QDialogButtonBox::Close + + + false + + + + + + + + + + + + buttonBox + rejected() + AboutUI + reject() + + + 296 + 384 + + + 293 + 1 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/actions.cc --- a/src/actions.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include -#include -#include -#include - -#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 (ui.le_title->text()); - objs << LDSpawn ("Name: .dat"); - objs << LDSpawn (format ("Author: %1", ui.le_author->text())); - objs << LDSpawn (format ("!LDRAW_ORG Unofficial_Part")); - - if (not license.isEmpty()) - objs << LDSpawn (license); - - objs << LDSpawn(); - objs << LDSpawn (bfctype); - objs << LDSpawn(); - 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 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 types; - QStringList subfilenames; - - for (LDObjectPtr obj : Selection()) - { - types << obj->type(); - - if (types.last() == OBJ_Subfile) - subfilenames << obj.staticCast()->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()->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()); - - // 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 %1 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 (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 (subtitle); - objs << LDSpawn ("Name: "); // This gets filled in when the subfile is saved - objs << LDSpawn (format ("Author: %1 [%2]", cfg::DefaultName, cfg::DefaultUser)); - objs << LDSpawn ("!LDRAW_ORG Unofficial_Subpart"); - - if (not license.isEmpty()) - objs << LDSpawn (license); - - objs << LDSpawn(); - objs << LDSpawn (bfctype); - objs << LDSpawn(); - - 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()); - 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(); - - 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(); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/actions.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/actions.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +#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 (ui.le_title->text()); + objs << LDSpawn ("Name: .dat"); + objs << LDSpawn (format ("Author: %1", ui.le_author->text())); + objs << LDSpawn (format ("!LDRAW_ORG Unofficial_Part")); + + if (not license.isEmpty()) + objs << LDSpawn (license); + + objs << LDSpawn(); + objs << LDSpawn (bfctype); + objs << LDSpawn(); + 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 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 types; + QStringList subfilenames; + + for (LDObjectPtr obj : Selection()) + { + types << obj->type(); + + if (types.last() == OBJ_Subfile) + subfilenames << obj.staticCast()->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()->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()); + + // 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 %1 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 (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 (subtitle); + objs << LDSpawn ("Name: "); // This gets filled in when the subfile is saved + objs << LDSpawn (format ("Author: %1 [%2]", cfg::DefaultName, cfg::DefaultUser)); + objs << LDSpawn ("!LDRAW_ORG Unofficial_Subpart"); + + if (not license.isEmpty()) + objs << LDSpawn (license); + + objs << LDSpawn(); + objs << LDSpawn (bfctype); + objs << LDSpawn(); + + 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()); + 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(); + + 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(); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/actionsEdit.cc --- a/src/actionsEdit.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include -#include -#include -#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 (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 (Selection(), [&](LDQuadPtr const& quad) - { - // Find the index of this quad - long index = quad->lineNumber(); - - if (index == -1) - return; - - QList 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()->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(); - lines[0] = LDSpawn (quad->vertex (0), quad->vertex (1)); - lines[1] = LDSpawn (quad->vertex (1), quad->vertex (2)); - lines[2] = LDSpawn (quad->vertex (2), quad->vertex (3)); - lines[3] = LDSpawn (quad->vertex (3), quad->vertex (0)); - } - else - { - LDTrianglePtr tri = obj.staticCast(); - lines[0] = LDSpawn (tri->vertex (0), tri->vertex (1)); - lines[1] = LDSpawn (tri->vertex (1), tri->vertex (2)); - lines[2] = LDSpawn (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(); - - 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 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 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 (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 (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(); - - 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())); - - buildObjList(); - delete ui; -} - -void MainWindow::actionSplitLines() -{ - bool ok; - int segments = QInputDialog::getInt (g_win, APPNAME, "Amount of segments:", cfg::SplitLinesSegments, 0, - std::numeric_limits::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 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 (v0, v1); - else - segment = LDSpawn (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(); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/actionsEdit.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/actionsEdit.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#include +#include +#include +#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 (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 (Selection(), [&](LDQuadPtr const& quad) + { + // Find the index of this quad + long index = quad->lineNumber(); + + if (index == -1) + return; + + QList 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()->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(); + lines[0] = LDSpawn (quad->vertex (0), quad->vertex (1)); + lines[1] = LDSpawn (quad->vertex (1), quad->vertex (2)); + lines[2] = LDSpawn (quad->vertex (2), quad->vertex (3)); + lines[3] = LDSpawn (quad->vertex (3), quad->vertex (0)); + } + else + { + LDTrianglePtr tri = obj.staticCast(); + lines[0] = LDSpawn (tri->vertex (0), tri->vertex (1)); + lines[1] = LDSpawn (tri->vertex (1), tri->vertex (2)); + lines[2] = LDSpawn (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(); + + 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 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 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 (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 (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(); + + 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())); + + buildObjList(); + delete ui; +} + +void MainWindow::actionSplitLines() +{ + bool ok; + int segments = QInputDialog::getInt (g_win, APPNAME, "Amount of segments:", cfg::SplitLinesSegments, 0, + std::numeric_limits::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 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 (v0, v1); + else + segment = LDSpawn (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(); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/addObjectDialog.cc --- a/src/addObjectDialog.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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()->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()->statement()); - } break; - - case OBJ_Subfile: - { - coordCount = 3; - tw_subfileList = new QTreeWidget(); - tw_subfileList->setHeaderLabel (tr ("Primitives")); - PopulatePrimitives (tw_subfileList, (obj != null ? obj.staticCast()->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(); - 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(); - 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 (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 -static QSharedPointer InitObject (LDObjectPtr& obj) -{ - if (obj == null) - obj = LDSpawn(); - - return obj.staticCast(); -} - -// ============================================================================= -// ============================================================================= -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 (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 (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 (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(); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/addObjectDialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/addObjectDialog.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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()->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()->statement()); + } break; + + case OBJ_Subfile: + { + coordCount = 3; + tw_subfileList = new QTreeWidget(); + tw_subfileList->setHeaderLabel (tr ("Primitives")); + PopulatePrimitives (tw_subfileList, (obj != null ? obj.staticCast()->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(); + 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(); + 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 (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 +static QSharedPointer InitObject (LDObjectPtr& obj) +{ + if (obj == null) + obj = LDSpawn(); + + return obj.staticCast(); +} + +// ============================================================================= +// ============================================================================= +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 (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 (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 (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(); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/addhistoryline.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/addhistoryline.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,98 @@ + + + AddHistoryLine + + + + 0 + 0 + 410 + 120 + + + + Add History Line + + + + + + + + Date: + + + + + + + + + + Username: + + + + + + + + + + Comment: + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + AddHistoryLine + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AddHistoryLine + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/basics.cc --- a/src/basics.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include -#include -#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& 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(); - 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); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/basics.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/basics.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#include +#include +#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& 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(); + 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); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/bombbox.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/bombbox.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,104 @@ + + + BombBox + + + + 0 + 0 + 676 + 569 + + + + Fatal Error + + + + + + + + + + + + + :/icons/bomb.png + + + + + + + Qt::Vertical + + + + 20 + 138 + + + + + + + + + + true + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + + + buttonBox + accepted() + BombBox + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + BombBox + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/colorSelector.cc --- a/src/colorSelector.cc Tue Mar 03 16:50:39 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 . - * ===================================================================== - * - * colorSelectDialog.cxx: Color selector box. - */ - -#include -#include -#include -#include -#include -#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 (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() ? "" : 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; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/colorSelector.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colorSelector.cpp Tue Mar 03 22:29:27 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 . + * ===================================================================== + * + * colorSelectDialog.cxx: Color selector box. + */ + +#include +#include +#include +#include +#include +#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 (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() ? "" : 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; +} diff -r ab77deb851fa -r 8d98ee0dc917 src/colors.cc --- a/src/colors.cc Tue Mar 03 16:50:39 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 . - */ - -#include "main.h" -#include "colors.h" -#include "ldDocument.h" -#include "miscallenous.h" -#include "mainWindow.h" -#include "ldConfig.h" -#include - -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); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/colors.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colors.cpp Tue Mar 03 22:29:27 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 . + */ + +#include "main.h" +#include "colors.h" +#include "ldDocument.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "ldConfig.h" +#include + +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); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/colorsel.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colorsel.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,138 @@ + + + ColorSelUI + + + + 0 + 0 + 588 + 404 + + + + Select Color + + + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOff + + + + + 0 + 0 + 384 + 287 + + + + + + + + + + + [[ COLOR ICON HERE]] + + + + + + + [[ COLOR HERE ]] + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Direct Color... + + + Direct Color... + + + + + + + Transparent + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + ColorSelUI + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ColorSelUI + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/config.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/config.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,1070 @@ + + + ConfigUI + + + + 0 + 0 + 648 + 370 + + + + Settings + + + + :/icons/settings.png:/icons/settings.png + + + + + + + + + 144 + 0 + + + + + 144 + 16777215 + + + + + Interface + + + + + Editing tools + + + + + Profile + + + + + Shortcuts + + + + + Color Toolbar + + + + + Grids + + + + + External Programs + + + + + Downloads + + + + + + + + 0 + + + + + + + Interface + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Background color + + + + + + + This is the background color for the viewport. + + + + + + + :/icons/colorselect.png:/icons/colorselect.png + + + + + + + + + + Main color + + + + + + + This color is used for the main color. + + + + + + + :/icons/colorselect.png:/icons/colorselect.png + + + + + + + Selected color + + + + + + + This color is used for the main color. + + + + + + + :/icons/colorselect.png:/icons/colorselect.png + + + + + + + + + + Main color alpha + + + + + + + 1.000000000000000 + + + 0.050000000000000 + + + 1.000000000000000 + + + + + + + + + + Line thickness + + + + + + + How thick lines should be drawn in the viewport. + + + 1 + + + 8 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 1 + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Use logoed studs + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Anti-aliased lines + + + + + + + Display line lenghts when drawing + + + + + + + Polygons' front sides become green and back sides red. + + + Red/green BFC view (incomplete) + + + + + + + Makes colored objects (non-16 and 24) appear colored in the list view. A red triangle will, for instance, have its entry written in red text. This can be useful to locate colored objects. + + + Colorize objects in list view + + + + + + + Makes all edgelines appear black. If this is not set, edge lines take their color as defined in LDConfig.ldr. + + + Black edges + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Highlight object below cursor + + + + + + + List implicitly loaded files + + + + + + + + + + + + + + + + + + Editing tools + + + + + + Rounding + + + + + + + + Position decimals: + + + + + + + + + + Matrix decimals: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Profile + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Name: + + + + + + + + 250 + 0 + + + + + + + + Username: + + + + + + + + + + Use CA license + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + Here you can alter keyboard shortcuts for almost all LDForge actions. Only exceptions are the controls for the viewport. Use the set button to set a key shortcut, clear to remove it and reset to restore the shortcut to its default value. + +Shortcut changes apply immediately after closing this window. + + + Shortcuts + + + + + + Qt::ScrollBarAsNeeded + + + + + + + + + Set + + + + + + + Reset + + + + :/icons/undo.png:/icons/undo.png + + + + + + + Clear + + + + :/icons/delete.png:/icons/delete.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + Here you can alter the layout of the quick colors toolbar. Use the controls to add, remove or edit the colors used. You can also add separators in between colors. + +Usually this contains MainColor, EdgeColor and some auxiliary colors used to group objects. + + + Color Toolbar + + + + + + Qt::ScrollBarAsNeeded + + + + + + + + + Add Color + + + + :/icons/palette.png:/icons/palette.png + + + + + + + Add Separator + + + + + + + Edit + + + + :/icons/mode-draw.png:/icons/mode-draw.png + + + + + + + Qt::Horizontal + + + + + + + Move Up + + + + :/icons/arrow-up.png:/icons/arrow-up.png + + + + + + + Move Down + + + + :/icons/arrow-down.png:/icons/arrow-down.png + + + + + + + Qt::Horizontal + + + + + + + Remove + + + + :/icons/delete.png:/icons/delete.png + + + + + + + Clear List + + + + :/icons/delete.png:/icons/delete.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + Grids + + + + + + + + ° + + + 3 + + + 360.000000000000000 + + + + + + + Coordinate snap + + + + + + + Angle snap + + + + + + + + + + :/icons/grid-coarse.png + + + + + + + Coarse + + + + + + + LDU + + + 5 + + + 10000.000000000000000 + + + + + + + ° + + + 3 + + + 360.000000000000000 + + + + + + + + + + :/icons/grid-medium.png + + + + + + + Medium + + + + + + + LDU + + + 5 + + + 10000.000000000000000 + + + + + + + ° + + + 3 + + + 360.000000000000000 + + + + + + + + + + :/icons/grid-fine.png + + + + + + + Fine + + + + + + + LDU + + + 5 + + + 10000.000000000000000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 165 + + + + + + + + + + + + + + + LDForge supports launching of several third-party utility tools; here you can set the file paths to these tools. Set the paths of the tools to the exe files. + +Under Linux, you can also set the programs to be launched with Wine, so you can use Windows binaries here as well. You will obviously need Wine installed. A 'wine' command in PATH is necessary for this to work. + + + External Programs + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Downloads + + + + + + + + Download path: + + + + + + + + + + + + + + :/icons/folder.png:/icons/folder.png + + + + + + + + + Attempt to download missing parts from the PT + + + + + + + <p>When this is set, LDForge tries to adjust and correct part paths based on the input. A full path given to the download prompt should be of form <tt>"&lt;dir&gt;/&lt;file&gt;.dat"</tt> - with this set, input can be automatically completed.</p> + +<p>Examples: +<ul> +<li>3002 -> parts/3002.dat</li> +<li>3002.da -> parts/3002.dat</li> +<li>3002s01 -> parts/s/3002s01.dat</li> +<li>4-4cyli -> p/4-4cyli.dat</li> +</ul></p> + + + Correct and guess part paths + + + + + + + If this is set, LDForge will close the download prompt after everything has been downloaded. The prompt will not be closed if a download has failed. + + + Close download prompt after completion + + + + + + + Qt::Vertical + + + + 20 + 187 + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/configDialog.cc --- a/src/configDialog.cc Tue Mar 03 16:50:39 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 . - * ===================================================================== - * - * configDialog.cxx: Settings dialog and everything related to it. - * Actual configuration core is in config.cxx. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 (wdg)) != null) - { - le->setText (value.toString()); - } - elif ((spinbox = qobject_cast (wdg)) != null) - { - spinbox->setValue (value.toInt()); - } - elif ((doublespinbox = qobject_cast (wdg)) != null) - { - doublespinbox->setValue (value.toDouble()); - } - elif ((slider = qobject_cast (wdg)) != null) - { - slider->setValue (value.toInt()); - } - elif ((checkbox = qobject_cast (wdg)) != null) - { - checkbox->setChecked (value.toBool()); - } - elif ((button = qobject_cast (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 func) -{ - // Apply configuration - for (QWidget* widget : findChildren()) - { - 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 (widget)) != null) - value = le->text(); - elif ((spinbox = qobject_cast (widget)) != null) - value = spinbox->value(); - elif ((doublespinbox = qobject_cast (widget)) != null) - value = doublespinbox->value(); - elif ((slider = qobject_cast (widget)) != null) - value = slider->value(); - elif ((checkbox = qobject_cast (widget)) != null) - value = checkbox->isChecked(); - elif ((button = qobject_cast (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 (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 ("
"); - 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 (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 (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 (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& haystack) -{ - int i = 0; - - for (QListWidgetItem* it : haystack) - { - if (it == item) - return i; - - ++i; - } - - return -1; -} - -// -// Which quick color is currently selected? -// -QListWidgetItem* ConfigDialog::getSelectedQuickColor() -{ - if (ui->quickColorList->selectedItems().isEmpty()) - return null; - - return ui->quickColorList->selectedItems() [0]; -} - -// -// Get the list of shortcuts selected -// -QList ConfigDialog::getShortcutSelection() -{ - QList out; - - for (QListWidgetItem* entry : ui->shortcutsList->selectedItems()) - out << static_cast (entry); - - return out; -} - -// -// Edit the shortcut of a given action. -// -void ConfigDialog::slot_setShortcut() -{ - QList sel = getShortcutSelection(); - - if (sel.size() < 1) - return; - - ShortcutListItem* item = sel[0]; - - if (KeySequenceDialog::staticDialog (item, this)) - setShortcutText (item); -} - -// -// Reset a shortcut to defaults -// -void ConfigDialog::slot_resetShortcut() -{ - QList 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 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 ("
%1
", shortcut); - lb_output->setText (text); -} - -// -// -void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) -{ - seq = ev->key() + ev->modifiers(); - updateOutput(); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/configDialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/configDialog.cpp Tue Mar 03 22:29:27 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 . + * ===================================================================== + * + * configDialog.cxx: Settings dialog and everything related to it. + * Actual configuration core is in config.cxx. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 (wdg)) != null) + { + le->setText (value.toString()); + } + elif ((spinbox = qobject_cast (wdg)) != null) + { + spinbox->setValue (value.toInt()); + } + elif ((doublespinbox = qobject_cast (wdg)) != null) + { + doublespinbox->setValue (value.toDouble()); + } + elif ((slider = qobject_cast (wdg)) != null) + { + slider->setValue (value.toInt()); + } + elif ((checkbox = qobject_cast (wdg)) != null) + { + checkbox->setChecked (value.toBool()); + } + elif ((button = qobject_cast (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 func) +{ + // Apply configuration + for (QWidget* widget : findChildren()) + { + 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 (widget)) != null) + value = le->text(); + elif ((spinbox = qobject_cast (widget)) != null) + value = spinbox->value(); + elif ((doublespinbox = qobject_cast (widget)) != null) + value = doublespinbox->value(); + elif ((slider = qobject_cast (widget)) != null) + value = slider->value(); + elif ((checkbox = qobject_cast (widget)) != null) + value = checkbox->isChecked(); + elif ((button = qobject_cast (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 (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 ("
"); + 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 (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 (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 (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& haystack) +{ + int i = 0; + + for (QListWidgetItem* it : haystack) + { + if (it == item) + return i; + + ++i; + } + + return -1; +} + +// +// Which quick color is currently selected? +// +QListWidgetItem* ConfigDialog::getSelectedQuickColor() +{ + if (ui->quickColorList->selectedItems().isEmpty()) + return null; + + return ui->quickColorList->selectedItems() [0]; +} + +// +// Get the list of shortcuts selected +// +QList ConfigDialog::getShortcutSelection() +{ + QList out; + + for (QListWidgetItem* entry : ui->shortcutsList->selectedItems()) + out << static_cast (entry); + + return out; +} + +// +// Edit the shortcut of a given action. +// +void ConfigDialog::slot_setShortcut() +{ + QList sel = getShortcutSelection(); + + if (sel.size() < 1) + return; + + ShortcutListItem* item = sel[0]; + + if (KeySequenceDialog::staticDialog (item, this)) + setShortcutText (item); +} + +// +// Reset a shortcut to defaults +// +void ConfigDialog::slot_resetShortcut() +{ + QList 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 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 ("
%1
", shortcut); + lb_output->setText (text); +} + +// +// +void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) +{ + seq = ev->key() + ev->modifiers(); + updateOutput(); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/configuration.cc --- a/src/configuration.cc Tue Mar 03 16:50:39 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 . - * ===================================================================== - * - * 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 -#include -#include -#include -#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 EntriesByName; -static QList 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 const& Config::AllConfigEntries() -{ - return ConfigurationEntries; -} - -AbstractConfigEntry* Config::FindByName (QString const& name) -{ - auto it = EntriesByName.find (name); - return (it != EntriesByName.end()) ? *it : null; -} - -template -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 (cfg); -} - -#undef IMPLEMENT_CONFIG - -#define IMPLEMENT_CONFIG(NAME) \ - NAME##ConfigEntry* NAME##ConfigEntry::getByName (QString name) \ - { \ - return GetConfigByName (name, E##NAME##Type); \ - } - -IMPLEMENT_CONFIG (Int) -IMPLEMENT_CONFIG (String) -IMPLEMENT_CONFIG (Bool) -IMPLEMENT_CONFIG (Float) -IMPLEMENT_CONFIG (List) -IMPLEMENT_CONFIG (KeySequence) -IMPLEMENT_CONFIG (Vertex) - -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(); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/configuration.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/configuration.cpp Tue Mar 03 22:29:27 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 . + * ===================================================================== + * + * 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 +#include +#include +#include +#include "main.h" +#include "configuration.h" +#include "miscallenous.h" +#include "mainWindow.h" +#include "ldDocument.h" +#include "glRenderer.h" +#include "config.aux" + +#ifdef _WIN32 +# define EXTENSION ".ini" +#else +# define EXTENSION ".cfg" +#endif // _WIN32 + +#define MAX_CONFIG 512 + +static QMap EntriesByName; +static QList 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 const& Config::AllConfigEntries() +{ + return ConfigurationEntries; +} + +AbstractConfigEntry* Config::FindByName (QString const& name) +{ + auto it = EntriesByName.find (name); + return (it != EntriesByName.end()) ? *it : null; +} + +template +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 (cfg); +} + +#undef IMPLEMENT_CONFIG + +#define IMPLEMENT_CONFIG(NAME) \ + NAME##ConfigEntry* NAME##ConfigEntry::getByName (QString name) \ + { \ + return GetConfigByName (name, E##NAME##Type); \ + } + +IMPLEMENT_CONFIG (Int) +IMPLEMENT_CONFIG (String) +IMPLEMENT_CONFIG (Bool) +IMPLEMENT_CONFIG (Float) +IMPLEMENT_CONFIG (List) +IMPLEMENT_CONFIG (KeySequence) +IMPLEMENT_CONFIG (Vertex) + +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(); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/coverer.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/coverer.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,157 @@ + + + CovererUI + + + + 0 + 0 + 310 + 220 + + + + + 310 + 220 + + + + + 10000 + 10000 + + + + Coverer + + + + + 40 + 180 + 261 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 10 + 5 + 291 + 171 + + + + + + + Shape 1 + + + + + + + Shape 2 + + + + + + + + + + + + + Segment split length: + + + + + + + Bias: + + + + + + + 10000.000000000000000 + + + + + + + -100 + + + 100 + + + + + + + Reverse shape 2 + + + + + + + Old sweep method + + + + + + + + + + buttonBox + accepted() + CovererUI + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CovererUI + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/crashCatcher.cc --- a/src/crashCatcher.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include -#include "crashCatcher.h" -#include "dialogs.h" - -#ifdef __unix__ -# ifdef Q_OS_LINUX -# include -# 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 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 ( - "

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

" - "

Assertion `%4' failed.

", - file, line, funcname, expr)); -#endif - - abort(); -} \ No newline at end of file diff -r ab77deb851fa -r 8d98ee0dc917 src/crashCatcher.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/crashCatcher.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#include +#include "crashCatcher.h" +#include "dialogs.h" + +#ifdef __unix__ +# ifdef Q_OS_LINUX +# include +# 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 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 ( + "

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

" + "

Assertion `%4' failed.

", + file, line, funcname, expr)); +#endif + + abort(); +} \ No newline at end of file diff -r ab77deb851fa -r 8d98ee0dc917 src/dialogs.cc --- a/src/dialogs.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 (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 (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 ("%1", LDPaths::getError())); - okButton()->setEnabled (false); - return; - } - - ui->status->setText ("OK!"); - okButton()->setEnabled (true); -} - -// ============================================================================= -// ============================================================================= -void LDrawPathDialog::slot_accept() -{ - Config::Save(); - accept(); -} - -// ============================================================================= -// ============================================================================= -OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) -{ - ui = new Ui_OpenProgressUI; - ui->setupUi (this); - ui->progressText->setText ("Parsing..."); - setNumLines (0); - m_progress = 0; -} - -// ============================================================================= -// ============================================================================= -OpenProgressDialog::~OpenProgressDialog() -{ - delete ui; -} - -// ============================================================================= -// ============================================================================= -void OpenProgressDialog::setNumLines (int const& a) -{ - m_numLines = a; - ui->progressBar->setRange (0, 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 ("", 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 (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 ?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(); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/dialogs.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dialogs.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 (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 (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 ("%1", LDPaths::getError())); + okButton()->setEnabled (false); + return; + } + + ui->status->setText ("OK!"); + okButton()->setEnabled (true); +} + +// ============================================================================= +// ============================================================================= +void LDrawPathDialog::slot_accept() +{ + Config::Save(); + accept(); +} + +// ============================================================================= +// ============================================================================= +OpenProgressDialog::OpenProgressDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) +{ + ui = new Ui_OpenProgressUI; + ui->setupUi (this); + ui->progressText->setText ("Parsing..."); + setNumLines (0); + m_progress = 0; +} + +// ============================================================================= +// ============================================================================= +OpenProgressDialog::~OpenProgressDialog() +{ + delete ui; +} + +// ============================================================================= +// ============================================================================= +void OpenProgressDialog::setNumLines (int const& a) +{ + m_numLines = a; + ui->progressBar->setRange (0, 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 ("", 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 (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 ?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(); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/documentation.cc --- a/src/documentation.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include -#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 = - "

Overlay images


" - "

" APPNAME " supports drawing transparent images over the part model. This " - "can be used to have, for instance, a photo of the part overlaid on top of the " - "model and use it for drawing curves somewhat accurately.

" - "

For this purpose, a specific photo has to be taken of the part; it should " - "represent the part as true as possible to the actual camera used for editing. " - "The image should be taken from straight above the part, at as an orthogonal " - "angle as possible. It is recommended to take a lot of pictures this way and " - "select the best candidate.

" - "

The image should then be cropped with the knowledge of the image's LDU " - "dimensions in mind. The offset should then be identified in the image in pixels.

" - "

Finally, use the \"Set Overlay Image\" dialog and fill in the details. The " - "overlay image should then be ready for use."; - -// ============================================================================= -// ============================================================================= -void showDocumentation (const char* text) -{ - DocumentViewer dlg; - dlg.setText (text); - dlg.exec(); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/documentation.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/documentation.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#include +#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 = + "

Overlay images


" + "

" APPNAME " supports drawing transparent images over the part model. This " + "can be used to have, for instance, a photo of the part overlaid on top of the " + "model and use it for drawing curves somewhat accurately.

" + "

For this purpose, a specific photo has to be taken of the part; it should " + "represent the part as true as possible to the actual camera used for editing. " + "The image should be taken from straight above the part, at as an orthogonal " + "angle as possible. It is recommended to take a lot of pictures this way and " + "select the best candidate.

" + "

The image should then be cropped with the knowledge of the image's LDU " + "dimensions in mind. The offset should then be identified in the image in pixels.

" + "

Finally, use the \"Set Overlay Image\" dialog and fill in the details. The " + "overlay image should then be ready for use."; + +// ============================================================================= +// ============================================================================= +void showDocumentation (const char* text) +{ + DocumentViewer dlg; + dlg.setText (text); + dlg.exec(); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/downloadfrom.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/downloadfrom.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,141 @@ + + + DownloadFrom + + + + 0 + 0 + 546 + 405 + + + + Download from LDraw.org + + + + + + + 11 + 75 + true + + + + Download from LDraw.org + + + Qt::AlignCenter + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Source: + + + + + + + + 0 + 0 + + + + + Parts tracker + + + + + Custom URL + + + + + + + + File name: + + + + + + + + + + + + false + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + false + + + + File + + + + 50 + false + + + + + + Status + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Abort|QDialogButtonBox::Close + + + + + + + + + buttonBox + rejected() + DownloadFrom + reject() + + + 322 + 312 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/edger2.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/edger2.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,297 @@ + + + Edger2Dialog + + + + 0 + 0 + 357 + 257 + + + + Edger 2 + + + 1.000000000000000 + + + + + + + + + + Precision + + + + + + + + + + 4 + + + 0.001000000000000 + + + + + + + ° + + + 4 + + + 0.000000000000000 + + + 360.000000000000000 + + + 0.100000000000000 + + + 0.100000000000000 + + + + + + + Flat angle + + + + + + + Conditional line angle + + + + + + + ° + + + 4 + + + 360.000000000000000 + + + 0.100000000000000 + + + 60.000000000000000 + + + + + + + ° + + + 4 + + + 360.000000000000000 + + + 0.100000000000000 + + + 60.000000000000000 + + + + + + + Edge line angle + + + + + + + 1 + + + + Only + + + + + Normally + + + + + Never + + + + + + + + Create unmatched edges + + + + + + + + + + + + + Color-coded result + + + + + + + Delete existing lines + + + + + + + Delete existing cond. lines + + + + + + + + + + + File is BFCd + + + + + + + false + + + Convex cond. lines only + + + + + + + false + + + Concave cond. lines only + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Edger2Dialog + accept() + + + 254 + 250 + + + 157 + 256 + + + + + buttonBox + rejected() + Edger2Dialog + reject() + + + 322 + 250 + + + 286 + 256 + + + + + bfc + clicked(bool) + convex + setEnabled(bool) + + + 249 + 157 + + + 248 + 185 + + + + + bfc + clicked(bool) + concave + setEnabled(bool) + + + 283 + 154 + + + 283 + 205 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/editHistory.cc --- a/src/editHistory.cc Tue Mar 03 16:50:39 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 . - */ - -#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(); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/editHistory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editHistory.cpp Tue Mar 03 22:29:27 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 . + */ + +#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(); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/editmodes/abstractEditMode.cc --- a/src/editmodes/abstractEditMode.cc Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,263 +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 . - */ - -#include -#include -#include "abstractEditMode.h" -#include "selectMode.h" -#include "drawMode.h" -#include "rectangleMode.h" -#include "circleMode.h" -#include "magicWandMode.h" -#include "linePathMode.h" -#include "../mainWindow.h" -#include "../glRenderer.h" - -CFGENTRY (Bool, DrawLineLengths, true) -CFGENTRY (Bool, DrawAngles, false) - -AbstractEditMode::AbstractEditMode (GLRenderer* renderer) : - m_renderer (renderer) {} - -AbstractEditMode::~AbstractEditMode() {} - -AbstractEditMode* AbstractEditMode::createByType (GLRenderer* renderer, EditModeType type) -{ - switch (type) - { - case EditModeType::Select: return new SelectMode (renderer); - case EditModeType::Draw: return new DrawMode (renderer); - case EditModeType::Rectangle: return new RectangleMode (renderer); - case EditModeType::Circle: return new CircleMode (renderer); - case EditModeType::MagicWand: return new MagicWandMode (renderer); - case EditModeType::LinePath: return new LinePathMode (renderer); - } - - throw std::logic_error ("bad type given to AbstractEditMode::createByType"); -} - -GLRenderer* AbstractEditMode::renderer() const -{ - return m_renderer; -} - -AbstractDrawMode::AbstractDrawMode (GLRenderer* renderer) : - AbstractEditMode (renderer), - m_polybrush (QBrush (QColor (64, 192, 0, 128))) -{ - // Disable the context menu - we need the right mouse button - // for removing vertices. - renderer->setContextMenuPolicy (Qt::NoContextMenu); - - // Use the crosshair cursor when drawing. - renderer->setCursor (Qt::CrossCursor); - - // Clear the selection when beginning to draw. - CurrentDocument()->clearSelection(); - - g_win->updateSelection(); - m_drawedVerts.clear(); -} - -AbstractSelectMode::AbstractSelectMode (GLRenderer* renderer) : - AbstractEditMode (renderer) -{ - renderer->unsetCursor(); - renderer->setContextMenuPolicy (Qt::DefaultContextMenu); -} - -// ============================================================================= -// -void AbstractDrawMode::addDrawnVertex (Vertex const& pos) -{ - if (preAddVertex (pos)) - return; - - m_drawedVerts << pos; -} - -bool AbstractDrawMode::mouseReleased (MouseEventData const& data) -{ - if (Super::mouseReleased (data)) - return true; - - if ((data.releasedButtons & Qt::MidButton) and (m_drawedVerts.size() < 4) and (not data.mouseMoved)) - { - // Find the closest vertex to our cursor - double minimumDistance = 1024.0; - const Vertex* closest = null; - Vertex cursorPosition = renderer()->coordconv2_3 (data.ev->pos(), false); - QPoint cursorPosition2D (data.ev->pos()); - const Axis relZ = renderer()->getRelativeZ(); - QVector vertices = renderer()->document()->inlineVertices(); - - // Sort the vertices in order of distance to camera - std::sort (vertices.begin(), vertices.end(), [&](const Vertex& a, const Vertex& b) -> bool - { - if (renderer()->getFixedCamera (renderer()->camera()).negatedDepth) - return a[relZ] > b[relZ]; - - return a[relZ] < b[relZ]; - }); - - for (const Vertex& vrt : vertices) - { - // If the vertex in 2d space is very close to the cursor then we use - // it regardless of depth. - QPoint vect2d = renderer()->coordconv3_2 (vrt) - cursorPosition2D; - const double distance2DSquared = std::pow (vect2d.x(), 2) + std::pow (vect2d.y(), 2); - if (distance2DSquared < 16.0 * 16.0) - { - closest = &vrt; - break; - } - - // Check if too far away from the cursor. - if (distance2DSquared > 64.0 * 64.0) - continue; - - // Not very close to the cursor. Compare using true distance, - // including depth. - const double distanceSquared = (vrt - cursorPosition).lengthSquared(); - - if (distanceSquared < minimumDistance) - { - minimumDistance = distanceSquared; - closest = &vrt; - } - } - - if (closest != null) - addDrawnVertex (*closest); - - return true; - } - - if ((data.releasedButtons & Qt::RightButton) and (not m_drawedVerts.isEmpty())) - { - // Remove the last vertex - m_drawedVerts.removeLast(); - - return true; - } - - return false; -} - -void AbstractDrawMode::finishDraw (LDObjectList const& objs) -{ - int pos = g_win->getInsertionPoint(); - - if (objs.size() > 0) - { - for (LDObjectPtr obj : objs) - { - renderer()->document()->insertObj (pos++, obj); - renderer()->compileObject (obj); - } - - g_win->refresh(); - g_win->endAction(); - } - - m_drawedVerts.clear(); -} - -void AbstractDrawMode::drawLength (QPainter &painter, const Vertex &v0, const Vertex &v1, - const QPointF& v0p, const QPointF& v1p) const -{ - if (not cfg::DrawLineLengths) - return; - - const QString label = QString::number ((v1 - v0).length()); - QPoint origin = QLineF (v0p, v1p).pointAt (0.5).toPoint(); - painter.drawText (origin, label); -} - -void AbstractDrawMode::renderPolygon (QPainter& painter, const QVector& poly3d, - bool withlengths, bool withangles) const -{ - QVector poly (poly3d.size()); - QFontMetrics metrics = QFontMetrics (QFont()); - - // Convert to 2D - for (int i = 0; i < poly3d.size(); ++i) - poly[i] = renderer()->coordconv3_2 (poly3d[i]); - - // Draw the polygon-to-be - painter.setBrush (m_polybrush); - painter.drawPolygon (QPolygonF (poly)); - - // Draw vertex blips - for (int i = 0; i < poly3d.size(); ++i) - { - QPoint& blip = poly[i]; - painter.setPen (renderer()->linePen()); - renderer()->drawBlip (painter, blip); - - // Draw their coordinates - painter.setPen (renderer()->textPen()); - painter.drawText (blip.x(), blip.y() - 8, poly3d[i].toString (true)); - } - - // Draw line lenghts and angle info if appropriate - if (poly3d.size() >= 2 and (withlengths or withangles)) - { - painter.setPen (renderer()->textPen()); - - for (int i = 0; i < poly3d.size(); ++i) - { - const int j = (i + 1) % poly3d.size(); - const int h = (i - 1 >= 0) ? (i - 1) : (poly3d.size() - 1); - - if (withlengths) - drawLength (painter, poly3d[i], poly3d[j], poly[i], poly[j]); - - if (withangles and cfg::DrawAngles) - { - QLineF l0 (poly[h], poly[i]), - l1 (poly[i], poly[j]); - - double angle = 180 - l0.angleTo (l1); - - if (angle < 0) - angle = 180 - l1.angleTo (l0); - - QString label = QString::number (angle) + QString::fromUtf8 (QByteArray ("\302\260")); - QPoint pos = poly[i]; - pos.setY (pos.y() + metrics.height()); - - painter.drawText (pos, label); - } - } - } -} - -bool AbstractDrawMode::keyReleased (QKeyEvent *ev) -{ - if (Super::keyReleased (ev)) - return true; - - if (not m_drawedVerts.isEmpty() and ev->key() == Qt::Key_Backspace) - { - m_drawedVerts.removeLast(); - return true; - } - - return false; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/editmodes/abstractEditMode.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editmodes/abstractEditMode.cpp Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,263 @@ +/* + * 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 . + */ + +#include +#include +#include "abstractEditMode.h" +#include "selectMode.h" +#include "drawMode.h" +#include "rectangleMode.h" +#include "circleMode.h" +#include "magicWandMode.h" +#include "linePathMode.h" +#include "../mainWindow.h" +#include "../glRenderer.h" + +CFGENTRY (Bool, DrawLineLengths, true) +CFGENTRY (Bool, DrawAngles, false) + +AbstractEditMode::AbstractEditMode (GLRenderer* renderer) : + m_renderer (renderer) {} + +AbstractEditMode::~AbstractEditMode() {} + +AbstractEditMode* AbstractEditMode::createByType (GLRenderer* renderer, EditModeType type) +{ + switch (type) + { + case EditModeType::Select: return new SelectMode (renderer); + case EditModeType::Draw: return new DrawMode (renderer); + case EditModeType::Rectangle: return new RectangleMode (renderer); + case EditModeType::Circle: return new CircleMode (renderer); + case EditModeType::MagicWand: return new MagicWandMode (renderer); + case EditModeType::LinePath: return new LinePathMode (renderer); + } + + throw std::logic_error ("bad type given to AbstractEditMode::createByType"); +} + +GLRenderer* AbstractEditMode::renderer() const +{ + return m_renderer; +} + +AbstractDrawMode::AbstractDrawMode (GLRenderer* renderer) : + AbstractEditMode (renderer), + m_polybrush (QBrush (QColor (64, 192, 0, 128))) +{ + // Disable the context menu - we need the right mouse button + // for removing vertices. + renderer->setContextMenuPolicy (Qt::NoContextMenu); + + // Use the crosshair cursor when drawing. + renderer->setCursor (Qt::CrossCursor); + + // Clear the selection when beginning to draw. + CurrentDocument()->clearSelection(); + + g_win->updateSelection(); + m_drawedVerts.clear(); +} + +AbstractSelectMode::AbstractSelectMode (GLRenderer* renderer) : + AbstractEditMode (renderer) +{ + renderer->unsetCursor(); + renderer->setContextMenuPolicy (Qt::DefaultContextMenu); +} + +// ============================================================================= +// +void AbstractDrawMode::addDrawnVertex (Vertex const& pos) +{ + if (preAddVertex (pos)) + return; + + m_drawedVerts << pos; +} + +bool AbstractDrawMode::mouseReleased (MouseEventData const& data) +{ + if (Super::mouseReleased (data)) + return true; + + if ((data.releasedButtons & Qt::MidButton) and (m_drawedVerts.size() < 4) and (not data.mouseMoved)) + { + // Find the closest vertex to our cursor + double minimumDistance = 1024.0; + const Vertex* closest = null; + Vertex cursorPosition = renderer()->coordconv2_3 (data.ev->pos(), false); + QPoint cursorPosition2D (data.ev->pos()); + const Axis relZ = renderer()->getRelativeZ(); + QVector vertices = renderer()->document()->inlineVertices(); + + // Sort the vertices in order of distance to camera + std::sort (vertices.begin(), vertices.end(), [&](const Vertex& a, const Vertex& b) -> bool + { + if (renderer()->getFixedCamera (renderer()->camera()).negatedDepth) + return a[relZ] > b[relZ]; + + return a[relZ] < b[relZ]; + }); + + for (const Vertex& vrt : vertices) + { + // If the vertex in 2d space is very close to the cursor then we use + // it regardless of depth. + QPoint vect2d = renderer()->coordconv3_2 (vrt) - cursorPosition2D; + const double distance2DSquared = std::pow (vect2d.x(), 2) + std::pow (vect2d.y(), 2); + if (distance2DSquared < 16.0 * 16.0) + { + closest = &vrt; + break; + } + + // Check if too far away from the cursor. + if (distance2DSquared > 64.0 * 64.0) + continue; + + // Not very close to the cursor. Compare using true distance, + // including depth. + const double distanceSquared = (vrt - cursorPosition).lengthSquared(); + + if (distanceSquared < minimumDistance) + { + minimumDistance = distanceSquared; + closest = &vrt; + } + } + + if (closest != null) + addDrawnVertex (*closest); + + return true; + } + + if ((data.releasedButtons & Qt::RightButton) and (not m_drawedVerts.isEmpty())) + { + // Remove the last vertex + m_drawedVerts.removeLast(); + + return true; + } + + return false; +} + +void AbstractDrawMode::finishDraw (LDObjectList const& objs) +{ + int pos = g_win->getInsertionPoint(); + + if (objs.size() > 0) + { + for (LDObjectPtr obj : objs) + { + renderer()->document()->insertObj (pos++, obj); + renderer()->compileObject (obj); + } + + g_win->refresh(); + g_win->endAction(); + } + + m_drawedVerts.clear(); +} + +void AbstractDrawMode::drawLength (QPainter &painter, const Vertex &v0, const Vertex &v1, + const QPointF& v0p, const QPointF& v1p) const +{ + if (not cfg::DrawLineLengths) + return; + + const QString label = QString::number ((v1 - v0).length()); + QPoint origin = QLineF (v0p, v1p).pointAt (0.5).toPoint(); + painter.drawText (origin, label); +} + +void AbstractDrawMode::renderPolygon (QPainter& painter, const QVector& poly3d, + bool withlengths, bool withangles) const +{ + QVector poly (poly3d.size()); + QFontMetrics metrics = QFontMetrics (QFont()); + + // Convert to 2D + for (int i = 0; i < poly3d.size(); ++i) + poly[i] = renderer()->coordconv3_2 (poly3d[i]); + + // Draw the polygon-to-be + painter.setBrush (m_polybrush); + painter.drawPolygon (QPolygonF (poly)); + + // Draw vertex blips + for (int i = 0; i < poly3d.size(); ++i) + { + QPoint& blip = poly[i]; + painter.setPen (renderer()->linePen()); + renderer()->drawBlip (painter, blip); + + // Draw their coordinates + painter.setPen (renderer()->textPen()); + painter.drawText (blip.x(), blip.y() - 8, poly3d[i].toString (true)); + } + + // Draw line lenghts and angle info if appropriate + if (poly3d.size() >= 2 and (withlengths or withangles)) + { + painter.setPen (renderer()->textPen()); + + for (int i = 0; i < poly3d.size(); ++i) + { + const int j = (i + 1) % poly3d.size(); + const int h = (i - 1 >= 0) ? (i - 1) : (poly3d.size() - 1); + + if (withlengths) + drawLength (painter, poly3d[i], poly3d[j], poly[i], poly[j]); + + if (withangles and cfg::DrawAngles) + { + QLineF l0 (poly[h], poly[i]), + l1 (poly[i], poly[j]); + + double angle = 180 - l0.angleTo (l1); + + if (angle < 0) + angle = 180 - l1.angleTo (l0); + + QString label = QString::number (angle) + QString::fromUtf8 (QByteArray ("\302\260")); + QPoint pos = poly[i]; + pos.setY (pos.y() + metrics.height()); + + painter.drawText (pos, label); + } + } + } +} + +bool AbstractDrawMode::keyReleased (QKeyEvent *ev) +{ + if (Super::keyReleased (ev)) + return true; + + if (not m_drawedVerts.isEmpty() and ev->key() == Qt::Key_Backspace) + { + m_drawedVerts.removeLast(); + return true; + } + + return false; +} diff -r ab77deb851fa -r 8d98ee0dc917 src/editmodes/circleMode.cc --- a/src/editmodes/circleMode.cc Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,315 +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 . - */ - -#include -#include "circleMode.h" -#include "../miscallenous.h" -#include "../ldObject.h" -#include "../ldDocument.h" -#include "../ringFinder.h" -#include "../primitives.h" -#include "../glRenderer.h" -#include "../mainWindow.h" -#include "../ldObjectMath.h" - -CircleMode::CircleMode (GLRenderer* renderer) : - Super (renderer) {} - -EditModeType CircleMode::type() const -{ - return EditModeType::Circle; -} - -double CircleMode::getCircleDrawDist (int pos) const -{ - assert (m_drawedVerts.size() >= pos + 1); - Vertex v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : - renderer()->coordconv2_3 (renderer()->mousePosition(), false); - Axis localx, localy; - renderer()->getRelativeAxes (localx, localy); - double dx = m_drawedVerts[0][localx] - v1[localx]; - double dy = m_drawedVerts[0][localy] - v1[localy]; - return Grid::Snap (sqrt ((dx * dx) + (dy * dy)), Grid::Coordinate); -} - -Matrix CircleMode::getCircleDrawMatrix (double scale) -{ - // Matrix templates. 2 is substituted with the scale value, 1 is inverted to -1 if needed. - static const Matrix templates[3] = - { - { 2, 0, 0, 0, 1, 0, 0, 0, 2 }, - { 2, 0, 0, 0, 0, 2, 0, 1, 0 }, - { 0, 1, 0, 2, 0, 0, 0, 0, 2 }, - }; - - Matrix transform = templates[renderer()->camera() % 3]; - - for (int i = 0; i < 9; ++i) - { - if (transform[i] == 2) - transform[i] = scale; - elif (transform[i] == 1 and renderer()->camera() >= 3) - transform[i] = -1; - } - - return transform; -} - -void CircleMode::buildCircle() -{ - LDObjectList objs; - const int segments (g_win->ringToolSegments()); - const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution); - double dist0 (getCircleDrawDist (0)); - double dist1 (getCircleDrawDist (1)); - LDDocumentPtr refFile; - Matrix transform; - bool circleOrDisc = false; - - if (dist1 < dist0) - qSwap (dist0, dist1); - - if (dist0 == dist1) - { - // If the radii are the same, there's no ring space to fill. Use a circle. - refFile = GetPrimitive (::Circle, segments, divisions, 0); - transform = getCircleDrawMatrix (dist0); - circleOrDisc = true; - } - elif (dist0 == 0 or dist1 == 0) - { - // If either radii is 0, use a disc. - refFile = GetPrimitive (::Disc, segments, divisions, 0); - transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); - circleOrDisc = true; - } - elif (g_RingFinder.findRings (dist0, dist1)) - { - // The ring finder found a solution, use that. Add the component rings to the file. - for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents()) - { - refFile = GetPrimitive (::Ring, segments, divisions, cmp.num); - LDSubfilePtr ref = LDSpawn(); - ref->setFileInfo (refFile); - ref->setTransform (getCircleDrawMatrix (cmp.scale)); - ref->setPosition (m_drawedVerts[0]); - ref->setColor (MainColor()); - objs << ref; - } - } - else - { - // Ring finder failed, last resort: draw the ring with quads - QList c0, c1; - Axis localx, localy, localz; - renderer()->getRelativeAxes (localx, localy); - localz = (Axis) (3 - localx - localy); - double x0 (m_drawedVerts[0][localx]); - double y0 (m_drawedVerts[0][localy]); - - Vertex templ; - templ.setCoordinate (localx, x0); - templ.setCoordinate (localy, y0); - templ.setCoordinate (localz, renderer()->getDepthValue()); - - // Calculate circle coords - MakeCircle (segments, divisions, dist0, c0); - MakeCircle (segments, divisions, dist1, c1); - - for (int i = 0; i < segments; ++i) - { - Vertex v0, v1, v2, v3; - v0 = v1 = v2 = v3 = templ; - v0.setCoordinate (localx, v0[localx] + c0[i].x1()); - v0.setCoordinate (localy, v0[localy] + c0[i].y1()); - v1.setCoordinate (localx, v1[localx] + c0[i].x2()); - v1.setCoordinate (localy, v1[localy] + c0[i].y2()); - v2.setCoordinate (localx, v2[localx] + c1[i].x2()); - v2.setCoordinate (localy, v2[localy] + c1[i].y2()); - v3.setCoordinate (localx, v3[localx] + c1[i].x1()); - v3.setCoordinate (localy, v3[localy] + c1[i].y1()); - - LDQuadPtr quad (LDSpawn (v0, v1, v2, v3)); - quad->setColor (MainColor()); - - // Ensure the quads always are BFC-front towards the camera - if (renderer()->camera() % 3 <= 0) - quad->invert(); - - objs << quad; - } - } - - if (circleOrDisc and refFile != null) - { - LDSubfilePtr ref = LDSpawn(); - ref->setFileInfo (refFile); - ref->setTransform (transform); - ref->setPosition (m_drawedVerts[0]); - ref->setColor (MainColor()); - objs << ref; - } - - unless (objs.isEmpty()) - { - Axis relZ = renderer()->getRelativeZ();; - const int l (relZ == X ? 1 : 0); - const int m (relZ == Y ? 1 : 0); - const int n (relZ == Z ? 1 : 0); - RotateObjects (l, m, n, -m_angleOffset, objs); - } - - finishDraw (objs); -} - -double CircleMode::getAngleOffset() const -{ - if (m_drawedVerts.isEmpty()) - return 0.0; - - const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution); - QPointF originspot (renderer()->coordconv3_2 (m_drawedVerts.first())); - QLineF bearing (originspot, renderer()->mousePositionF()); - QLineF bearing2 (originspot, QPointF (originspot.x(), 0.0)); - double angleoffset (-bearing.angleTo (bearing2) + 90); - angleoffset /= (360.0 / divisions); // convert angle to 0-16 scale - angleoffset = round (angleoffset); // round to nearest 16th - angleoffset *= ((2 * Pi) / divisions); // convert to radians - angleoffset *= renderer()->depthNegateFactor(); // negate based on camera - return angleoffset; -} - -void CircleMode::render (QPainter& painter) const -{ - QFontMetrics metrics = QFontMetrics (QFont()); - - // If we have not specified the center point of the circle yet, preview it on the screen. - if (m_drawedVerts.isEmpty()) - { - renderer()->drawBlip (painter, renderer()->coordconv3_2 (renderer()->position3D())); - return; - } - - QVector innerverts, outerverts; - QVector innerverts2d, outerverts2d; - const double innerdistance (getCircleDrawDist (0)); - const double outerdistance (m_drawedVerts.size() >= 2 ? getCircleDrawDist (1) : -1); - const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution); - const int segments (g_win->ringToolSegments()); - const double angleUnit (2 * Pi / divisions); - Axis relX, relY; - renderer()->getRelativeAxes (relX, relY); - const double angleoffset (m_drawedVerts.size() < 3 ? getAngleOffset() : m_angleOffset); - - // Calculate the preview positions of vertices - for (int i = 0; i < segments + 1; ++i) - { - const double sinangle (sin (angleoffset + i * angleUnit)); - const double cosangle (cos (angleoffset + i * angleUnit)); - Vertex v (Origin); - v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * innerdistance)); - v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * innerdistance)); - innerverts << v; - innerverts2d << renderer()->coordconv3_2 (v); - - if (outerdistance != -1) - { - v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * outerdistance)); - v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * outerdistance)); - outerverts << v; - outerverts2d << renderer()->coordconv3_2 (v); - } - } - - QVector lines (segments); - - if (outerdistance != -1 and outerdistance != innerdistance) - { - painter.setBrush (m_polybrush); - painter.setPen (Qt::NoPen); - - // Compile polygons - for (int i = 0; i < segments; ++i) - { - QVector points; - points << innerverts2d[i] - << innerverts2d[i + 1] - << outerverts2d[i + 1] - << outerverts2d[i]; - painter.drawPolygon (QPolygonF (points)); - lines << QLineF (innerverts2d[i], innerverts2d[i + 1]); - lines << QLineF (outerverts2d[i], outerverts2d[i + 1]); - } - - // Add bordering edges for unclosed rings/discs - if (segments != divisions) - { - lines << QLineF (innerverts2d.first(), outerverts2d.first()); - lines << QLineF (innerverts2d.last(), outerverts2d.last()); - } - } - else - { - for (int i = 0; i < segments; ++i) - lines << QLineF (innerverts2d[i], innerverts2d[i + 1]); - } - - // Draw a green blips at where the points are - for (QPointF const& point : innerverts2d + outerverts2d) - renderer()->drawBlip (painter, point); - - // Draw edge lines - painter.setPen (renderer()->linePen()); - painter.drawLines (lines); - - // Draw the current radius in the middle of the circle. - QPoint origin = renderer()->coordconv3_2 (m_drawedVerts[0]); - QString label = QString::number (innerdistance); - painter.setPen (renderer()->textPen()); - painter.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label); - - if (m_drawedVerts.size() >= 2) - { - painter.drawText (origin.x() - (metrics.width (label) / 2), - origin.y() + metrics.height(), QString::number (outerdistance)); - } -} - -bool CircleMode::mouseReleased (MouseEventData const& data) -{ - if (Super::mouseReleased (data)) - return true; - - if (data.releasedButtons & Qt::LeftButton) - { - if (m_drawedVerts.size() < 3) - addDrawnVertex (renderer()->position3D()); - else - buildCircle(); - - return true; - } - - return false; -} - -bool CircleMode::preAddVertex (const Vertex&) -{ - m_angleOffset = getAngleOffset(); - return false; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/editmodes/circleMode.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editmodes/circleMode.cpp Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,315 @@ +/* + * 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 . + */ + +#include +#include "circleMode.h" +#include "../miscallenous.h" +#include "../ldObject.h" +#include "../ldDocument.h" +#include "../ringFinder.h" +#include "../primitives.h" +#include "../glRenderer.h" +#include "../mainWindow.h" +#include "../ldObjectMath.h" + +CircleMode::CircleMode (GLRenderer* renderer) : + Super (renderer) {} + +EditModeType CircleMode::type() const +{ + return EditModeType::Circle; +} + +double CircleMode::getCircleDrawDist (int pos) const +{ + assert (m_drawedVerts.size() >= pos + 1); + Vertex v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : + renderer()->coordconv2_3 (renderer()->mousePosition(), false); + Axis localx, localy; + renderer()->getRelativeAxes (localx, localy); + double dx = m_drawedVerts[0][localx] - v1[localx]; + double dy = m_drawedVerts[0][localy] - v1[localy]; + return Grid::Snap (sqrt ((dx * dx) + (dy * dy)), Grid::Coordinate); +} + +Matrix CircleMode::getCircleDrawMatrix (double scale) +{ + // Matrix templates. 2 is substituted with the scale value, 1 is inverted to -1 if needed. + static const Matrix templates[3] = + { + { 2, 0, 0, 0, 1, 0, 0, 0, 2 }, + { 2, 0, 0, 0, 0, 2, 0, 1, 0 }, + { 0, 1, 0, 2, 0, 0, 0, 0, 2 }, + }; + + Matrix transform = templates[renderer()->camera() % 3]; + + for (int i = 0; i < 9; ++i) + { + if (transform[i] == 2) + transform[i] = scale; + elif (transform[i] == 1 and renderer()->camera() >= 3) + transform[i] = -1; + } + + return transform; +} + +void CircleMode::buildCircle() +{ + LDObjectList objs; + const int segments (g_win->ringToolSegments()); + const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution); + double dist0 (getCircleDrawDist (0)); + double dist1 (getCircleDrawDist (1)); + LDDocumentPtr refFile; + Matrix transform; + bool circleOrDisc = false; + + if (dist1 < dist0) + qSwap (dist0, dist1); + + if (dist0 == dist1) + { + // If the radii are the same, there's no ring space to fill. Use a circle. + refFile = GetPrimitive (::Circle, segments, divisions, 0); + transform = getCircleDrawMatrix (dist0); + circleOrDisc = true; + } + elif (dist0 == 0 or dist1 == 0) + { + // If either radii is 0, use a disc. + refFile = GetPrimitive (::Disc, segments, divisions, 0); + transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); + circleOrDisc = true; + } + elif (g_RingFinder.findRings (dist0, dist1)) + { + // The ring finder found a solution, use that. Add the component rings to the file. + for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents()) + { + refFile = GetPrimitive (::Ring, segments, divisions, cmp.num); + LDSubfilePtr ref = LDSpawn(); + ref->setFileInfo (refFile); + ref->setTransform (getCircleDrawMatrix (cmp.scale)); + ref->setPosition (m_drawedVerts[0]); + ref->setColor (MainColor()); + objs << ref; + } + } + else + { + // Ring finder failed, last resort: draw the ring with quads + QList c0, c1; + Axis localx, localy, localz; + renderer()->getRelativeAxes (localx, localy); + localz = (Axis) (3 - localx - localy); + double x0 (m_drawedVerts[0][localx]); + double y0 (m_drawedVerts[0][localy]); + + Vertex templ; + templ.setCoordinate (localx, x0); + templ.setCoordinate (localy, y0); + templ.setCoordinate (localz, renderer()->getDepthValue()); + + // Calculate circle coords + MakeCircle (segments, divisions, dist0, c0); + MakeCircle (segments, divisions, dist1, c1); + + for (int i = 0; i < segments; ++i) + { + Vertex v0, v1, v2, v3; + v0 = v1 = v2 = v3 = templ; + v0.setCoordinate (localx, v0[localx] + c0[i].x1()); + v0.setCoordinate (localy, v0[localy] + c0[i].y1()); + v1.setCoordinate (localx, v1[localx] + c0[i].x2()); + v1.setCoordinate (localy, v1[localy] + c0[i].y2()); + v2.setCoordinate (localx, v2[localx] + c1[i].x2()); + v2.setCoordinate (localy, v2[localy] + c1[i].y2()); + v3.setCoordinate (localx, v3[localx] + c1[i].x1()); + v3.setCoordinate (localy, v3[localy] + c1[i].y1()); + + LDQuadPtr quad (LDSpawn (v0, v1, v2, v3)); + quad->setColor (MainColor()); + + // Ensure the quads always are BFC-front towards the camera + if (renderer()->camera() % 3 <= 0) + quad->invert(); + + objs << quad; + } + } + + if (circleOrDisc and refFile != null) + { + LDSubfilePtr ref = LDSpawn(); + ref->setFileInfo (refFile); + ref->setTransform (transform); + ref->setPosition (m_drawedVerts[0]); + ref->setColor (MainColor()); + objs << ref; + } + + unless (objs.isEmpty()) + { + Axis relZ = renderer()->getRelativeZ();; + const int l (relZ == X ? 1 : 0); + const int m (relZ == Y ? 1 : 0); + const int n (relZ == Z ? 1 : 0); + RotateObjects (l, m, n, -m_angleOffset, objs); + } + + finishDraw (objs); +} + +double CircleMode::getAngleOffset() const +{ + if (m_drawedVerts.isEmpty()) + return 0.0; + + const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution); + QPointF originspot (renderer()->coordconv3_2 (m_drawedVerts.first())); + QLineF bearing (originspot, renderer()->mousePositionF()); + QLineF bearing2 (originspot, QPointF (originspot.x(), 0.0)); + double angleoffset (-bearing.angleTo (bearing2) + 90); + angleoffset /= (360.0 / divisions); // convert angle to 0-16 scale + angleoffset = round (angleoffset); // round to nearest 16th + angleoffset *= ((2 * Pi) / divisions); // convert to radians + angleoffset *= renderer()->depthNegateFactor(); // negate based on camera + return angleoffset; +} + +void CircleMode::render (QPainter& painter) const +{ + QFontMetrics metrics = QFontMetrics (QFont()); + + // If we have not specified the center point of the circle yet, preview it on the screen. + if (m_drawedVerts.isEmpty()) + { + renderer()->drawBlip (painter, renderer()->coordconv3_2 (renderer()->position3D())); + return; + } + + QVector innerverts, outerverts; + QVector innerverts2d, outerverts2d; + const double innerdistance (getCircleDrawDist (0)); + const double outerdistance (m_drawedVerts.size() >= 2 ? getCircleDrawDist (1) : -1); + const int divisions (g_win->ringToolHiRes() ? HighResolution : LowResolution); + const int segments (g_win->ringToolSegments()); + const double angleUnit (2 * Pi / divisions); + Axis relX, relY; + renderer()->getRelativeAxes (relX, relY); + const double angleoffset (m_drawedVerts.size() < 3 ? getAngleOffset() : m_angleOffset); + + // Calculate the preview positions of vertices + for (int i = 0; i < segments + 1; ++i) + { + const double sinangle (sin (angleoffset + i * angleUnit)); + const double cosangle (cos (angleoffset + i * angleUnit)); + Vertex v (Origin); + v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * innerdistance)); + v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * innerdistance)); + innerverts << v; + innerverts2d << renderer()->coordconv3_2 (v); + + if (outerdistance != -1) + { + v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * outerdistance)); + v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * outerdistance)); + outerverts << v; + outerverts2d << renderer()->coordconv3_2 (v); + } + } + + QVector lines (segments); + + if (outerdistance != -1 and outerdistance != innerdistance) + { + painter.setBrush (m_polybrush); + painter.setPen (Qt::NoPen); + + // Compile polygons + for (int i = 0; i < segments; ++i) + { + QVector points; + points << innerverts2d[i] + << innerverts2d[i + 1] + << outerverts2d[i + 1] + << outerverts2d[i]; + painter.drawPolygon (QPolygonF (points)); + lines << QLineF (innerverts2d[i], innerverts2d[i + 1]); + lines << QLineF (outerverts2d[i], outerverts2d[i + 1]); + } + + // Add bordering edges for unclosed rings/discs + if (segments != divisions) + { + lines << QLineF (innerverts2d.first(), outerverts2d.first()); + lines << QLineF (innerverts2d.last(), outerverts2d.last()); + } + } + else + { + for (int i = 0; i < segments; ++i) + lines << QLineF (innerverts2d[i], innerverts2d[i + 1]); + } + + // Draw a green blips at where the points are + for (QPointF const& point : innerverts2d + outerverts2d) + renderer()->drawBlip (painter, point); + + // Draw edge lines + painter.setPen (renderer()->linePen()); + painter.drawLines (lines); + + // Draw the current radius in the middle of the circle. + QPoint origin = renderer()->coordconv3_2 (m_drawedVerts[0]); + QString label = QString::number (innerdistance); + painter.setPen (renderer()->textPen()); + painter.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label); + + if (m_drawedVerts.size() >= 2) + { + painter.drawText (origin.x() - (metrics.width (label) / 2), + origin.y() + metrics.height(), QString::number (outerdistance)); + } +} + +bool CircleMode::mouseReleased (MouseEventData const& data) +{ + if (Super::mouseReleased (data)) + return true; + + if (data.releasedButtons & Qt::LeftButton) + { + if (m_drawedVerts.size() < 3) + addDrawnVertex (renderer()->position3D()); + else + buildCircle(); + + return true; + } + + return false; +} + +bool CircleMode::preAddVertex (const Vertex&) +{ + m_angleOffset = getAngleOffset(); + return false; +} diff -r ab77deb851fa -r 8d98ee0dc917 src/editmodes/drawMode.cc --- a/src/editmodes/drawMode.cc Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,156 +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 . - */ - -#include -#include -#include "drawMode.h" -#include "../ldObject.h" -#include "../glRenderer.h" -#include "../miscallenous.h" - -DrawMode::DrawMode (GLRenderer* renderer) : - Super (renderer) {} - -EditModeType DrawMode::type() const -{ - return EditModeType::Draw; -} - -void DrawMode::render (QPainter& painter) const -{ - QVector poly; - QFontMetrics metrics = QFontMetrics (QFont()); - - for (Vertex const& vert : m_drawedVerts) - poly << vert; - - // Draw the cursor vertex as the last one in the list. - if (poly.size() < 4) - poly << getCursorVertex(); - - renderPolygon (painter, poly, true, true); -} - -bool DrawMode::preAddVertex (Vertex const& pos) -{ - // If we picked an already-existing vertex, stop drawing - for (Vertex& vert : m_drawedVerts) - { - if (vert == pos) - { - endDraw(); - return true; - } - } - - return false; -} - -bool DrawMode::mouseReleased (MouseEventData const& data) -{ - if (Super::mouseReleased (data)) - return true; - - if (data.releasedButtons & Qt::LeftButton) - { - // If we have 4 verts, stop drawing. - if (m_drawedVerts.size() >= 4) - { - endDraw(); - return true; - } - - addDrawnVertex (getCursorVertex()); - return true; - } - - return false; -} - -void DrawMode::endDraw() -{ - // Clean the selection and create the object - QList& verts = m_drawedVerts; - LDObjectList objs; - - switch (verts.size()) - { - case 1: - return; - - case 2: - { - // 2 verts - make a line - LDLinePtr obj = LDSpawn (verts[0], verts[1]); - obj->setColor (EdgeColor()); - objs << obj; - break; - } - - case 3: - case 4: - { - LDObjectPtr obj = (verts.size() == 3) ? - static_cast (LDSpawn()) : - static_cast (LDSpawn()); - - obj->setColor (MainColor()); - - for (int i = 0; i < verts.size(); ++i) - obj->setVertex (i, verts[i]); - - objs << obj; - break; - } - } - - finishDraw (objs); -} - -template -_Type IntervalClamp (_Type a, _Type interval) -{ - _Type remainder = a % interval; - - if (remainder >= float (interval / 2)) - a += interval; - - a -= remainder; - return a; -} - -Vertex DrawMode::getCursorVertex() const -{ - Vertex result = renderer()->position3D(); - - if (renderer()->keyboardModifiers() & Qt::ControlModifier - and not m_drawedVerts.isEmpty()) - { - Vertex const& v0 = m_drawedVerts.last(); - Vertex const& v1 = result; - Axis relX, relY; - - renderer()->getRelativeAxes (relX, relY); - QLineF ln (v0[relX], v0[relY], v1[relX], v1[relY]); - ln.setAngle (IntervalClamp (ln.angle(), 45)); - result.setCoordinate (relX, Grid::Snap (ln.x2(), Grid::Coordinate)); - result.setCoordinate (relY, Grid::Snap (ln.y2(), Grid::Coordinate)); - } - - return result; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/editmodes/drawMode.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editmodes/drawMode.cpp Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,156 @@ +/* + * 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 . + */ + +#include +#include +#include "drawMode.h" +#include "../ldObject.h" +#include "../glRenderer.h" +#include "../miscallenous.h" + +DrawMode::DrawMode (GLRenderer* renderer) : + Super (renderer) {} + +EditModeType DrawMode::type() const +{ + return EditModeType::Draw; +} + +void DrawMode::render (QPainter& painter) const +{ + QVector poly; + QFontMetrics metrics = QFontMetrics (QFont()); + + for (Vertex const& vert : m_drawedVerts) + poly << vert; + + // Draw the cursor vertex as the last one in the list. + if (poly.size() < 4) + poly << getCursorVertex(); + + renderPolygon (painter, poly, true, true); +} + +bool DrawMode::preAddVertex (Vertex const& pos) +{ + // If we picked an already-existing vertex, stop drawing + for (Vertex& vert : m_drawedVerts) + { + if (vert == pos) + { + endDraw(); + return true; + } + } + + return false; +} + +bool DrawMode::mouseReleased (MouseEventData const& data) +{ + if (Super::mouseReleased (data)) + return true; + + if (data.releasedButtons & Qt::LeftButton) + { + // If we have 4 verts, stop drawing. + if (m_drawedVerts.size() >= 4) + { + endDraw(); + return true; + } + + addDrawnVertex (getCursorVertex()); + return true; + } + + return false; +} + +void DrawMode::endDraw() +{ + // Clean the selection and create the object + QList& verts = m_drawedVerts; + LDObjectList objs; + + switch (verts.size()) + { + case 1: + return; + + case 2: + { + // 2 verts - make a line + LDLinePtr obj = LDSpawn (verts[0], verts[1]); + obj->setColor (EdgeColor()); + objs << obj; + break; + } + + case 3: + case 4: + { + LDObjectPtr obj = (verts.size() == 3) ? + static_cast (LDSpawn()) : + static_cast (LDSpawn()); + + obj->setColor (MainColor()); + + for (int i = 0; i < verts.size(); ++i) + obj->setVertex (i, verts[i]); + + objs << obj; + break; + } + } + + finishDraw (objs); +} + +template +_Type IntervalClamp (_Type a, _Type interval) +{ + _Type remainder = a % interval; + + if (remainder >= float (interval / 2)) + a += interval; + + a -= remainder; + return a; +} + +Vertex DrawMode::getCursorVertex() const +{ + Vertex result = renderer()->position3D(); + + if (renderer()->keyboardModifiers() & Qt::ControlModifier + and not m_drawedVerts.isEmpty()) + { + Vertex const& v0 = m_drawedVerts.last(); + Vertex const& v1 = result; + Axis relX, relY; + + renderer()->getRelativeAxes (relX, relY); + QLineF ln (v0[relX], v0[relY], v1[relX], v1[relY]); + ln.setAngle (IntervalClamp (ln.angle(), 45)); + result.setCoordinate (relX, Grid::Snap (ln.x2(), Grid::Coordinate)); + result.setCoordinate (relY, Grid::Snap (ln.y2(), Grid::Coordinate)); + } + + return result; +} diff -r ab77deb851fa -r 8d98ee0dc917 src/editmodes/magicWandMode.cc --- a/src/editmodes/magicWandMode.cc Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,220 +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 . - */ - -#include -#include "magicWandMode.h" -#include "../ldDocument.h" -#include "../mainWindow.h" -#include "../glRenderer.h" - -MagicWandMode::MagicWandMode (GLRenderer* renderer) : - Super (renderer) -{ - // Get vertex<->object data - for (LDObjectPtr obj : CurrentDocument()->objects()) - { - // Note: this deliberately only takes vertex-objects into account. - // The magic wand does not process subparts. - for (int i = 0; i < obj->numVertices(); ++i) - m_vertices[obj->vertex (i)] << obj; - } -} - -EditModeType MagicWandMode::type() const -{ - return EditModeType::MagicWand; -} - -void MagicWandMode::fillBoundaries (LDObjectPtr obj, QVector& boundaries, QVector& candidates) -{ - // All boundaries obviously share vertices with the object, therefore they're all in the list - // of candidates. - for (auto it = candidates.begin(); it != candidates.end(); ++it) - { - if (not Eq ((*it)->type(), OBJ_Line, OBJ_CondLine) or (*it)->vertex (0) == (*it)->vertex (1)) - continue; - - int matches = 0; - - for (int i = 0; i < obj->numVertices(); ++i) - { - if (not Eq (obj->vertex (i), (*it)->vertex (0), (*it)->vertex (1))) - continue; - - if (++matches == 2) - { - // Boundary found. If it's an edgeline, add it to the boundaries list, if a - // conditional line, select it. - if ((*it)->type() == OBJ_CondLine) - m_selection << *it; - else - boundaries.append (std::make_tuple ((*it)->vertex (0), (*it)->vertex (1))); - - break; - } - } - } -} - -void MagicWandMode::doMagic (LDObjectPtr obj, MagicWandMode::MagicType type) -{ - if (obj == null) - { - if (type == Set) - { - CurrentDocument()->clearSelection(); - g_win->buildObjList(); - } - - return; - } - - int matchesneeded = 0; - QVector boundaries; - LDObjectType objtype = obj->type(); - - if (type != InternalRecursion) - { - m_selection.clear(); - m_selection.append (obj); - } - - switch (obj->type()) - { - case OBJ_Line: - case OBJ_CondLine: - matchesneeded = 1; - break; - - case OBJ_Triangle: - case OBJ_Quad: - matchesneeded = 2; - break; - - default: - return; - } - - QVector candidates; - - // Get the list of objects that touch this object, i.e. share a vertex - // with this. - for (int i = 0; i < obj->numVertices(); ++i) - candidates += m_vertices[obj->vertex (i)]; - - RemoveDuplicates (candidates); - - // If we're dealing with surfaces, get a list of boundaries. - if (matchesneeded > 1) - fillBoundaries (obj, boundaries, candidates); - - for (LDObjectPtr candidate : candidates) - { - try - { - // If we're doing this on lines, we need exact type match. Surface types (quads and - // triangles) can be mixed. Also don't consider self a candidate, and don't consider - // objects we have already processed. - if ((candidate == obj) or - (candidate->color() != obj->color()) or - (m_selection.contains (candidate)) or - (matchesneeded == 1 and (candidate->type() != objtype)) or - ((candidate->numVertices() > 2) ^ (matchesneeded == 2))) - { - throw 0; - } - - // Now ensure the two objects share enough vertices. - QVector matches; - - for (int i = 0; i < obj->numVertices(); ++i) - { - for (int j = 0; j < candidate->numVertices(); ++j) - { - if (obj->vertex(i) == candidate->vertex(j)) - { - matches << obj->vertex(i); - break; - } - } - } - - if (matches.size() < matchesneeded) - throw 0; // Not enough matches. - - // Check if a boundary gets in between the objects. - for (auto boundary : boundaries) - { - if (Eq (matches[0], std::get<0> (boundary), std::get<1> (boundary)) and - Eq (matches[1], std::get<0> (boundary), std::get<1> (boundary))) - { - throw 0; - } - } - - m_selection.append (candidate); - doMagic (candidate, InternalRecursion); - } - catch (int&) - { - continue; - } - } - - switch (type) - { - case Set: - CurrentDocument()->clearSelection(); - case Additive: - for (LDObjectPtr obj : m_selection) - obj->select(); - break; - - case Subtractive: - for (LDObjectPtr obj : m_selection) - obj->deselect(); - break; - - case InternalRecursion: - break; - } - - if (type != InternalRecursion) - g_win->buildObjList(); -} - -bool MagicWandMode::mouseReleased (MouseEventData const& data) -{ - if (Super::mouseReleased (data)) - return true; - - if (data.releasedButtons & Qt::LeftButton and not data.mouseMoved) - { - MagicType wandtype = MagicWandMode::Set; - - if (data.keymods & Qt::ShiftModifier) - wandtype = MagicWandMode::Additive; - elif (data.keymods & Qt::ControlModifier) - wandtype = MagicWandMode::Subtractive; - - doMagic (renderer()->pickOneObject (data.ev->x(), data.ev->y()), wandtype); - return true; - } - - return false; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/editmodes/magicWandMode.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editmodes/magicWandMode.cpp Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,220 @@ +/* + * 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 . + */ + +#include +#include "magicWandMode.h" +#include "../ldDocument.h" +#include "../mainWindow.h" +#include "../glRenderer.h" + +MagicWandMode::MagicWandMode (GLRenderer* renderer) : + Super (renderer) +{ + // Get vertex<->object data + for (LDObjectPtr obj : CurrentDocument()->objects()) + { + // Note: this deliberately only takes vertex-objects into account. + // The magic wand does not process subparts. + for (int i = 0; i < obj->numVertices(); ++i) + m_vertices[obj->vertex (i)] << obj; + } +} + +EditModeType MagicWandMode::type() const +{ + return EditModeType::MagicWand; +} + +void MagicWandMode::fillBoundaries (LDObjectPtr obj, QVector& boundaries, QVector& candidates) +{ + // All boundaries obviously share vertices with the object, therefore they're all in the list + // of candidates. + for (auto it = candidates.begin(); it != candidates.end(); ++it) + { + if (not Eq ((*it)->type(), OBJ_Line, OBJ_CondLine) or (*it)->vertex (0) == (*it)->vertex (1)) + continue; + + int matches = 0; + + for (int i = 0; i < obj->numVertices(); ++i) + { + if (not Eq (obj->vertex (i), (*it)->vertex (0), (*it)->vertex (1))) + continue; + + if (++matches == 2) + { + // Boundary found. If it's an edgeline, add it to the boundaries list, if a + // conditional line, select it. + if ((*it)->type() == OBJ_CondLine) + m_selection << *it; + else + boundaries.append (std::make_tuple ((*it)->vertex (0), (*it)->vertex (1))); + + break; + } + } + } +} + +void MagicWandMode::doMagic (LDObjectPtr obj, MagicWandMode::MagicType type) +{ + if (obj == null) + { + if (type == Set) + { + CurrentDocument()->clearSelection(); + g_win->buildObjList(); + } + + return; + } + + int matchesneeded = 0; + QVector boundaries; + LDObjectType objtype = obj->type(); + + if (type != InternalRecursion) + { + m_selection.clear(); + m_selection.append (obj); + } + + switch (obj->type()) + { + case OBJ_Line: + case OBJ_CondLine: + matchesneeded = 1; + break; + + case OBJ_Triangle: + case OBJ_Quad: + matchesneeded = 2; + break; + + default: + return; + } + + QVector candidates; + + // Get the list of objects that touch this object, i.e. share a vertex + // with this. + for (int i = 0; i < obj->numVertices(); ++i) + candidates += m_vertices[obj->vertex (i)]; + + RemoveDuplicates (candidates); + + // If we're dealing with surfaces, get a list of boundaries. + if (matchesneeded > 1) + fillBoundaries (obj, boundaries, candidates); + + for (LDObjectPtr candidate : candidates) + { + try + { + // If we're doing this on lines, we need exact type match. Surface types (quads and + // triangles) can be mixed. Also don't consider self a candidate, and don't consider + // objects we have already processed. + if ((candidate == obj) or + (candidate->color() != obj->color()) or + (m_selection.contains (candidate)) or + (matchesneeded == 1 and (candidate->type() != objtype)) or + ((candidate->numVertices() > 2) ^ (matchesneeded == 2))) + { + throw 0; + } + + // Now ensure the two objects share enough vertices. + QVector matches; + + for (int i = 0; i < obj->numVertices(); ++i) + { + for (int j = 0; j < candidate->numVertices(); ++j) + { + if (obj->vertex(i) == candidate->vertex(j)) + { + matches << obj->vertex(i); + break; + } + } + } + + if (matches.size() < matchesneeded) + throw 0; // Not enough matches. + + // Check if a boundary gets in between the objects. + for (auto boundary : boundaries) + { + if (Eq (matches[0], std::get<0> (boundary), std::get<1> (boundary)) and + Eq (matches[1], std::get<0> (boundary), std::get<1> (boundary))) + { + throw 0; + } + } + + m_selection.append (candidate); + doMagic (candidate, InternalRecursion); + } + catch (int&) + { + continue; + } + } + + switch (type) + { + case Set: + CurrentDocument()->clearSelection(); + case Additive: + for (LDObjectPtr obj : m_selection) + obj->select(); + break; + + case Subtractive: + for (LDObjectPtr obj : m_selection) + obj->deselect(); + break; + + case InternalRecursion: + break; + } + + if (type != InternalRecursion) + g_win->buildObjList(); +} + +bool MagicWandMode::mouseReleased (MouseEventData const& data) +{ + if (Super::mouseReleased (data)) + return true; + + if (data.releasedButtons & Qt::LeftButton and not data.mouseMoved) + { + MagicType wandtype = MagicWandMode::Set; + + if (data.keymods & Qt::ShiftModifier) + wandtype = MagicWandMode::Additive; + elif (data.keymods & Qt::ControlModifier) + wandtype = MagicWandMode::Subtractive; + + doMagic (renderer()->pickOneObject (data.ev->x(), data.ev->y()), wandtype); + return true; + } + + return false; +} diff -r ab77deb851fa -r 8d98ee0dc917 src/editmodes/rectangleMode.cc --- a/src/editmodes/rectangleMode.cc Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +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 . - */ - -#include -#include -#include "rectangleMode.h" -#include "../ldObject.h" -#include "../glRenderer.h" - -RectangleMode::RectangleMode (GLRenderer* renderer) : - Super (renderer), - m_rectangleVerts (QVector(4)) {} - -EditModeType RectangleMode::type() const -{ - return EditModeType::Rectangle; -} - -void RectangleMode::render (QPainter& painter) const -{ - renderPolygon (painter, (m_drawedVerts.size() > 0) ? m_rectangleVerts : - QVector ({renderer()->position3D()}), true, false); -} - -bool RectangleMode::mouseReleased (MouseEventData const& data) -{ - if (Super::mouseReleased (data)) - return true; - - if (data.releasedButtons & Qt::LeftButton) - { - if (m_drawedVerts.size() == 2) - { - LDQuadPtr quad (LDSpawn()); - updateRectVerts(); - - for (int i = 0; i < quad->numVertices(); ++i) - quad->setVertex (i, m_rectangleVerts[i]); - - quad->setColor (MainColor()); - finishDraw (LDObjectList ({quad})); - return true; - } - - addDrawnVertex (renderer()->position3D()); - return true; - } - - return false; -} - -// -// Update rect vertices when the mouse moves since the 3d position likely has changed -// -bool RectangleMode::mouseMoved (QMouseEvent*) -{ - updateRectVerts(); - return false; -} - -void RectangleMode::updateRectVerts() -{ - if (m_drawedVerts.isEmpty()) - { - for (int i = 0; i < 4; ++i) - m_rectangleVerts[i] = renderer()->position3D(); - - return; - } - - Vertex v0 = m_drawedVerts[0], - v1 = (m_drawedVerts.size() >= 2) ? m_drawedVerts[1] : renderer()->position3D(); - - const Axis localx = renderer()->getCameraAxis (false), - localy = renderer()->getCameraAxis (true), - localz = (Axis) (3 - localx - localy); - - for (int i = 0; i < 4; ++i) - m_rectangleVerts[i].setCoordinate (localz, renderer()->getDepthValue()); - - m_rectangleVerts[0].setCoordinate (localx, v0[localx]); - m_rectangleVerts[0].setCoordinate (localy, v0[localy]); - m_rectangleVerts[1].setCoordinate (localx, v1[localx]); - m_rectangleVerts[1].setCoordinate (localy, v0[localy]); - m_rectangleVerts[2].setCoordinate (localx, v1[localx]); - m_rectangleVerts[2].setCoordinate (localy, v1[localy]); - m_rectangleVerts[3].setCoordinate (localx, v0[localx]); - m_rectangleVerts[3].setCoordinate (localy, v1[localy]); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/editmodes/rectangleMode.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editmodes/rectangleMode.cpp Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,104 @@ +/* + * 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 . + */ + +#include +#include +#include "rectangleMode.h" +#include "../ldObject.h" +#include "../glRenderer.h" + +RectangleMode::RectangleMode (GLRenderer* renderer) : + Super (renderer), + m_rectangleVerts (QVector(4)) {} + +EditModeType RectangleMode::type() const +{ + return EditModeType::Rectangle; +} + +void RectangleMode::render (QPainter& painter) const +{ + renderPolygon (painter, (m_drawedVerts.size() > 0) ? m_rectangleVerts : + QVector ({renderer()->position3D()}), true, false); +} + +bool RectangleMode::mouseReleased (MouseEventData const& data) +{ + if (Super::mouseReleased (data)) + return true; + + if (data.releasedButtons & Qt::LeftButton) + { + if (m_drawedVerts.size() == 2) + { + LDQuadPtr quad (LDSpawn()); + updateRectVerts(); + + for (int i = 0; i < quad->numVertices(); ++i) + quad->setVertex (i, m_rectangleVerts[i]); + + quad->setColor (MainColor()); + finishDraw (LDObjectList ({quad})); + return true; + } + + addDrawnVertex (renderer()->position3D()); + return true; + } + + return false; +} + +// +// Update rect vertices when the mouse moves since the 3d position likely has changed +// +bool RectangleMode::mouseMoved (QMouseEvent*) +{ + updateRectVerts(); + return false; +} + +void RectangleMode::updateRectVerts() +{ + if (m_drawedVerts.isEmpty()) + { + for (int i = 0; i < 4; ++i) + m_rectangleVerts[i] = renderer()->position3D(); + + return; + } + + Vertex v0 = m_drawedVerts[0], + v1 = (m_drawedVerts.size() >= 2) ? m_drawedVerts[1] : renderer()->position3D(); + + const Axis localx = renderer()->getCameraAxis (false), + localy = renderer()->getCameraAxis (true), + localz = (Axis) (3 - localx - localy); + + for (int i = 0; i < 4; ++i) + m_rectangleVerts[i].setCoordinate (localz, renderer()->getDepthValue()); + + m_rectangleVerts[0].setCoordinate (localx, v0[localx]); + m_rectangleVerts[0].setCoordinate (localy, v0[localy]); + m_rectangleVerts[1].setCoordinate (localx, v1[localx]); + m_rectangleVerts[1].setCoordinate (localy, v0[localy]); + m_rectangleVerts[2].setCoordinate (localx, v1[localx]); + m_rectangleVerts[2].setCoordinate (localy, v1[localy]); + m_rectangleVerts[3].setCoordinate (localx, v0[localx]); + m_rectangleVerts[3].setCoordinate (localy, v1[localy]); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/editmodes/selectMode.cc --- a/src/editmodes/selectMode.cc Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,137 +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 . - */ - -#include -#include "selectMode.h" -#include "../glRenderer.h" -#include "../addObjectDialog.h" -#include "../mainWindow.h" -#include "../glRenderer.h" - -SelectMode::SelectMode (GLRenderer* renderer) : - Super (renderer), - m_rangepick (false) {} - -EditModeType SelectMode::type() const -{ - return EditModeType::Select; -} - -void SelectMode::render (QPainter& painter) const -{ - // If we're range-picking, draw a rectangle encompassing the selection area. - if (m_rangepick) - { - int x0 = m_rangeStart.x(), - y0 = m_rangeStart.y(), - x1 = renderer()->mousePosition().x(), - y1 = renderer()->mousePosition().y(); - - QRect rect (x0, y0, x1 - x0, y1 - y0); - QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF"); - fillColor.setAlphaF (0.2f); - painter.setPen (QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - painter.setBrush (QBrush (fillColor)); - painter.drawRect (rect); - } -} - -bool SelectMode::mouseReleased (MouseEventData const& data) -{ - if (Super::mouseReleased (data)) - return true; - - if (data.releasedButtons & Qt::LeftButton) - { - if (not data.mouseMoved) - m_rangepick = false; - - if (not m_rangepick) - m_addpick = (data.keymods & Qt::ControlModifier); - - if (not data.mouseMoved or m_rangepick) - { - QRect area; - int const mx = data.ev->x(); - int const my = data.ev->y(); - - if (not m_rangepick) - { - area = QRect (mx, my, 1, 1); - } - else - { - int const x = Min (m_rangeStart.x(), mx); - int const y = Min (m_rangeStart.y(), my); - int const width = Abs (m_rangeStart.x() - mx); - int const height = Abs (m_rangeStart.y() - my); - area = QRect (x, y, width, height); - } - - renderer()->pick (area, m_addpick); - } - - m_rangepick = false; - return true; - } - - return false; -} - -bool SelectMode::mousePressed (QMouseEvent* ev) -{ - if (Super::mousePressed (ev)) - return true; - - if (ev->modifiers() & Qt::ControlModifier) - { - m_rangepick = true; - m_rangeStart.setX (ev->x()); - m_rangeStart.setY (ev->y()); - m_addpick = (ev->modifiers() & Qt::AltModifier); - return true; - } - - return false; -} - -bool SelectMode::mouseDoubleClicked (QMouseEvent* ev) -{ - if (Super::mouseDoubleClicked (ev)) - return true; - - if (ev->buttons() & Qt::LeftButton) - { - renderer()->document()->clearSelection(); - LDObjectPtr obj = renderer()->pickOneObject (ev->x(), ev->y()); - - if (obj != null) - { - AddObjectDialog::staticDialog (obj->type(), obj); - g_win->endAction(); - return true; - } - } - - return false; -} - -bool SelectMode::mouseMoved (QMouseEvent*) -{ - return m_rangepick; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/editmodes/selectMode.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editmodes/selectMode.cpp Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,137 @@ +/* + * 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 . + */ + +#include +#include "selectMode.h" +#include "../glRenderer.h" +#include "../addObjectDialog.h" +#include "../mainWindow.h" +#include "../glRenderer.h" + +SelectMode::SelectMode (GLRenderer* renderer) : + Super (renderer), + m_rangepick (false) {} + +EditModeType SelectMode::type() const +{ + return EditModeType::Select; +} + +void SelectMode::render (QPainter& painter) const +{ + // If we're range-picking, draw a rectangle encompassing the selection area. + if (m_rangepick) + { + int x0 = m_rangeStart.x(), + y0 = m_rangeStart.y(), + x1 = renderer()->mousePosition().x(), + y1 = renderer()->mousePosition().y(); + + QRect rect (x0, y0, x1 - x0, y1 - y0); + QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF"); + fillColor.setAlphaF (0.2f); + painter.setPen (QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + painter.setBrush (QBrush (fillColor)); + painter.drawRect (rect); + } +} + +bool SelectMode::mouseReleased (MouseEventData const& data) +{ + if (Super::mouseReleased (data)) + return true; + + if (data.releasedButtons & Qt::LeftButton) + { + if (not data.mouseMoved) + m_rangepick = false; + + if (not m_rangepick) + m_addpick = (data.keymods & Qt::ControlModifier); + + if (not data.mouseMoved or m_rangepick) + { + QRect area; + int const mx = data.ev->x(); + int const my = data.ev->y(); + + if (not m_rangepick) + { + area = QRect (mx, my, 1, 1); + } + else + { + int const x = Min (m_rangeStart.x(), mx); + int const y = Min (m_rangeStart.y(), my); + int const width = Abs (m_rangeStart.x() - mx); + int const height = Abs (m_rangeStart.y() - my); + area = QRect (x, y, width, height); + } + + renderer()->pick (area, m_addpick); + } + + m_rangepick = false; + return true; + } + + return false; +} + +bool SelectMode::mousePressed (QMouseEvent* ev) +{ + if (Super::mousePressed (ev)) + return true; + + if (ev->modifiers() & Qt::ControlModifier) + { + m_rangepick = true; + m_rangeStart.setX (ev->x()); + m_rangeStart.setY (ev->y()); + m_addpick = (ev->modifiers() & Qt::AltModifier); + return true; + } + + return false; +} + +bool SelectMode::mouseDoubleClicked (QMouseEvent* ev) +{ + if (Super::mouseDoubleClicked (ev)) + return true; + + if (ev->buttons() & Qt::LeftButton) + { + renderer()->document()->clearSelection(); + LDObjectPtr obj = renderer()->pickOneObject (ev->x(), ev->y()); + + if (obj != null) + { + AddObjectDialog::staticDialog (obj->type(), obj); + g_win->endAction(); + return true; + } + } + + return false; +} + +bool SelectMode::mouseMoved (QMouseEvent*) +{ + return m_rangepick; +} diff -r ab77deb851fa -r 8d98ee0dc917 src/editraw.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editraw.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,116 @@ + + + EditRawUI + + + + 0 + 0 + 400 + 87 + + + + Edit LDraw Code + + + + + + LDraw code: + + + + + + + <html><head/><body><p>The LDraw code of this object. The code written here is expected to be valid LDraw code, invalid code here results the object being turned into an error object. Please do refer to the <a href="http://www.ldraw.org/article/218.html"><span style=" text-decoration: underline; color:#0057ae;">official file format standard</span></a> for further information.</p></body></html> + + + + + + + + + + 16 + 16 + + + + + + + :/icons/error.png + + + true + + + + + + + true + + + color: #900 + + + Error description + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + buttonBox + accepted() + EditRawUI + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + EditRawUI + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/extPrograms.cc --- a/src/extPrograms.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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(); - 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 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, {}); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/extPrograms.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/extPrograms.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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(); + 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 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, {}); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/extprogpath.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/extprogpath.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,85 @@ + + + ExtProgPath + + + + 0 + 0 + 444 + 89 + + + + Program path required + + + + + + Please input a path for <PROGRAM>: + + + + + + + + + + + + ... + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ExtProgPath + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ExtProgPath + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/flip.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/flip.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,94 @@ + + + FlipUI + + + + 0 + 0 + 178 + 93 + + + + Flip + + + + + + Axes + + + + + + X + + + + + + + Y + + + + + + + Z + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + FlipUI + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + FlipUI + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/glCompiler.cc --- a/src/glCompiler.cc Tue Mar 03 16:50:39 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 . - */ - -#define GL_GLEXT_PROTOTYPES -#include -#include -#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 g_warnedColors; -static const QColor g_BFCFrontColor (64, 192, 80); -static const QColor g_BFCBackColor (208, 64, 64); - -// static QMap 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 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(); - 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& 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; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/glCompiler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/glCompiler.cpp Tue Mar 03 22:29:27 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 . + */ + +#define GL_GLEXT_PROTOTYPES +#include +#include +#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 g_warnedColors; +static const QColor g_BFCFrontColor (64, 192, 80); +static const QColor g_BFCBackColor (208, 64, 64); + +// static QMap 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 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(); + 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& 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; +} diff -r ab77deb851fa -r 8d98ee0dc917 src/glRenderer.cc --- a/src/glRenderer.cc Tue Mar 03 16:50:39 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 . - */ - -#define GL_GLEXT_PROTOTYPES -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 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 (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 GetVerticesOf (LDObjectPtr obj) -{ - QList 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(); - 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 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(); - - if (ovlobj != null and obj.staticCast()->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(); - - // 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()); - } - } - - 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 (g_win->getPrimitivesTree()->currentItem())->primitive()->name; - LDSubfilePtr ref = LDSpawn(); - 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]; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/glRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/glRenderer.cpp Tue Mar 03 22:29:27 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 . + */ + +#define GL_GLEXT_PROTOTYPES +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 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 (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 GetVerticesOf (LDObjectPtr obj) +{ + QList 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(); + 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 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(); + + if (ovlobj != null and obj.staticCast()->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(); + + // 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()); + } + } + + 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 (g_win->getPrimitivesTree()->currentItem())->primitive()->name; + LDSubfilePtr ref = LDSpawn(); + 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]; +} diff -r ab77deb851fa -r 8d98ee0dc917 src/intersector.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/intersector.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,175 @@ + + + IntersectorUI + + + + 0 + 0 + 250 + 200 + + + + + 250 + 200 + + + + + 250 + 200 + + + + Intersector + + + + + 10 + 160 + 231 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 10 + 10 + 233 + 143 + + + + + + + + + Cutter: + + + + + + + Input: + + + + + + + + + + + + + + + + + Colorize output + + + + + + + Repeat inverse + + + + + + + No condensing + + + + + + + Add edges + + + + + + + + + + + Prescaling factor + + + + + + + + + + 10000.000000000000000 + + + 0.010000000000000 + + + 1.000000000000000 + + + + + + + + + + + + buttonBox + accepted() + IntersectorUI + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + IntersectorUI + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/isecalc.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/isecalc.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,112 @@ + + + IsecalcUI + + + + 0 + 0 + 240 + 120 + + + + + 240 + 120 + + + + + 10000 + 120 + + + + Isecalc + + + + + 30 + 80 + 201 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 10 + 10 + 221 + 61 + + + + + + + + + + + + + Shape 1: + + + + + + + Shape 2: + + + + + + + + + + buttonBox + accepted() + IsecalcUI + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + IsecalcUI + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/ldConfig.cc --- a/src/ldConfig.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#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); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/ldConfig.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldConfig.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#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); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/ldDocument.cc --- a/src/ldDocument.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include -#include - -#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 g_allDocuments; -static QList 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
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 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 ({ cfg::LDrawPath, cfg::DownloadFilePath })) - { - for (const QString& subdir : QList ({ "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()->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()->fileReferenced().isEmpty()) - continue; - - unknowns << obj.staticCast()->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(); - - 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(); - } - - 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 (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 (BFCStatement::InvertNext); - elif (commentTextSimplified == "BFC CERTIFY CLIP") - return LDSpawn (BFCStatement::Clip); - elif (commentTextSimplified == "BFC CERTIFY NOCLIP") - return LDSpawn (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(); - 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(); - 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 (line, format ("Could not open %1", tokens[14])); - obj->setFileReferenced (tokens[14]); - return obj; - } - - LDSubfilePtr obj = LDSpawn(); - 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()); - 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()); - 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(); - else - obj = LDSpawn(); - - 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 (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(); - LDDocumentPtr fileInfo = GetDocument (ref->fileInfo()->name()); - - if (fileInfo != null) - { - ref->setFileInfo (fileInfo); - } - else - { - ref->replace (LDSpawn (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()->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()); - 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()->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 const& verts : m_objectVertices) - m_vertices << verts; - - RemoveDuplicates (m_vertices); - m_needVertexMerge = false; -} - -// ============================================================================= -// -QList 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()->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 const& LDDocument::inlineVertices() -{ - initializeCachedData(); - return m_vertices; -} - -void LDDocument::redoVertices() -{ - m_verticesOutdated = true; -} - -void LDDocument::needVertexMerge() -{ - m_needVertexMerge = true; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/ldDocument.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldDocument.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#include +#include + +#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 g_allDocuments; +static QList 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
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 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 ({ cfg::LDrawPath, cfg::DownloadFilePath })) + { + for (const QString& subdir : QList ({ "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()->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()->fileReferenced().isEmpty()) + continue; + + unknowns << obj.staticCast()->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(); + + 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(); + } + + 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 (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 (BFCStatement::InvertNext); + elif (commentTextSimplified == "BFC CERTIFY CLIP") + return LDSpawn (BFCStatement::Clip); + elif (commentTextSimplified == "BFC CERTIFY NOCLIP") + return LDSpawn (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(); + 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(); + 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 (line, format ("Could not open %1", tokens[14])); + obj->setFileReferenced (tokens[14]); + return obj; + } + + LDSubfilePtr obj = LDSpawn(); + 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()); + 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()); + 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(); + else + obj = LDSpawn(); + + 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 (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(); + LDDocumentPtr fileInfo = GetDocument (ref->fileInfo()->name()); + + if (fileInfo != null) + { + ref->setFileInfo (fileInfo); + } + else + { + ref->replace (LDSpawn (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()->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()); + 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()->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 const& verts : m_objectVertices) + m_vertices << verts; + + RemoveDuplicates (m_vertices); + m_needVertexMerge = false; +} + +// ============================================================================= +// +QList 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()->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 const& LDDocument::inlineVertices() +{ + initializeCachedData(); + return m_vertices; +} + +void LDDocument::redoVertices() +{ + m_verticesOutdated = true; +} + +void LDDocument::needVertexMerge() +{ + m_needVertexMerge = true; +} diff -r ab77deb851fa -r 8d98ee0dc917 src/ldObject.cc --- a/src/ldObject.cc Tue Mar 03 16:50:39 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 . - */ - - -#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 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 LDQuad::splitToTriangles() -{ - // Create the two triangles based on this quadrilateral: - // 0---3 0---3 3 - // | | | / /| - // | | ==> | / / | - // | | |/ / | - // 1---2 1 1---2 - LDTrianglePtr tri1 (LDSpawn (vertex (0), vertex (1), vertex (3))); - LDTrianglePtr tri2 (LDSpawn (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 (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 LDSubfile::inlinePolygons() -{ - QList 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()->statement() == BFCStatement::InvertNext) - { - ptr = prev.staticCast(); - return true; - } - - return false; -} - -// ============================================================================= -// -void LDObject::move (Vertex vect) -{ - if (hasMatrix()) - { - LDMatrixObjectPtr mo = self().toStrongRef().dynamicCast(); - 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(); - case OBJ_BFC: return LDSpawn(); - case OBJ_Line: return LDSpawn(); - case OBJ_CondLine: return LDSpawn(); - case OBJ_Subfile: return LDSpawn(); - case OBJ_Triangle: return LDSpawn(); - case OBJ_Quad: return LDSpawn(); - case OBJ_Empty: return LDSpawn(); - case OBJ_Error: return LDSpawn(); - case OBJ_Overlay: return LDSpawn(); - 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(); - - 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 (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()); - - 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 -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& verts) const -{ - for (int i = 0; i < numVertices(); ++i) - verts << vertex (i); -} - -void LDSubfile::getVertices (QVector& verts) const -{ - verts << fileInfo()->inlineVertices(); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/ldObject.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldObject.cpp Tue Mar 03 22:29:27 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 . + */ + + +#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 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 LDQuad::splitToTriangles() +{ + // Create the two triangles based on this quadrilateral: + // 0---3 0---3 3 + // | | | / /| + // | | ==> | / / | + // | | |/ / | + // 1---2 1 1---2 + LDTrianglePtr tri1 (LDSpawn (vertex (0), vertex (1), vertex (3))); + LDTrianglePtr tri2 (LDSpawn (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 (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 LDSubfile::inlinePolygons() +{ + QList 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()->statement() == BFCStatement::InvertNext) + { + ptr = prev.staticCast(); + return true; + } + + return false; +} + +// ============================================================================= +// +void LDObject::move (Vertex vect) +{ + if (hasMatrix()) + { + LDMatrixObjectPtr mo = self().toStrongRef().dynamicCast(); + 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(); + case OBJ_BFC: return LDSpawn(); + case OBJ_Line: return LDSpawn(); + case OBJ_CondLine: return LDSpawn(); + case OBJ_Subfile: return LDSpawn(); + case OBJ_Triangle: return LDSpawn(); + case OBJ_Quad: return LDSpawn(); + case OBJ_Empty: return LDSpawn(); + case OBJ_Error: return LDSpawn(); + case OBJ_Overlay: return LDSpawn(); + 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(); + + 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 (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()); + + 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 +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& verts) const +{ + for (int i = 0; i < numVertices(); ++i) + verts << vertex (i); +} + +void LDSubfile::getVertices (QVector& verts) const +{ + verts << fileInfo()->inlineVertices(); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/ldforge.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldforge.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,1702 @@ + + + LDForgeUI + + + + 0 + 0 + 1008 + 641 + + + + + + + + :/icons/ldforge.png:/icons/ldforge.png + + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + + + + + 0 + 0 + 234 + 428 + + + + Document + + + + + + + 0 + 0 + + + + QAbstractItemView::ExtendedSelection + + + + + + + + + 0 + 0 + 234 + 428 + + + + Tool Options + + + + + + Circle Tool Options + + + + + + + + High resolution + + + + + + + Segments: + + + + + + + + + true + + + 1 + + + 16 + + + 16 + + + + + + + a / b + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + 0 + 234 + 428 + + + + Primitives + + + + + + QAbstractItemView::DragOnly + + + false + + + + 1 + + + + + + + + + + + + + + + + 0 + 0 + 1008 + 21 + + + + + File + + + + Open Recent... + + + + :/icons/open-recent.png:/icons/open-recent.png + + + + + + + + + + + + + + + + + + + + + + + + + + + View + + + + + + + + + + + + + + + + + + + + Insert + + + + + + + + + + + + + + Edit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tools + + + + + + + + + + + + + + + + + + + + + + + + + + + + + External Tools + + + + + + + + + + + Help + + + + + + + + + Move + + + + Grids + + + + + + + + Move Objects + + + + + + + + + + + Object List + + + + + + + Rotate + + + + + + + + + + + + + + + + + + + + + + + + + + + + File + + + TopToolBarArea + + + false + + + + + + + + + + New Object + + + TopToolBarArea + + + false + + + + + + + + + + + + Basic tools + + + TopToolBarArea + + + false + + + + + + + + + + + Select + + + TopToolBarArea + + + false + + + + + + + + Grid + + + TopToolBarArea + + + false + + + + + + + + Display options + + + TopToolBarArea + + + true + + + + + + + + + + Editing tools + + + TopToolBarArea + + + false + + + + + + + + + + + + + + + + + Editing modes + + + LeftToolBarArea + + + false + + + + + + + + + + + Colors + + + RightToolBarArea + + + false + + + + + External Programs + + + TopToolBarArea + + + false + + + + + + + + + + + + :/icons/brick.png:/icons/brick.png + + + New Part + + + Create a new part model. + + + Ctrl+N + + + + + + :/icons/file-open.png:/icons/file-open.png + + + Open + + + Load a part model from a file. + + + Ctrl+O + + + + + + :/icons/file-save.png:/icons/file-save.png + + + Save + + + Save the part model. + + + + + + Ctrl+S + + + + + + :/icons/file-save-as.png:/icons/file-save-as.png + + + Save As.. + + + Save the part model to a specific file. + + + Ctrl+Shift+S + + + + + + :/icons/file-import.png:/icons/file-import.png + + + Insert From.. + + + + + + :/icons/file-export.png:/icons/file-export.png + + + Export To.. + + + + + + :/icons/settings.png:/icons/settings.png + + + Settings + + + Edit the settings of LDForge. + + + + + + + + + :/icons/settings.png:/icons/settings.png + + + Set LDraw Path + + + Change the LDraw directory path. + + + + + + :/icons/radial.png:/icons/radial.png + + + Scan Primitives + + + Scan the primitives folder for primitive info. Use this if you add new primitives. + + + + + + :/icons/exit.png:/icons/exit.png + + + Exit + + + Ctrl+Q + + + + + Reset View + + + + + true + + + true + + + + :/icons/axes.png:/icons/axes.png + + + Draw Axes + + + + + true + + + + :/icons/wireframe.png:/icons/wireframe.png + + + Wireframe + + + + + true + + + + :/icons/bfc-view.png:/icons/bfc-view.png + + + BFC Red/Green View + + + Shift+B + + + + + + :/icons/overlay.png:/icons/overlay.png + + + Set Overlay Image + + + + + + :/icons/overlay-clear.png:/icons/overlay-clear.png + + + Clear Overlay Image + + + + + + :/icons/screencap.png:/icons/screencap.png + + + Screenshot + + + + + LDraw Code.. + + + + + + :/icons/add-line.png:/icons/add-line.png + + + New Line + + + + + + :/icons/add-subfile.png:/icons/add-subfile.png + + + New Subfile Reference + + + + + + :/icons/add-triangle.png:/icons/add-triangle.png + + + New Triangle + + + + + + :/icons/add-quad.png:/icons/add-quad.png + + + New Quadrilateral + + + + + + :/icons/add-condline.png:/icons/add-condline.png + + + New Conditional Line + + + + + + :/icons/add-comment.png:/icons/add-comment.png + + + New Comment + + + + + + :/icons/add-bfc.png:/icons/add-bfc.png + + + New BFC Statement + + + + + + :/icons/undo.png:/icons/undo.png + + + Undo + + + Undo a step. + + + Ctrl+Z + + + + + + :/icons/redo.png:/icons/redo.png + + + Redo + + + Redo a step. + + + Ctrl+Shift+Z + + + + + + :/icons/cut.png:/icons/cut.png + + + Cut + + + Cut the current selection to clipboard. + + + Ctrl+X + + + + + + :/icons/copy.png:/icons/copy.png + + + Copy + + + Copy the current selection to clipboard. + + + + + + Ctrl+C + + + + + + :/icons/paste.png:/icons/paste.png + + + Paste + + + Paste clipboard contents. + + + Ctrl+V + + + + + + :/icons/delete.png:/icons/delete.png + + + Delete + + + Delete the selection + + + Del + + + + + + :/icons/select-all.png:/icons/select-all.png + + + Select All + + + Ctrl+A + + + + + + :/icons/select-color.png:/icons/select-color.png + + + Select by Color + + + + + + :/icons/select-type.png:/icons/select-type.png + + + Select by Type + + + + + true + + + true + + + + :/icons/mode-select.png:/icons/mode-select.png + + + Select Mode + + + S + + + + + true + + + + :/icons/mode-draw.png:/icons/mode-draw.png + + + Draw Mode + + + D + + + + + Set Draw Depth + + + + + + :/icons/palette.png:/icons/palette.png + + + Set Color + + + Set the color on given objects. + + + Shift+C + + + + + + :/icons/autocolor.png:/icons/autocolor.png + + + Auto-color + + + Set the color of the given object to the first found unused color. + + + Ctrl+Shift+C + + + + + + :/icons/uncolorize.png:/icons/uncolorize.png + + + Uncolor + + + Uncolor + + + Reduce colors of everything selected to main and edge colors + + + + + + :/icons/inline.png:/icons/inline.png + + + Inline + + + Inline selected subfiles. + + + Ctrl+I + + + + + + :/icons/inline-deep.png:/icons/inline-deep.png + + + Deep Inline + + + Recursively inline selected subfiles down to polygons only. + + + Ctrl+Shift+I + + + + + + :/icons/invert.png:/icons/invert.png + + + Invert + + + Ctrl+Shift+W + + + + + + :/icons/radial.png:/icons/radial.png + + + Generate Primitive + + + + + + :/icons/quad-split.png:/icons/quad-split.png + + + Split Quads + + + Split quads into triangles. + + + + + + :/icons/set-contents.png:/icons/set-contents.png + + + Edit LDraw Code + + + Edit the LDraw code of this object. + + + + + + :/icons/make-borders.png:/icons/make-borders.png + + + Make Borders + + + Add borders around given polygons. + + + + + + :/icons/round-coords.png:/icons/round-coords.png + + + Round Coordinates + + + Round coordinates down to 3/4 decimals + + + + + + :/icons/visibility-toggle.png:/icons/visibility-toggle.png + + + Toggle Visibility + + + Toggles visibility/hiding on objects. + + + + + + :/icons/replace-coords.png:/icons/replace-coords.png + + + Replace Coordinates + + + Find and replace coordinate values. + + + + + + :/icons/flip.png:/icons/flip.png + + + Flip + + + Flip coordinates. + + + Ctrl+Shift+F + + + + + Demote Conditional Lines + + + Demote conditional lines down to normal lines. + + + + + + :/icons/ytruder.png:/icons/ytruder.png + + + Ytruder + + + Extrude selected lines to a given plane + + + + + + :/icons/rectifier.png:/icons/rectifier.png + + + Rectifier + + + Optimizes quads into rect primitives. + + + + + + :/icons/intersector.png:/icons/intersector.png + + + Intersector + + + Perform clipping between two input groups. + + + + + + :/icons/isecalc.png:/icons/isecalc.png + + + Isecalc + + + Compute intersection edgelines between two input groups. + + + + + + :/icons/coverer.png:/icons/coverer.png + + + Coverer + + + Fill the space between two line shapes + + + + + Edger 2 + + + + + false + + + + :/icons/help.png:/icons/help.png + + + Help + + + F1 + + + + + + :/icons/ldforge.png:/icons/ldforge.png + + + About LDForge + + + + + About Qt + + + + + true + + + + :/icons/grid-coarse.png:/icons/grid-coarse.png + + + Coarse Grid + + + + + true + + + true + + + + :/icons/grid-medium.png:/icons/grid-medium.png + + + Medium Grid + + + + + true + + + + :/icons/grid-fine.png:/icons/grid-fine.png + + + Fine Grid + + + + + Edit Selected Object + + + + + + :/icons/arrow-up.png:/icons/arrow-up.png + + + Move Up + + + PgUp + + + + + + :/icons/arrow-down.png:/icons/arrow-down.png + + + Move Down + + + PgDown + + + + + + :/icons/move-x-neg.png:/icons/move-x-neg.png + + + Move -X + + + Left + + + + + + :/icons/move-x-pos.png:/icons/move-x-pos.png + + + Move +X + + + Right + + + + + + :/icons/move-y-neg.png:/icons/move-y-neg.png + + + Move -Y + + + Home + + + + + + :/icons/move-y-pos.png:/icons/move-y-pos.png + + + Move +Y + + + End + + + + + + :/icons/move-z-neg.png:/icons/move-z-neg.png + + + Move -Z + + + Down + + + + + + :/icons/move-z-pos.png:/icons/move-z-pos.png + + + Move +Z + + + Up + + + + + Rotate -X + + + Ctrl+Left + + + + + Rotate +X + + + Ctrl+Right + + + + + Rotate -Y + + + Ctrl+End + + + + + Rotate +Y + + + Ctrl+Home + + + + + Rotate -Z + + + Ctrl+Down + + + + + Rotate +Z + + + Ctrl+Up + + + + + Set Rotation Point + + + + + Save All + + + + + Close + + + Ctrl+W + + + + + Close All + + + Ctrl+Shift+W + + + + + + :/icons/file-new.png:/icons/file-new.png + + + New File + + + Ctrl+N + + + + + Download From... + + + + + Add History Line + + + + + Go to Line... + + + Ctrl+G + + + + + true + + + + :/icons/mode-circle.png:/icons/mode-circle.png + + + Circle Mode + + + C + + + + + + :/icons/visibility-hide.png:/icons/visibility-hide.png + + + Hide + + + Hides objects from view + + + + + + :/icons/visibility-show.png:/icons/visibility-show.png + + + Reveal + + + Reveals objects. Undoes hiding. + + + + + Subfile Selection + + + + + true + + + + :/icons/mode-angle.png:/icons/mode-angle.png + + + Draw Angles + + + Draw angle information when drawing lines + + + + + true + + + + :/icons/random-colors.png:/icons/random-colors.png + + + Random colors + + + Shift+R + + + + + Open Subfiles for Editing + + + Opens the documents used by the selected subparts for editing. + + + + + Split Lines... + + + + + true + + + Draw surfaces + + + Render surfaces (i.e. quads and triangles) on the viewport. + + + + + true + + + Draw edgelines + + + Render edgelines on the viewport + + + + + true + + + Draw conditional lines + + + Render conditional lines on the viewport. + + + + + true + + + + :/icons/mode-magicwand.png:/icons/mode-magicwand.png + + + Magic wand + + + W + + + + + true + + + + :/icons/mode-rectangle.png:/icons/mode-rectangle.png + + + Rectangle Mode + + + R + + + + + true + + + + :/icons/line.png:/icons/line.png + + + Line Path Mode + + + P + + + + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/ldrawpath.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldrawpath.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,88 @@ + + + LDPathUI + + + + 0 + 0 + 344 + 123 + + + + Set LDraw Path + + + + + + Please input your LDraw directory root to proceed: + + + + + + + + + LDraw Path: + + + + + + + + + + + + + + :/icons/folder.png:/icons/folder.png + + + + + + + + + font-weight: bold + + + [[ Information ]] + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/main.cc --- a/src/main.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include -#include -#include -#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); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#include +#include +#include +#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); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/mainWindow.cc --- a/src/mainWindow.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "main.h" -#include "glRenderer.h" -#include "mainWindow.h" -#include "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 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 (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 LoadQuickColorList() -{ - QList 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(); - 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()->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(); - - 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()->statement())]; - break; - } - - case OBJ_Overlay: - { - LDOverlayPtr ovl = obj.staticCast(); - 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 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 (sender()); - OpenMainModel (qAct->text()); -} - -// ============================================================================= -// -void MainWindow::slot_quickColor() -{ - QToolButton* button = static_cast (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()) - { - 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 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()) - { - QKeySequence seq = settings->value ("shortcut_" + act->objectName(), act->shortcut()).value(); - 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 function) -{ - for (QAction* act : findChildren()) - { - 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 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); - } -} diff -r ab77deb851fa -r 8d98ee0dc917 src/mainWindow.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mainWindow.cpp Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,1188 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "main.h" +#include "glRenderer.h" +#include "mainWindow.h" +#include "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 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 (toolbarname.toString()); + + if (toolbar != null) + toolbar->hide(); + } +} + +MainWindow::~MainWindow() +{ + g_win = null; +} + +// ============================================================================= +// +void MainWindow::actionTriggered() +{ + QMetaObject::invokeMethod (this, qPrintable (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 LoadQuickColorList() +{ + QList 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(); + 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()->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(); + + 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()->statement())]; + break; + } + + case OBJ_Overlay: + { + LDOverlayPtr ovl = obj.staticCast(); + 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 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 (sender()); + OpenMainModel (qAct->text()); +} + +// ============================================================================= +// +void MainWindow::slot_quickColor() +{ + QToolButton* button = static_cast (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()) + { + 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 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()) + { + QKeySequence seq = settings->value ("shortcut_" + act->objectName(), act->shortcut()).value(); + 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 function) +{ + for (QAction* act : findChildren()) + { + 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 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); + } +} diff -r ab77deb851fa -r 8d98ee0dc917 src/makeprim.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/makeprim.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,309 @@ + + + MakePrimUI + + + + 0 + 0 + 336 + 147 + + + + Generate a Primitive + + + + + + + + + + + 0 + 0 + + + + Type + + + + + + Circle + + + true + + + false + + + + + + + Cylinder + + + + + + + Disc + + + + + + + Disc Negative + + + + + + + Ring + + + + + + + Cone + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Hi-res + + + + + + + + + Segments: + + + + + + + Ring number: + + + + + + + 1 + + + 16 + + + 16 + + + + + + + false + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + MakePrimUI + accept() + + + 254 + 140 + + + 157 + 146 + + + + + buttonBox + rejected() + MakePrimUI + reject() + + + 322 + 140 + + + 286 + 146 + + + + + rb_circle + clicked(bool) + sb_ringnum + setDisabled(bool) + + + 45 + 41 + + + 305 + 86 + + + + + rb_cylinder + clicked(bool) + sb_ringnum + setDisabled(bool) + + + 109 + 42 + + + 287 + 86 + + + + + rb_disc + clicked(bool) + sb_ringnum + setDisabled(bool) + + + 49 + 66 + + + 287 + 87 + + + + + rb_ndisc + clicked(bool) + sb_ringnum + setDisabled(bool) + + + 121 + 58 + + + 288 + 83 + + + + + rb_ring + clicked(bool) + sb_ringnum + setEnabled(bool) + + + 45 + 90 + + + 301 + 86 + + + + + rb_cone + clicked(bool) + sb_ringnum + setEnabled(bool) + + + 111 + 89 + + + 302 + 85 + + + + + + enableRingNumber() + + diff -r ab77deb851fa -r 8d98ee0dc917 src/messageLog.cc --- a/src/messageLog.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#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::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); - } -} diff -r ab77deb851fa -r 8d98ee0dc917 src/messageLog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/messageLog.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#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::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); + } +} diff -r ab77deb851fa -r 8d98ee0dc917 src/miscallenous.cc --- a/src/miscallenous.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#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 (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()->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 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"; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/miscallenous.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/miscallenous.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#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 (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()->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 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"; +} diff -r ab77deb851fa -r 8d98ee0dc917 src/newpart.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/newpart.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,191 @@ + + + NewPartUI + + + + 0 + 0 + 491 + 233 + + + + New Part + + + + + + + + + + + + + :/icons/brick.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + + + + + + + + Author: + + + + + + + Title: + + + + + + + + + + + + + BFC winding + + + + + + CCW + + + true + + + + + + + CW + + + + + + + None + + + + + + + + + + + + Use CA license + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + buttonBox + accepted() + NewPartUI + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NewPartUI + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/openprogress.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/openprogress.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,78 @@ + + + OpenProgressUI + + + + 0 + 0 + 400 + 89 + + + + Opening + + + + + + [[ Progress text ]] + + + + + + + 24 + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel + + + + + + + + + buttonBox + accepted() + OpenProgressUI + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + OpenProgressUI + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/overlay.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/overlay.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,278 @@ + + + OverlayUI + + + + 0 + 0 + 276 + 244 + + + + Set Overlay + + + + :/icons/overlay.png:/icons/overlay.png + + + + + + Camera + + + + + + Top + + + + :/icons/camera-top.png:/icons/camera-top.png + + + + + + + Front + + + + :/icons/camera-front.png:/icons/camera-front.png + + + + + + + Left + + + + :/icons/camera-left.png:/icons/camera-left.png + + + + + + + Bottom + + + + :/icons/camera-bottom.png:/icons/camera-bottom.png + + + + + + + Back + + + + :/icons/camera-back.png:/icons/camera-back.png + + + + + + + Right + + + + :/icons/camera-right.png:/icons/camera-right.png + + + + + + + + + + Image + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + File: + + + + + + + + + + + + + + + + :/icons/file-open.png:/icons/file-open.png + + + + + + + + + Origin: + + + + + + + + + + 80 + 0 + + + + px + + + + + + 10000 + + + + + + + + 80 + 0 + + + + px + + + 10000 + + + + + + + + + Dimensions: + + + + + + + + + + 80 + 0 + + + + LDU + + + 10000.000000000000000 + + + + + + + + 80 + 0 + + + + LDU + + + 0.000000000000000 + + + 10000.000000000000000 + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + OverlayUI + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + OverlayUI + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/partDownloader.cc --- a/src/partDownloader.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#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 (form()->buttonBox->button (QDialogButtonBox::Abort)); - - case Close: - return qobject_cast (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 (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) - ? "FINISHED" - : "FAILED"; - - QLabel* lb = new QLabel (text); - lb->setAlignment (Qt::AlignCenter); - table->setCellWidget (tableRow(), progcol, lb); - } break; - } - - QLabel* lb = qobject_cast (table->cellWidget (tableRow(), labelcol)); - - if (isFirstUpdate()) - { - lb = new QLabel (format ("%1", 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(); - - 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(); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/partDownloader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/partDownloader.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#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 (form()->buttonBox->button (QDialogButtonBox::Abort)); + + case Close: + return qobject_cast (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 (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) + ? "FINISHED" + : "FAILED"; + + QLabel* lb = new QLabel (text); + lb->setAlignment (Qt::AlignCenter); + table->setCellWidget (tableRow(), progcol, lb); + } break; + } + + QLabel* lb = qobject_cast (table->cellWidget (tableRow(), labelcol)); + + if (isFirstUpdate()) + { + lb = new QLabel (format ("%1", 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(); + + 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(); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/primitives.cc --- a/src/primitives.cc Tue Mar 03 16:50:39 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 . - */ - -#include -#include -#include -#include "ldDocument.h" -#include "mainWindow.h" -#include "primitives.h" -#include "ui_makeprim.h" -#include "miscallenous.h" -#include "colors.h" - -QList g_PrimitiveCategories; -QList 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& 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& 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 condLineSegs; - QList circle; - - MakeCircle (segs, divs, 1, circle); - - for (int i = 0; i < segs; ++i) - { - double x0 = circle[i].x1(), - x1 = circle[i].x2(), - z0 = circle[i].y1(), - z1 = circle[i].y2(); - - switch (type) - { - case Circle: - { - Vertex v0 (x0, 0.0f, z0), - v1 (x1, 0.0f, z1); - - LDLinePtr line (LDSpawn()); - 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 (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()); - 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()); - 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 (descr) - << LDSpawn (format ("Name: %1", name)) - << LDSpawn (format ("Author: %1", author)) - << LDSpawn (format ("!LDRAW_ORG Unofficial_%1Primitive", - divs == HighResolution ? "48_" : "")) - << LDSpawn (license) - << LDSpawn() - << LDSpawn (BFCStatement::CertifyCCW) - << LDSpawn(); - - 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); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/primitives.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/primitives.cpp Tue Mar 03 22:29:27 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 . + */ + +#include +#include +#include +#include "ldDocument.h" +#include "mainWindow.h" +#include "primitives.h" +#include "ui_makeprim.h" +#include "miscallenous.h" +#include "colors.h" + +QList g_PrimitiveCategories; +QList 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& 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& 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 condLineSegs; + QList circle; + + MakeCircle (segs, divs, 1, circle); + + for (int i = 0; i < segs; ++i) + { + double x0 = circle[i].x1(), + x1 = circle[i].x2(), + z0 = circle[i].y1(), + z1 = circle[i].y2(); + + switch (type) + { + case Circle: + { + Vertex v0 (x0, 0.0f, z0), + v1 (x1, 0.0f, z1); + + LDLinePtr line (LDSpawn()); + 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 (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()); + 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()); + 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 (descr) + << LDSpawn (format ("Name: %1", name)) + << LDSpawn (format ("Author: %1", author)) + << LDSpawn (format ("!LDRAW_ORG Unofficial_%1Primitive", + divs == HighResolution ? "48_" : "")) + << LDSpawn (license) + << LDSpawn() + << LDSpawn (BFCStatement::CertifyCCW) + << LDSpawn(); + + 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); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/radioGroup.cc --- a/src/radioGroup.cc Tue Mar 03 16:50:39 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 . - */ - -// I still find the radio group useful... find a way to use this in Designer. -// I probably need to look into how to make Designer plugins. -// TODO: try make this usable in Designer - -#include -#include -#include -#include -#include - -#include "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 entries, int const defaultId, const Qt::Orientation orient, QWidget* parent) : - QGroupBox (title, parent), - m_defId (defaultId) -{ - init (orient); - m_oldId = m_defId; - - for (const char* entry : entries) - addButton (entry); -} - -// ============================================================================= -// -void RadioGroup::rowBreak() -{ - QBoxLayout* newLayout = new QBoxLayout (m_vert ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); - m_currentLayout = newLayout; - m_layouts << newLayout; - - m_coreLayout->addLayout (newLayout); -} - -// ============================================================================= -// -void RadioGroup::addButton (const char* entry) -{ - QRadioButton* button = new QRadioButton (entry); - addButton (button); -} - -// ============================================================================= -// -void RadioGroup::addButton (QRadioButton* button) -{ - bool const selectThis = (m_curId == m_defId); - - m_objects << button; - m_buttonGroup->addButton (button, m_curId++); - m_currentLayout->addWidget (button); - - if (selectThis) - button->setChecked (true); -} - -// ============================================================================= -// -RadioGroup& RadioGroup::operator<< (QRadioButton* button) -{ - addButton (button); - return *this; -} - -// ============================================================================= -// -RadioGroup& RadioGroup::operator<< (const char* entry) -{ - addButton (entry); - return *this; -} - -// ============================================================================= -// -void RadioGroup::setCurrentRow (int row) -{ - m_currentLayout = m_layouts[row]; -} - -// ============================================================================= -// -int RadioGroup::value() const -{ - return m_buttonGroup->checkedId(); -} - -// ============================================================================= -// -void RadioGroup::setValue (int val) -{ - m_buttonGroup->button (val)->setChecked (true); -} - -// ============================================================================= -// -QRadioButton* RadioGroup::operator[] (int n) const -{ - return m_objects[n]; -} - -// ============================================================================= -// -void RadioGroup::slot_buttonPressed (int btn) -{ - emit buttonPressed (btn); - - m_oldId = m_buttonGroup->checkedId(); -} - -// ============================================================================= -// -void RadioGroup::slot_buttonReleased (int btn) -{ - emit buttonReleased (btn); - int newid = m_buttonGroup->checkedId(); - - if (m_oldId != newid) - emit valueChanged (newid); -} - -// ============================================================================= -// -RadioGroup::Iterator RadioGroup::begin() -{ - return m_objects.begin(); -} - -// ============================================================================= -// -RadioGroup::Iterator RadioGroup::end() -{ - return m_objects.end(); -} diff -r ab77deb851fa -r 8d98ee0dc917 src/radioGroup.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/radioGroup.cpp Tue Mar 03 22:29:27 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 . + */ + +// I still find the radio group useful... find a way to use this in Designer. +// I probably need to look into how to make Designer plugins. +// TODO: try make this usable in Designer + +#include +#include +#include +#include +#include + +#include "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 entries, int const defaultId, const Qt::Orientation orient, QWidget* parent) : + QGroupBox (title, parent), + m_defId (defaultId) +{ + init (orient); + m_oldId = m_defId; + + for (const char* entry : entries) + addButton (entry); +} + +// ============================================================================= +// +void RadioGroup::rowBreak() +{ + QBoxLayout* newLayout = new QBoxLayout (m_vert ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); + m_currentLayout = newLayout; + m_layouts << newLayout; + + m_coreLayout->addLayout (newLayout); +} + +// ============================================================================= +// +void RadioGroup::addButton (const char* entry) +{ + QRadioButton* button = new QRadioButton (entry); + addButton (button); +} + +// ============================================================================= +// +void RadioGroup::addButton (QRadioButton* button) +{ + bool const selectThis = (m_curId == m_defId); + + m_objects << button; + m_buttonGroup->addButton (button, m_curId++); + m_currentLayout->addWidget (button); + + if (selectThis) + button->setChecked (true); +} + +// ============================================================================= +// +RadioGroup& RadioGroup::operator<< (QRadioButton* button) +{ + addButton (button); + return *this; +} + +// ============================================================================= +// +RadioGroup& RadioGroup::operator<< (const char* entry) +{ + addButton (entry); + return *this; +} + +// ============================================================================= +// +void RadioGroup::setCurrentRow (int row) +{ + m_currentLayout = m_layouts[row]; +} + +// ============================================================================= +// +int RadioGroup::value() const +{ + return m_buttonGroup->checkedId(); +} + +// ============================================================================= +// +void RadioGroup::setValue (int val) +{ + m_buttonGroup->button (val)->setChecked (true); +} + +// ============================================================================= +// +QRadioButton* RadioGroup::operator[] (int n) const +{ + return m_objects[n]; +} + +// ============================================================================= +// +void RadioGroup::slot_buttonPressed (int btn) +{ + emit buttonPressed (btn); + + m_oldId = m_buttonGroup->checkedId(); +} + +// ============================================================================= +// +void RadioGroup::slot_buttonReleased (int btn) +{ + emit buttonReleased (btn); + int newid = m_buttonGroup->checkedId(); + + if (m_oldId != newid) + emit valueChanged (newid); +} + +// ============================================================================= +// +RadioGroup::Iterator RadioGroup::begin() +{ + return m_objects.begin(); +} + +// ============================================================================= +// +RadioGroup::Iterator RadioGroup::end() +{ + return m_objects.end(); +} diff -r ab77deb851fa -r 8d98ee0dc917 src/rectifier.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/rectifier.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,150 @@ + + + RectifierUI + + + + 0 + 0 + 300 + 175 + + + + + 300 + 175 + + + + Rectifier + + + false + + + false + + + + + 10 + 140 + 281 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 0 + 0 + 301 + 131 + + + + + + + Condense triangles to quads + + + true + + + + + + + Substitute with rect primitives + + + true + + + + + + + Don't replace quads that have adj. cond. lines + + + + + + + Colorize result + + + + + + + + + Coplanarity threshold + + + + + + + 3 + + + 360.000000000000000 + + + 0.100000000000000 + + + + + + + + + + + + buttonBox + accepted() + RectifierUI + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + RectifierUI + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/replcoords.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/replcoords.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,196 @@ + + + ReplaceCoordsUI + + + + 0 + 0 + 239 + 153 + + + + Replace Coordinates + + + + + + Axes + + + + + + Replace X coordinates. + + + X + + + + + + + Replace Y coordinates. + + + Y + + + + + + + Replace Z coordinates. + + + Z + + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Search: + + + + + + + + + 4 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + + + + + If this is checked, all of the coordinates of selected objects will be changed. If not, they have to match the search value. + +Use this with the Relative option to offset objects, or without to project or flatten. + + + Any + + + + + + + + + Replace: + + + + + + + + + 4 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + + + + + If this is set, the replace value is added to the coordinates, rather than replaced with. + + + Relative + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ReplaceCoordsUI + accept() + + + 224 + 128 + + + 157 + 144 + + + + + buttonBox + rejected() + ReplaceCoordsUI + reject() + + + 246 + 134 + + + 252 + 144 + + + + + any + clicked(bool) + search + setDisabled(bool) + + + 207 + 13 + + + 125 + 12 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/ringFinder.cc --- a/src/ringFinder.cc Tue Mar 03 16:50:39 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 . - */ - -#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; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/ringFinder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ringFinder.cpp Tue Mar 03 22:29:27 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 . + */ + +#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; +} diff -r ab77deb851fa -r 8d98ee0dc917 src/rotpoint.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/rotpoint.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,214 @@ + + + RotPointUI + + + + 0 + 0 + 178 + 267 + + + + Set Rotation Point + + + + + + Rotation Point + + + + + + Object origin + + + + + + + World origin (0, 0, 0) + + + + + + + Custom + + + + + + + + + + false + + + Custom Point + + + + + + 4 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + + + + + 4 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + + + + + X: + + + + + + + 4 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + + + + + Y: + + + + + + + Z: + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + RotPointUI + accept() + + + 171 + 250 + + + 157 + 266 + + + + + buttonBox + rejected() + RotPointUI + reject() + + + 171 + 256 + + + 177 + 266 + + + + + customPoint + clicked(bool) + groupBox + setEnabled(bool) + + + 46 + 85 + + + 136 + 131 + + + + + worldPoint + clicked(bool) + groupBox + setDisabled(bool) + + + 72 + 66 + + + 90 + 127 + + + + + objectPoint + clicked(bool) + groupBox + setDisabled(bool) + + + 36 + 45 + + + 23 + 129 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 src/version.cc --- a/src/version.cc Tue Mar 03 16:50:39 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 . - */ - -// 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 -#include -#include -#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; -} diff -r ab77deb851fa -r 8d98ee0dc917 src/version.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/version.cpp Tue Mar 03 22:29:27 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 . + */ + +// 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 +#include +#include +#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; +} diff -r ab77deb851fa -r 8d98ee0dc917 src/ytruder.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ytruder.ui Tue Mar 03 22:29:27 2015 +0200 @@ -0,0 +1,204 @@ + + + YtruderUI + + + + 0 + 0 + 340 + 170 + + + + Ytruder + + + + + + + + Extrusion Mode + + + + + + Distance + + + true + + + + + + + Symmetry + + + + + + + Projection + + + + + + + Radial + + + + + + + + + + + + Axis + + + + + + X + + + + + + + Y + + + true + + + + + + + Z + + + + + + + + + + + + Plane depth: + + + + + + + Line threshold angle: + + + + + + + + 85 + 0 + + + + 3 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + + + + + + 85 + 0 + + + + ° + + + 3 + + + 360.000000000000000 + + + 30.000000000000000 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + YtruderUI + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + YtruderUI + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff -r ab77deb851fa -r 8d98ee0dc917 ui/about.ui --- a/ui/about.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ - - - AboutUI - - - - 0 - 0 - 320 - 400 - - - - - 320 - 400 - - - - - 16777215 - 16777215 - - - - About LDForge - - - - - - - 16777215 - 16777215 - - - - - - - :/icons/ldforge.png - - - false - - - Qt::AlignCenter - - - - - - - font-weight: bold - - - [[ VERSION INFO HERE]] - - - Qt::AlignCenter - - - - - - - Copyright (C) 2013, 2014 Teemu Piippo - - - Qt::AlignCenter - - - - - - - - 16777215 - 16777215 - - - - QFrame::NoFrame - - - <html><head/><body><p>This software is intended for usage as a parts authoring tool for the <a href="http://ldraw.org/"><span style=" text-decoration: underline; color:#0057ae;">LDraw</span></a> parts library.</p><p>LDForge is free software, and you are welcome to redistribute it under the terms of GPL v3. See the LICENSE text file for details. If the license text is not available for some reason, see <a href="http://www.gnu.org/licenses/"><span style=" text-decoration: underline; color:#0057ae;">http://www.gnu.org/licenses/</span></a> for the license terms.</p><p>The graphical assets of LDForge are licensed under the <a href="http://creativecommons.org/licenses/by-sa/3.0/"><span style=" text-decoration: underline; color:#0057ae;">CC Attribution-ShareAlike 3.0 Unported license</span></a>. The GNU GPL applies to the source code of the program. The application icon is derived from <a href="http://en.wikipedia.org/wiki/File:Anvil,_labelled_en.svg"><span style=" text-decoration: underline; color:#0057ae;">this image on Wikipedia</span></a>. The linked image (retrieved 22 May 2013) was released into the public domain.</p></body></html> - - - Qt::AlignCenter - - - true - - - - - - - In living memory of James Jessiman. - - - Qt::AlignCenter - - - - - - - QDialogButtonBox::Close - - - false - - - - - - - - - - - - buttonBox - rejected() - AboutUI - reject() - - - 296 - 384 - - - 293 - 1 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/addhistoryline.ui --- a/ui/addhistoryline.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ - - - AddHistoryLine - - - - 0 - 0 - 410 - 120 - - - - Add History Line - - - - - - - - Date: - - - - - - - - - - Username: - - - - - - - - - - Comment: - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - AddHistoryLine - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - AddHistoryLine - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/bombbox.ui --- a/ui/bombbox.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ - - - BombBox - - - - 0 - 0 - 676 - 569 - - - - Fatal Error - - - - - - - - - - - - - :/icons/bomb.png - - - - - - - Qt::Vertical - - - - 20 - 138 - - - - - - - - - - true - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Close - - - - - - - - - - - buttonBox - accepted() - BombBox - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - BombBox - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/colorsel.ui --- a/ui/colorsel.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +0,0 @@ - - - ColorSelUI - - - - 0 - 0 - 588 - 404 - - - - Select Color - - - - - - Qt::ScrollBarAlwaysOn - - - Qt::ScrollBarAlwaysOff - - - - - 0 - 0 - 384 - 287 - - - - - - - - - - - [[ COLOR ICON HERE]] - - - - - - - [[ COLOR HERE ]] - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Direct Color... - - - Direct Color... - - - - - - - Transparent - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - - buttonBox - accepted() - ColorSelUI - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - ColorSelUI - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/config.ui --- a/ui/config.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1070 +0,0 @@ - - - ConfigUI - - - - 0 - 0 - 648 - 370 - - - - Settings - - - - :/icons/settings.png:/icons/settings.png - - - - - - - - - 144 - 0 - - - - - 144 - 16777215 - - - - - Interface - - - - - Editing tools - - - - - Profile - - - - - Shortcuts - - - - - Color Toolbar - - - - - Grids - - - - - External Programs - - - - - Downloads - - - - - - - - 0 - - - - - - - Interface - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - Background color - - - - - - - This is the background color for the viewport. - - - - - - - :/icons/colorselect.png:/icons/colorselect.png - - - - - - - - - - Main color - - - - - - - This color is used for the main color. - - - - - - - :/icons/colorselect.png:/icons/colorselect.png - - - - - - - Selected color - - - - - - - This color is used for the main color. - - - - - - - :/icons/colorselect.png:/icons/colorselect.png - - - - - - - - - - Main color alpha - - - - - - - 1.000000000000000 - - - 0.050000000000000 - - - 1.000000000000000 - - - - - - - - - - Line thickness - - - - - - - How thick lines should be drawn in the viewport. - - - 1 - - - 8 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 1 - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Use logoed studs - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Anti-aliased lines - - - - - - - Display line lenghts when drawing - - - - - - - Polygons' front sides become green and back sides red. - - - Red/green BFC view (incomplete) - - - - - - - Makes colored objects (non-16 and 24) appear colored in the list view. A red triangle will, for instance, have its entry written in red text. This can be useful to locate colored objects. - - - Colorize objects in list view - - - - - - - Makes all edgelines appear black. If this is not set, edge lines take their color as defined in LDConfig.ldr. - - - Black edges - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Highlight object below cursor - - - - - - - List implicitly loaded files - - - - - - - - - - - - - - - - - - Editing tools - - - - - - Rounding - - - - - - - - Position decimals: - - - - - - - - - - Matrix decimals: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - Profile - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - QFormLayout::ExpandingFieldsGrow - - - - - Name: - - - - - - - - 250 - 0 - - - - - - - - Username: - - - - - - - - - - Use CA license - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - Here you can alter keyboard shortcuts for almost all LDForge actions. Only exceptions are the controls for the viewport. Use the set button to set a key shortcut, clear to remove it and reset to restore the shortcut to its default value. - -Shortcut changes apply immediately after closing this window. - - - Shortcuts - - - - - - Qt::ScrollBarAsNeeded - - - - - - - - - Set - - - - - - - Reset - - - - :/icons/undo.png:/icons/undo.png - - - - - - - Clear - - - - :/icons/delete.png:/icons/delete.png - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - - Here you can alter the layout of the quick colors toolbar. Use the controls to add, remove or edit the colors used. You can also add separators in between colors. - -Usually this contains MainColor, EdgeColor and some auxiliary colors used to group objects. - - - Color Toolbar - - - - - - Qt::ScrollBarAsNeeded - - - - - - - - - Add Color - - - - :/icons/palette.png:/icons/palette.png - - - - - - - Add Separator - - - - - - - Edit - - - - :/icons/mode-draw.png:/icons/mode-draw.png - - - - - - - Qt::Horizontal - - - - - - - Move Up - - - - :/icons/arrow-up.png:/icons/arrow-up.png - - - - - - - Move Down - - - - :/icons/arrow-down.png:/icons/arrow-down.png - - - - - - - Qt::Horizontal - - - - - - - Remove - - - - :/icons/delete.png:/icons/delete.png - - - - - - - Clear List - - - - :/icons/delete.png:/icons/delete.png - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - - Grids - - - - - - - - ° - - - 3 - - - 360.000000000000000 - - - - - - - Coordinate snap - - - - - - - Angle snap - - - - - - - - - - :/icons/grid-coarse.png - - - - - - - Coarse - - - - - - - LDU - - - 5 - - - 10000.000000000000000 - - - - - - - ° - - - 3 - - - 360.000000000000000 - - - - - - - - - - :/icons/grid-medium.png - - - - - - - Medium - - - - - - - LDU - - - 5 - - - 10000.000000000000000 - - - - - - - ° - - - 3 - - - 360.000000000000000 - - - - - - - - - - :/icons/grid-fine.png - - - - - - - Fine - - - - - - - LDU - - - 5 - - - 10000.000000000000000 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 165 - - - - - - - - - - - - - - - LDForge supports launching of several third-party utility tools; here you can set the file paths to these tools. Set the paths of the tools to the exe files. - -Under Linux, you can also set the programs to be launched with Wine, so you can use Windows binaries here as well. You will obviously need Wine installed. A 'wine' command in PATH is necessary for this to work. - - - External Programs - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - Downloads - - - - - - - - Download path: - - - - - - - - - - - - - - :/icons/folder.png:/icons/folder.png - - - - - - - - - Attempt to download missing parts from the PT - - - - - - - <p>When this is set, LDForge tries to adjust and correct part paths based on the input. A full path given to the download prompt should be of form <tt>"&lt;dir&gt;/&lt;file&gt;.dat"</tt> - with this set, input can be automatically completed.</p> - -<p>Examples: -<ul> -<li>3002 -> parts/3002.dat</li> -<li>3002.da -> parts/3002.dat</li> -<li>3002s01 -> parts/s/3002s01.dat</li> -<li>4-4cyli -> p/4-4cyli.dat</li> -</ul></p> - - - Correct and guess part paths - - - - - - - If this is set, LDForge will close the download prompt after everything has been downloaded. The prompt will not be closed if a download has failed. - - - Close download prompt after completion - - - - - - - Qt::Vertical - - - - 20 - 187 - - - - - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/coverer.ui --- a/ui/coverer.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,157 +0,0 @@ - - - CovererUI - - - - 0 - 0 - 310 - 220 - - - - - 310 - 220 - - - - - 10000 - 10000 - - - - Coverer - - - - - 40 - 180 - 261 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 10 - 5 - 291 - 171 - - - - - - - Shape 1 - - - - - - - Shape 2 - - - - - - - - - - - - - Segment split length: - - - - - - - Bias: - - - - - - - 10000.000000000000000 - - - - - - - -100 - - - 100 - - - - - - - Reverse shape 2 - - - - - - - Old sweep method - - - - - - - - - - buttonBox - accepted() - CovererUI - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - CovererUI - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/downloadfrom.ui --- a/ui/downloadfrom.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ - - - DownloadFrom - - - - 0 - 0 - 546 - 405 - - - - Download from LDraw.org - - - - - - - 11 - 75 - true - - - - Download from LDraw.org - - - Qt::AlignCenter - - - - - - - QFormLayout::ExpandingFieldsGrow - - - - - Source: - - - - - - - - 0 - 0 - - - - - Parts tracker - - - - - Custom URL - - - - - - - - File name: - - - - - - - - - - - - false - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::NoSelection - - - false - - - - File - - - - 50 - false - - - - - - Status - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Abort|QDialogButtonBox::Close - - - - - - - - - buttonBox - rejected() - DownloadFrom - reject() - - - 322 - 312 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/edger2.ui --- a/ui/edger2.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,297 +0,0 @@ - - - Edger2Dialog - - - - 0 - 0 - 357 - 257 - - - - Edger 2 - - - 1.000000000000000 - - - - - - - - - - Precision - - - - - - - - - - 4 - - - 0.001000000000000 - - - - - - - ° - - - 4 - - - 0.000000000000000 - - - 360.000000000000000 - - - 0.100000000000000 - - - 0.100000000000000 - - - - - - - Flat angle - - - - - - - Conditional line angle - - - - - - - ° - - - 4 - - - 360.000000000000000 - - - 0.100000000000000 - - - 60.000000000000000 - - - - - - - ° - - - 4 - - - 360.000000000000000 - - - 0.100000000000000 - - - 60.000000000000000 - - - - - - - Edge line angle - - - - - - - 1 - - - - Only - - - - - Normally - - - - - Never - - - - - - - - Create unmatched edges - - - - - - - - - - - - - Color-coded result - - - - - - - Delete existing lines - - - - - - - Delete existing cond. lines - - - - - - - - - - - File is BFCd - - - - - - - false - - - Convex cond. lines only - - - - - - - false - - - Concave cond. lines only - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - Edger2Dialog - accept() - - - 254 - 250 - - - 157 - 256 - - - - - buttonBox - rejected() - Edger2Dialog - reject() - - - 322 - 250 - - - 286 - 256 - - - - - bfc - clicked(bool) - convex - setEnabled(bool) - - - 249 - 157 - - - 248 - 185 - - - - - bfc - clicked(bool) - concave - setEnabled(bool) - - - 283 - 154 - - - 283 - 205 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/editraw.ui --- a/ui/editraw.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +0,0 @@ - - - EditRawUI - - - - 0 - 0 - 400 - 87 - - - - Edit LDraw Code - - - - - - LDraw code: - - - - - - - <html><head/><body><p>The LDraw code of this object. The code written here is expected to be valid LDraw code, invalid code here results the object being turned into an error object. Please do refer to the <a href="http://www.ldraw.org/article/218.html"><span style=" text-decoration: underline; color:#0057ae;">official file format standard</span></a> for further information.</p></body></html> - - - - - - - - - - 16 - 16 - - - - - - - :/icons/error.png - - - true - - - - - - - true - - - color: #900 - - - Error description - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - - - - buttonBox - accepted() - EditRawUI - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - EditRawUI - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/extprogpath.ui --- a/ui/extprogpath.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ - - - ExtProgPath - - - - 0 - 0 - 444 - 89 - - - - Program path required - - - - - - Please input a path for <PROGRAM>: - - - - - - - - - - - - ... - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - ExtProgPath - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - ExtProgPath - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/flip.ui --- a/ui/flip.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ - - - FlipUI - - - - 0 - 0 - 178 - 93 - - - - Flip - - - - - - Axes - - - - - - X - - - - - - - Y - - - - - - - Z - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - FlipUI - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - FlipUI - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/intersector.ui --- a/ui/intersector.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,175 +0,0 @@ - - - IntersectorUI - - - - 0 - 0 - 250 - 200 - - - - - 250 - 200 - - - - - 250 - 200 - - - - Intersector - - - - - 10 - 160 - 231 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 10 - 10 - 233 - 143 - - - - - - - - - Cutter: - - - - - - - Input: - - - - - - - - - - - - - - - - - Colorize output - - - - - - - Repeat inverse - - - - - - - No condensing - - - - - - - Add edges - - - - - - - - - - - Prescaling factor - - - - - - - - - - 10000.000000000000000 - - - 0.010000000000000 - - - 1.000000000000000 - - - - - - - - - - - - buttonBox - accepted() - IntersectorUI - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - IntersectorUI - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/isecalc.ui --- a/ui/isecalc.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,112 +0,0 @@ - - - IsecalcUI - - - - 0 - 0 - 240 - 120 - - - - - 240 - 120 - - - - - 10000 - 120 - - - - Isecalc - - - - - 30 - 80 - 201 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 10 - 10 - 221 - 61 - - - - - - - - - - - - - Shape 1: - - - - - - - Shape 2: - - - - - - - - - - buttonBox - accepted() - IsecalcUI - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - IsecalcUI - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/ldforge.ui --- a/ui/ldforge.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1702 +0,0 @@ - - - LDForgeUI - - - - 0 - 0 - 1008 - 641 - - - - - - - - :/icons/ldforge.png:/icons/ldforge.png - - - - - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - 0 - - - - - 0 - 0 - 234 - 428 - - - - Document - - - - - - - 0 - 0 - - - - QAbstractItemView::ExtendedSelection - - - - - - - - - 0 - 0 - 234 - 428 - - - - Tool Options - - - - - - Circle Tool Options - - - - - - - - High resolution - - - - - - - Segments: - - - - - - - - - true - - - 1 - - - 16 - - - 16 - - - - - - - a / b - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - 0 - 0 - 234 - 428 - - - - Primitives - - - - - - QAbstractItemView::DragOnly - - - false - - - - 1 - - - - - - - - - - - - - - - - 0 - 0 - 1008 - 21 - - - - - File - - - - Open Recent... - - - - :/icons/open-recent.png:/icons/open-recent.png - - - - - - - - - - - - - - - - - - - - - - - - - - - View - - - - - - - - - - - - - - - - - - - - Insert - - - - - - - - - - - - - - Edit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Tools - - - - - - - - - - - - - - - - - - - - - - - - - - - - - External Tools - - - - - - - - - - - Help - - - - - - - - - Move - - - - Grids - - - - - - - - Move Objects - - - - - - - - - - - Object List - - - - - - - Rotate - - - - - - - - - - - - - - - - - - - - - - - - - - - - File - - - TopToolBarArea - - - false - - - - - - - - - - New Object - - - TopToolBarArea - - - false - - - - - - - - - - - - Basic tools - - - TopToolBarArea - - - false - - - - - - - - - - - Select - - - TopToolBarArea - - - false - - - - - - - - Grid - - - TopToolBarArea - - - false - - - - - - - - Display options - - - TopToolBarArea - - - true - - - - - - - - - - Editing tools - - - TopToolBarArea - - - false - - - - - - - - - - - - - - - - - Editing modes - - - LeftToolBarArea - - - false - - - - - - - - - - - Colors - - - RightToolBarArea - - - false - - - - - External Programs - - - TopToolBarArea - - - false - - - - - - - - - - - - :/icons/brick.png:/icons/brick.png - - - New Part - - - Create a new part model. - - - Ctrl+N - - - - - - :/icons/file-open.png:/icons/file-open.png - - - Open - - - Load a part model from a file. - - - Ctrl+O - - - - - - :/icons/file-save.png:/icons/file-save.png - - - Save - - - Save the part model. - - - - - - Ctrl+S - - - - - - :/icons/file-save-as.png:/icons/file-save-as.png - - - Save As.. - - - Save the part model to a specific file. - - - Ctrl+Shift+S - - - - - - :/icons/file-import.png:/icons/file-import.png - - - Insert From.. - - - - - - :/icons/file-export.png:/icons/file-export.png - - - Export To.. - - - - - - :/icons/settings.png:/icons/settings.png - - - Settings - - - Edit the settings of LDForge. - - - - - - - - - :/icons/settings.png:/icons/settings.png - - - Set LDraw Path - - - Change the LDraw directory path. - - - - - - :/icons/radial.png:/icons/radial.png - - - Scan Primitives - - - Scan the primitives folder for primitive info. Use this if you add new primitives. - - - - - - :/icons/exit.png:/icons/exit.png - - - Exit - - - Ctrl+Q - - - - - Reset View - - - - - true - - - true - - - - :/icons/axes.png:/icons/axes.png - - - Draw Axes - - - - - true - - - - :/icons/wireframe.png:/icons/wireframe.png - - - Wireframe - - - - - true - - - - :/icons/bfc-view.png:/icons/bfc-view.png - - - BFC Red/Green View - - - Shift+B - - - - - - :/icons/overlay.png:/icons/overlay.png - - - Set Overlay Image - - - - - - :/icons/overlay-clear.png:/icons/overlay-clear.png - - - Clear Overlay Image - - - - - - :/icons/screencap.png:/icons/screencap.png - - - Screenshot - - - - - LDraw Code.. - - - - - - :/icons/add-line.png:/icons/add-line.png - - - New Line - - - - - - :/icons/add-subfile.png:/icons/add-subfile.png - - - New Subfile Reference - - - - - - :/icons/add-triangle.png:/icons/add-triangle.png - - - New Triangle - - - - - - :/icons/add-quad.png:/icons/add-quad.png - - - New Quadrilateral - - - - - - :/icons/add-condline.png:/icons/add-condline.png - - - New Conditional Line - - - - - - :/icons/add-comment.png:/icons/add-comment.png - - - New Comment - - - - - - :/icons/add-bfc.png:/icons/add-bfc.png - - - New BFC Statement - - - - - - :/icons/undo.png:/icons/undo.png - - - Undo - - - Undo a step. - - - Ctrl+Z - - - - - - :/icons/redo.png:/icons/redo.png - - - Redo - - - Redo a step. - - - Ctrl+Shift+Z - - - - - - :/icons/cut.png:/icons/cut.png - - - Cut - - - Cut the current selection to clipboard. - - - Ctrl+X - - - - - - :/icons/copy.png:/icons/copy.png - - - Copy - - - Copy the current selection to clipboard. - - - - - - Ctrl+C - - - - - - :/icons/paste.png:/icons/paste.png - - - Paste - - - Paste clipboard contents. - - - Ctrl+V - - - - - - :/icons/delete.png:/icons/delete.png - - - Delete - - - Delete the selection - - - Del - - - - - - :/icons/select-all.png:/icons/select-all.png - - - Select All - - - Ctrl+A - - - - - - :/icons/select-color.png:/icons/select-color.png - - - Select by Color - - - - - - :/icons/select-type.png:/icons/select-type.png - - - Select by Type - - - - - true - - - true - - - - :/icons/mode-select.png:/icons/mode-select.png - - - Select Mode - - - S - - - - - true - - - - :/icons/mode-draw.png:/icons/mode-draw.png - - - Draw Mode - - - D - - - - - Set Draw Depth - - - - - - :/icons/palette.png:/icons/palette.png - - - Set Color - - - Set the color on given objects. - - - Shift+C - - - - - - :/icons/autocolor.png:/icons/autocolor.png - - - Auto-color - - - Set the color of the given object to the first found unused color. - - - Ctrl+Shift+C - - - - - - :/icons/uncolorize.png:/icons/uncolorize.png - - - Uncolor - - - Uncolor - - - Reduce colors of everything selected to main and edge colors - - - - - - :/icons/inline.png:/icons/inline.png - - - Inline - - - Inline selected subfiles. - - - Ctrl+I - - - - - - :/icons/inline-deep.png:/icons/inline-deep.png - - - Deep Inline - - - Recursively inline selected subfiles down to polygons only. - - - Ctrl+Shift+I - - - - - - :/icons/invert.png:/icons/invert.png - - - Invert - - - Ctrl+Shift+W - - - - - - :/icons/radial.png:/icons/radial.png - - - Generate Primitive - - - - - - :/icons/quad-split.png:/icons/quad-split.png - - - Split Quads - - - Split quads into triangles. - - - - - - :/icons/set-contents.png:/icons/set-contents.png - - - Edit LDraw Code - - - Edit the LDraw code of this object. - - - - - - :/icons/make-borders.png:/icons/make-borders.png - - - Make Borders - - - Add borders around given polygons. - - - - - - :/icons/round-coords.png:/icons/round-coords.png - - - Round Coordinates - - - Round coordinates down to 3/4 decimals - - - - - - :/icons/visibility-toggle.png:/icons/visibility-toggle.png - - - Toggle Visibility - - - Toggles visibility/hiding on objects. - - - - - - :/icons/replace-coords.png:/icons/replace-coords.png - - - Replace Coordinates - - - Find and replace coordinate values. - - - - - - :/icons/flip.png:/icons/flip.png - - - Flip - - - Flip coordinates. - - - Ctrl+Shift+F - - - - - Demote Conditional Lines - - - Demote conditional lines down to normal lines. - - - - - - :/icons/ytruder.png:/icons/ytruder.png - - - Ytruder - - - Extrude selected lines to a given plane - - - - - - :/icons/rectifier.png:/icons/rectifier.png - - - Rectifier - - - Optimizes quads into rect primitives. - - - - - - :/icons/intersector.png:/icons/intersector.png - - - Intersector - - - Perform clipping between two input groups. - - - - - - :/icons/isecalc.png:/icons/isecalc.png - - - Isecalc - - - Compute intersection edgelines between two input groups. - - - - - - :/icons/coverer.png:/icons/coverer.png - - - Coverer - - - Fill the space between two line shapes - - - - - Edger 2 - - - - - false - - - - :/icons/help.png:/icons/help.png - - - Help - - - F1 - - - - - - :/icons/ldforge.png:/icons/ldforge.png - - - About LDForge - - - - - About Qt - - - - - true - - - - :/icons/grid-coarse.png:/icons/grid-coarse.png - - - Coarse Grid - - - - - true - - - true - - - - :/icons/grid-medium.png:/icons/grid-medium.png - - - Medium Grid - - - - - true - - - - :/icons/grid-fine.png:/icons/grid-fine.png - - - Fine Grid - - - - - Edit Selected Object - - - - - - :/icons/arrow-up.png:/icons/arrow-up.png - - - Move Up - - - PgUp - - - - - - :/icons/arrow-down.png:/icons/arrow-down.png - - - Move Down - - - PgDown - - - - - - :/icons/move-x-neg.png:/icons/move-x-neg.png - - - Move -X - - - Left - - - - - - :/icons/move-x-pos.png:/icons/move-x-pos.png - - - Move +X - - - Right - - - - - - :/icons/move-y-neg.png:/icons/move-y-neg.png - - - Move -Y - - - Home - - - - - - :/icons/move-y-pos.png:/icons/move-y-pos.png - - - Move +Y - - - End - - - - - - :/icons/move-z-neg.png:/icons/move-z-neg.png - - - Move -Z - - - Down - - - - - - :/icons/move-z-pos.png:/icons/move-z-pos.png - - - Move +Z - - - Up - - - - - Rotate -X - - - Ctrl+Left - - - - - Rotate +X - - - Ctrl+Right - - - - - Rotate -Y - - - Ctrl+End - - - - - Rotate +Y - - - Ctrl+Home - - - - - Rotate -Z - - - Ctrl+Down - - - - - Rotate +Z - - - Ctrl+Up - - - - - Set Rotation Point - - - - - Save All - - - - - Close - - - Ctrl+W - - - - - Close All - - - Ctrl+Shift+W - - - - - - :/icons/file-new.png:/icons/file-new.png - - - New File - - - Ctrl+N - - - - - Download From... - - - - - Add History Line - - - - - Go to Line... - - - Ctrl+G - - - - - true - - - - :/icons/mode-circle.png:/icons/mode-circle.png - - - Circle Mode - - - C - - - - - - :/icons/visibility-hide.png:/icons/visibility-hide.png - - - Hide - - - Hides objects from view - - - - - - :/icons/visibility-show.png:/icons/visibility-show.png - - - Reveal - - - Reveals objects. Undoes hiding. - - - - - Subfile Selection - - - - - true - - - - :/icons/mode-angle.png:/icons/mode-angle.png - - - Draw Angles - - - Draw angle information when drawing lines - - - - - true - - - - :/icons/random-colors.png:/icons/random-colors.png - - - Random colors - - - Shift+R - - - - - Open Subfiles for Editing - - - Opens the documents used by the selected subparts for editing. - - - - - Split Lines... - - - - - true - - - Draw surfaces - - - Render surfaces (i.e. quads and triangles) on the viewport. - - - - - true - - - Draw edgelines - - - Render edgelines on the viewport - - - - - true - - - Draw conditional lines - - - Render conditional lines on the viewport. - - - - - true - - - - :/icons/mode-magicwand.png:/icons/mode-magicwand.png - - - Magic wand - - - W - - - - - true - - - - :/icons/mode-rectangle.png:/icons/mode-rectangle.png - - - Rectangle Mode - - - R - - - - - true - - - - :/icons/line.png:/icons/line.png - - - Line Path Mode - - - P - - - - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/ldrawpath.ui --- a/ui/ldrawpath.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ - - - LDPathUI - - - - 0 - 0 - 344 - 123 - - - - Set LDraw Path - - - - - - Please input your LDraw directory root to proceed: - - - - - - - - - LDraw Path: - - - - - - - - - - - - - - :/icons/folder.png:/icons/folder.png - - - - - - - - - font-weight: bold - - - [[ Information ]] - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/makeprim.ui --- a/ui/makeprim.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,309 +0,0 @@ - - - MakePrimUI - - - - 0 - 0 - 336 - 147 - - - - Generate a Primitive - - - - - - - - - - - 0 - 0 - - - - Type - - - - - - Circle - - - true - - - false - - - - - - - Cylinder - - - - - - - Disc - - - - - - - Disc Negative - - - - - - - Ring - - - - - - - Cone - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Hi-res - - - - - - - - - Segments: - - - - - - - Ring number: - - - - - - - 1 - - - 16 - - - 16 - - - - - - - false - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - MakePrimUI - accept() - - - 254 - 140 - - - 157 - 146 - - - - - buttonBox - rejected() - MakePrimUI - reject() - - - 322 - 140 - - - 286 - 146 - - - - - rb_circle - clicked(bool) - sb_ringnum - setDisabled(bool) - - - 45 - 41 - - - 305 - 86 - - - - - rb_cylinder - clicked(bool) - sb_ringnum - setDisabled(bool) - - - 109 - 42 - - - 287 - 86 - - - - - rb_disc - clicked(bool) - sb_ringnum - setDisabled(bool) - - - 49 - 66 - - - 287 - 87 - - - - - rb_ndisc - clicked(bool) - sb_ringnum - setDisabled(bool) - - - 121 - 58 - - - 288 - 83 - - - - - rb_ring - clicked(bool) - sb_ringnum - setEnabled(bool) - - - 45 - 90 - - - 301 - 86 - - - - - rb_cone - clicked(bool) - sb_ringnum - setEnabled(bool) - - - 111 - 89 - - - 302 - 85 - - - - - - enableRingNumber() - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/newpart.ui --- a/ui/newpart.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,191 +0,0 @@ - - - NewPartUI - - - - 0 - 0 - 491 - 233 - - - - New Part - - - - - - - - - - - - - :/icons/brick.png - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - - - - - - - - - - - Author: - - - - - - - Title: - - - - - - - - - - - - - BFC winding - - - - - - CCW - - - true - - - - - - - CW - - - - - - - None - - - - - - - - - - - - Use CA license - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - - - buttonBox - accepted() - NewPartUI - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - NewPartUI - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/openprogress.ui --- a/ui/openprogress.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ - - - OpenProgressUI - - - - 0 - 0 - 400 - 89 - - - - Opening - - - - - - [[ Progress text ]] - - - - - - - 24 - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel - - - - - - - - - buttonBox - accepted() - OpenProgressUI - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - OpenProgressUI - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/overlay.ui --- a/ui/overlay.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,278 +0,0 @@ - - - OverlayUI - - - - 0 - 0 - 276 - 244 - - - - Set Overlay - - - - :/icons/overlay.png:/icons/overlay.png - - - - - - Camera - - - - - - Top - - - - :/icons/camera-top.png:/icons/camera-top.png - - - - - - - Front - - - - :/icons/camera-front.png:/icons/camera-front.png - - - - - - - Left - - - - :/icons/camera-left.png:/icons/camera-left.png - - - - - - - Bottom - - - - :/icons/camera-bottom.png:/icons/camera-bottom.png - - - - - - - Back - - - - :/icons/camera-back.png:/icons/camera-back.png - - - - - - - Right - - - - :/icons/camera-right.png:/icons/camera-right.png - - - - - - - - - - Image - - - - - - QFormLayout::ExpandingFieldsGrow - - - - - File: - - - - - - - - - - - - - - - - :/icons/file-open.png:/icons/file-open.png - - - - - - - - - Origin: - - - - - - - - - - 80 - 0 - - - - px - - - - - - 10000 - - - - - - - - 80 - 0 - - - - px - - - 10000 - - - - - - - - - Dimensions: - - - - - - - - - - 80 - 0 - - - - LDU - - - 10000.000000000000000 - - - - - - - - 80 - 0 - - - - LDU - - - 0.000000000000000 - - - 10000.000000000000000 - - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok - - - - - - - - - - - buttonBox - accepted() - OverlayUI - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - OverlayUI - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/rectifier.ui --- a/ui/rectifier.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,150 +0,0 @@ - - - RectifierUI - - - - 0 - 0 - 300 - 175 - - - - - 300 - 175 - - - - Rectifier - - - false - - - false - - - - - 10 - 140 - 281 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 0 - 0 - 301 - 131 - - - - - - - Condense triangles to quads - - - true - - - - - - - Substitute with rect primitives - - - true - - - - - - - Don't replace quads that have adj. cond. lines - - - - - - - Colorize result - - - - - - - - - Coplanarity threshold - - - - - - - 3 - - - 360.000000000000000 - - - 0.100000000000000 - - - - - - - - - - - - buttonBox - accepted() - RectifierUI - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - RectifierUI - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/replcoords.ui --- a/ui/replcoords.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,196 +0,0 @@ - - - ReplaceCoordsUI - - - - 0 - 0 - 239 - 153 - - - - Replace Coordinates - - - - - - Axes - - - - - - Replace X coordinates. - - - X - - - - - - - Replace Y coordinates. - - - Y - - - - - - - Replace Z coordinates. - - - Z - - - - - - - - - - QFormLayout::ExpandingFieldsGrow - - - - - Search: - - - - - - - - - 4 - - - -10000.000000000000000 - - - 10000.000000000000000 - - - - - - - If this is checked, all of the coordinates of selected objects will be changed. If not, they have to match the search value. - -Use this with the Relative option to offset objects, or without to project or flatten. - - - Any - - - - - - - - - Replace: - - - - - - - - - 4 - - - -10000.000000000000000 - - - 10000.000000000000000 - - - - - - - If this is set, the replace value is added to the coordinates, rather than replaced with. - - - Relative - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - ReplaceCoordsUI - accept() - - - 224 - 128 - - - 157 - 144 - - - - - buttonBox - rejected() - ReplaceCoordsUI - reject() - - - 246 - 134 - - - 252 - 144 - - - - - any - clicked(bool) - search - setDisabled(bool) - - - 207 - 13 - - - 125 - 12 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/rotpoint.ui --- a/ui/rotpoint.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,214 +0,0 @@ - - - RotPointUI - - - - 0 - 0 - 178 - 267 - - - - Set Rotation Point - - - - - - Rotation Point - - - - - - Object origin - - - - - - - World origin (0, 0, 0) - - - - - - - Custom - - - - - - - - - - false - - - Custom Point - - - - - - 4 - - - -10000.000000000000000 - - - 10000.000000000000000 - - - - - - - 4 - - - -10000.000000000000000 - - - 10000.000000000000000 - - - - - - - X: - - - - - - - 4 - - - -10000.000000000000000 - - - 10000.000000000000000 - - - - - - - Y: - - - - - - - Z: - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - RotPointUI - accept() - - - 171 - 250 - - - 157 - 266 - - - - - buttonBox - rejected() - RotPointUI - reject() - - - 171 - 256 - - - 177 - 266 - - - - - customPoint - clicked(bool) - groupBox - setEnabled(bool) - - - 46 - 85 - - - 136 - 131 - - - - - worldPoint - clicked(bool) - groupBox - setDisabled(bool) - - - 72 - 66 - - - 90 - 127 - - - - - objectPoint - clicked(bool) - groupBox - setDisabled(bool) - - - 36 - 45 - - - 23 - 129 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 ui/ytruder.ui --- a/ui/ytruder.ui Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,204 +0,0 @@ - - - YtruderUI - - - - 0 - 0 - 340 - 170 - - - - Ytruder - - - - - - - - Extrusion Mode - - - - - - Distance - - - true - - - - - - - Symmetry - - - - - - - Projection - - - - - - - Radial - - - - - - - - - - - - Axis - - - - - - X - - - - - - - Y - - - true - - - - - - - Z - - - - - - - - - - - - Plane depth: - - - - - - - Line threshold angle: - - - - - - - - 85 - 0 - - - - 3 - - - -10000.000000000000000 - - - 10000.000000000000000 - - - - - - - - 85 - 0 - - - - ° - - - 3 - - - 360.000000000000000 - - - 30.000000000000000 - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - YtruderUI - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - YtruderUI - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff -r ab77deb851fa -r 8d98ee0dc917 updaterevision.py --- a/updaterevision.py Tue Mar 03 16:50:39 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -# -# Copyright 2014 Teemu Piippo -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER -# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -import sys -import subprocess -from datetime import datetime - -if len (sys.argv) != 2: - print 'usage: %s ' % sys.argv[0] - quit (1) - -oldrev = '' - -try: - with open (sys.argv[1]) as fp: - oldrev = fp.readline().replace ('\n', '').replace ('// ', '') -except IOError: - pass - -data = subprocess.check_output (['hg', 'log', '-r.', '--template', - '{node|short} {branch} {date|hgdate}']).replace ('\n', '').split (' ') - -rev = data[0] -branch = data[1] -timestamp = int (data[2]) -date = datetime.utcfromtimestamp (timestamp) -datestring = date.strftime ('%y%m%d-%H%M') if date.year >= 2000 else '000000-0000' - -if len(rev) > 7: - rev = rev[0:7] - -if subprocess.check_output (['hg', 'id', '-n']).replace ('\n', '')[-1] == '+': - rev += '+' - -if rev == oldrev: - print "%s is up to date at %s" % (sys.argv[1], rev) - quit (0) - -with open (sys.argv[1], 'w') as fp: - fp.write ('// %s\n' % rev) - fp.write ('#define HG_NODE "%s"\n' % rev) - fp.write ('#define HG_BRANCH "%s"\n' % branch) - fp.write ('#define HG_DATE_VERSION "%s"\n' % datestring) - fp.write ('#define HG_DATE_STRING "%s"\n' % date.strftime ('%d %b %Y')) - fp.write ('#define HG_DATE_TIME %d\n' % int (timestamp)) - print '%s updated to %s' % (sys.argv[1], rev)