Wed, 08 May 2013 15:19:06 +0300
Moved source files to src/, removed zz_ prefix off dialog files.
--- a/bbox.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,167 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "common.h" -#include "bbox.h" -#include "ldtypes.h" -#include "file.h" - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -bbox::bbox () { - reset (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void bbox::calculate () { - reset (); - - if (!g_curfile) - return; - - for (LDObject* obj : g_curfile->m_objs) - calcObject (obj); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void bbox::calcObject (LDObject* obj) { - switch (obj->getType ()) { - case LDObject::Line: - { - LDLine* line = static_cast<LDLine*> (obj); - for (short i = 0; i < 2; ++i) - calcVertex (line->vaCoords[i]); - } - break; - - case LDObject::Triangle: - { - LDTriangle* tri = static_cast<LDTriangle*> (obj); - for (short i = 0; i < 3; ++i) - calcVertex (tri->vaCoords[i]); - } - break; - - case LDObject::Quad: - { - LDQuad* quad = static_cast<LDQuad*> (obj); - for (short i = 0; i < 4; ++i) - calcVertex (quad->vaCoords[i]); - } - break; - - case LDObject::CondLine: - { - LDCondLine* line = static_cast<LDCondLine*> (obj); - for (short i = 0; i < 4; ++i) - calcVertex (line->vaCoords[i]); - } - break; - - case LDObject::Subfile: - { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - vector<LDObject*> objs = ref->inlineContents (true, true); - - for (LDObject* obj : objs) { - calcObject (obj); - delete obj; - } - } - break; - - case LDObject::Radial: - { - LDRadial* rad = static_cast<LDRadial*> (obj); - vector<LDObject*> objs = rad->decompose (true); - - for (LDObject* obj : objs) { - calcObject (obj); - delete obj; - } - } - break; - - default: - break; - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void bbox::calcVertex (vertex v) { - for (const Axis ax : g_Axes) { - if (v[ax] < m_v0[ax]) - m_v0[ax] = v[ax]; - - if (v[ax] > m_v1[ax]) - m_v1[ax] = v[ax]; - } - - m_empty = false; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void bbox::reset () { - m_v0[X] = m_v0[Y] = m_v0[Z] = +0x7FFFFFFF; - m_v1[X] = m_v1[Y] = m_v1[Z] = -0x7FFFFFFF; - - m_empty = true; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -double bbox::size () const { - double fXScale = (m_v0[X] - m_v1[X]); - double fYScale = (m_v0[Y] - m_v1[Y]); - double fZScale = (m_v0[Z] - m_v1[Z]); - double fSize = fZScale; - - if (fXScale > fYScale) { - if (fXScale > fZScale) - fSize = fXScale; - } else if (fYScale > fZScale) - fSize = fYScale; - - if (abs (fSize) >= 2.0f) - return abs (fSize / 2); - - return 1.0f; -} - -// ============================================================================= -vertex bbox::center () const { - return vertex ( - (m_v0[X] + m_v1[X]) / 2, - (m_v0[Y] + m_v1[Y]) / 2, - (m_v0[Z] + m_v1[Z]) / 2); -} - -// ============================================================================= -bool bbox::empty() const { - return m_empty; -} \ No newline at end of file
--- a/bbox.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef BBOX_H -#define BBOX_H - -#include "common.h" -#include "types.h" - -// ============================================================================= -// bbox -// -// The bounding box is the box that encompasses a given set of objects. The -// global instance g_BBox is the bbox for the model we have open. -// v0 is the minimum vertex, v1 is the maximum vertex. -// ============================================================================= -class bbox { -public: - bbox (); - void reset (); - void calculate (); - double size () const; - void calcObject (LDObject* obj); - void calcVertex (vertex v); - vertex center () const; - bool empty () const; - - bbox& operator<< (LDObject* obj) { - calcObject (obj); - return *this; - } - - bbox& operator<< (vertex& v) { - calcVertex (v); - return *this; - } - - const vertex& v0 () { return m_v0; } - const vertex& v1 () { return m_v1; } - -private: - vertex m_v0, m_v1; - bool m_empty; -}; - -#endif // BBOX_H \ No newline at end of file
--- a/colors.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,173 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "common.h" -#include "colors.h" -#include "file.h" -#include "misc.h" -#include <qcolor.h> - -static color* g_LDColors[MAX_COLORS]; -static bool g_bColorsInit = false; - -void initColors () { - if (g_bColorsInit) - return; - - logf ("%s: initializing color information.\n", __func__); - - color* col; - - // Always make sure there's 16 and 24 available. They're special like that. - col = new color; - col->zColorString = "#AAAAAA"; - col->qColor = col->zColorString.chars (); - col->qEdge = Qt::black; - g_LDColors[maincolor] = col; - - col = new color; - col->zColorString = "#000000"; - col->qEdge = col->qColor = Qt::black; - g_LDColors[edgecolor] = col; - - parseLDConfig (); - - g_bColorsInit = true; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -color* getColor (short dColorNum) { - // Check bounds - if (dColorNum < 0 || dColorNum >= MAX_COLORS) - return null; - - return g_LDColors[dColorNum]; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -static bool parseLDConfigTag (StringParser& pars, char const* sTag, str& zVal) { - short dPos; - if (!pars.findToken (dPos, sTag, 1)) - return false; - - return pars.getToken (zVal, dPos + 1); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -short color::index () { - short idx = 0; - for (color* it : g_LDColors) { - if (it == this) - return idx; - idx++; - } - - return -1; -} - -// ============================================================================= -uchar luma (QColor& col) { - return (0.2126f * col.red ()) + - (0.7152f * col.green ()) + - (0.0722f * col.blue ()); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void parseLDConfig () { - FILE* fp = openLDrawFile ("LDConfig.ldr", false); - - if (!fp) - return; - - // Even though LDConfig.ldr is technically an LDraw file, parsing it as one - // would be overkill by any standard. - char line[1024]; - while (fgets (line, sizeof line, fp)) { - if (strlen (line) == 0 || line[0] != '0') - continue; // empty or illogical - - str zLine = line; - zLine.replace ("\n", ""); - zLine.replace ("\r", ""); - - StringParser pars (zLine, ' '); - short dCode = 0, dAlpha = 255; - str zName, zColor, zEdge, zValue; - - // Check 0 !COLOUR, parse the name - if (!pars.tokenCompare (0, "0") || !pars.tokenCompare (1, "!COLOUR") || !pars.getToken (zName, 2)) - continue; - - // Replace underscores in the name with spaces for readability - zName.replace ("_", " "); - - // get the CODE tag - if (!parseLDConfigTag (pars, "CODE", zValue)) - continue; - - // Ensure that the code is within range. must be within 0 - 512 - dCode = atoi (zValue); - if (dCode < 0 || dCode >= 512) - continue; - - // Don't let LDConfig.ldr override the special colors 16 and 24. However, - // do take the name it gives for the color - if (dCode == maincolor || dCode == edgecolor) { - g_LDColors[dCode]->zName = zName; - continue; - } - - // VALUE tag - if (!parseLDConfigTag (pars, "VALUE", zColor)) - continue; - - // EDGE tag - if (!parseLDConfigTag (pars, "EDGE", zEdge)) - continue; - - // Ensure that our colors are correct - QColor qColor (zColor.chars()), - qEdge (zEdge.chars()); - - if (!qColor.isValid () || !qEdge.isValid ()) - continue; - - // Parse alpha if given. - if (parseLDConfigTag (pars, "ALPHA", zValue)) - dAlpha = clamp<short> (atoi (zValue), 0, 255); - - color* col = new color; - col->zName = zName; - col->qColor = qColor; - col->qEdge = qEdge; - col->zColorString = zColor; - col->qColor.setAlpha (dAlpha); - - g_LDColors[dCode] = col; - } - - fclose (fp); -} \ No newline at end of file
--- a/colors.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef COLORS_H -#define COLORS_H - -#include <qcolor.h> -#include "common.h" - -#define MAX_COLORS 512 - -class color { -public: - str zName, zColorString; - QColor qColor, qEdge; - - short index (); -}; - -typedef struct { - const short dIndex; - const char* sName, *sColor; - const float fAlpha; -} TemporaryColorMeta; - -void initColors (); -void parseLDConfig (); -uchar luma (QColor& col); - -// Safely gets a color with the given number or null if no such color. -color* getColor (short dColorNum); - -#endif // COLORS_H \ No newline at end of file
--- a/common.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,183 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -// ============================================================================= -// This file is included one way or another in every source file of LDForge. -// Stuff defined and included here is universally included. - -#ifndef COMMON_H -#define COMMON_H - -#include <stdio.h> -#include <stdlib.h> -#include <assert.h> -#include <vector> -#include <stdint.h> -#include <stdarg.h> -#include "str.h" -#include "config.h" -#include "types.h" - -#define APPNAME "LDForge" - -#define VERSION_MAJOR 0 -#define VERSION_MAJOR_STR "0" -#define VERSION_MINOR 1 -#define VERSION_MINOR_STR "1" - -// ============--- -// #define RELEASE - -// Version string identifier -static const str versionString = fmt ("%d.%d", VERSION_MAJOR, VERSION_MINOR); - -#ifdef __GNUC__ -# define FORMAT_PRINTF(M,N) __attribute__ ((format (printf, M, N))) -# define ATTR(N) __attribute__ ((N)) -#else -# define FORMAT_PRINTF(M,N) -# define ATTR(N) -#endif // __GNUC__ - -#ifdef WIN32 -#define DIRSLASH "\\" -#else // WIN32 -#define DIRSLASH "/" -#endif // WIN32 - -#ifdef RELEASE -#define NDEBUG // remove asserts -#endif // RELEASE - -#ifdef null -#undef null -#endif // null - -// Null pointer -static const std::nullptr_t null = nullptr; - -// Main and edge color identifiers -static const short maincolor = 16; -static const short edgecolor = 24; - -static const bool yup = true; -static const bool nope = false; - -class ForgeWindow; -class LDObject; -class bbox; -class OpenFile; -class QApplication; - -// ============================================================================= -// Plural expression -#define PLURAL(n) ((n != 1) ? "s" : "") - -// ----------------------------------------------------------------------------- -// Shortcut for formatting -#define PERFORM_FORMAT(in, out) \ - va_list v; \ - va_start (v, in); \ - char* out = vdynformat (in, v, 256); \ - va_end (v); - -// ----------------------------------------------------------------------------- -// Shortcuts for stuffing vertices into printf-formatting. -#define FMT_VERTEX "(%.3f, %.3f, %.3f)" -#define FVERTEX(V) V.x, V.y, V.z - -// ----------------------------------------------------------------------------- -// Templated clamp -template<class T> static inline T clamp (T a, T min, T max) { - return (a > max) ? max : (a < min) ? min : a; -} - -// Templated minimum -template<class T> static inline T min (T a, T b) { - return (a < b) ? a : b; -} - -// Templated maximum -template<class T> static inline T max (T a, T b) { - return (a > b) ? a : b; -} - -// Templated absolute value -template<class T> static inline T abs (T a) { - return (a >= 0) ? a : -a; -} - -// Quick QString to const char* conversion -static inline const char* qchars (QString qstr) { - return qstr.toStdString ().c_str (); -} - -static const double pi = 3.14159265358979323846f; - -#ifdef IN_IDE_PARSER // KDevelop workaround -// Current function name -static const char* __func__ = ""; -#endif // IN_IDE_PARSER - -// ----------------------------------------------------------------------------- -enum LogType { - LOG_Normal, - LOG_Warning, - LOG_Error, - LOG_Dev, -}; - -// logf - universal access to the message log. Defined here so that I don't have -// to include gui.h here and recompile everything every time that file changes. -// logf is defined in main.cpp -void logf (const char* fmtstr, ...) FORMAT_PRINTF (1, 2); -void logf (LogType type, const char* fmtstr, ...) FORMAT_PRINTF (2, 3); -void warnf (const char* fmtstr, ...) FORMAT_PRINTF (1, 2); -void errf (const char* fmtstr, ...) FORMAT_PRINTF (1, 2); - -#ifndef RELEASE -void devf (const char* fmtstr, ...); -#else -# define devf(...) -#endif // RELEASE - -// ----------------------------------------------------------------------------- -// Vertex at (0, 0, 0) -extern const vertex g_origin; - -// ----------------------------------------------------------------------------- -// Pointer to the OpenFile which is currently being edited by the user. -extern OpenFile* g_curfile; - -// ----------------------------------------------------------------------------- -// Pointer to the bounding box. -extern bbox g_BBox; - -// ----------------------------------------------------------------------------- -// Vector of all currently opened files. -extern vector<OpenFile*> g_loadedFiles; - -// ----------------------------------------------------------------------------- -// Pointer to the main application. -extern const QApplication* g_app; - -// ----------------------------------------------------------------------------- -// Identity matrix -extern const matrix<3> g_identity; - -#endif // COMMON_H \ No newline at end of file
--- a/config.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,276 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <errno.h> -#include <time.h> -#include <QDir> -#include "common.h" -#include "str.h" -#include "config.h" - -std::vector<config*> g_pConfigPointers; - -// ============================================================================= -const char* g_WeekdayNames[7] = { - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", -}; - -// ============================================================================= -static const char* g_MonthNames[12] = { - "Januray", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November" - "December", -}; - -static const char* g_ConfigTypeNames[] = { - "None", - "Integer", - "String", - "Float", - "Boolean", - "Key sequence", -}; - -// ============================================================================= -// Load the configuration from file -bool config::load () { - printf ("config::load: loading configuration file.\n"); - - // Locale must be disabled for atof - setlocale (LC_NUMERIC, "C"); - - FILE* fp = fopen (filepath().chars(), "r"); - char linedata[MAX_INI_LINE]; - char* line; - size_t ln = 0; - - if (!fp) - return false; // can't open for reading - - // Read the values. - while (fgets (linedata, MAX_INI_LINE, fp)) { - ln++; - line = linedata; - - while (*line != 0 && (*line <= 32 || *line >= 127)) - line++; // Skip junk - - if (*line == '\0' || line[0] == '#') - continue; // Empty line or comment. - - // Find the equals sign. - char* equals = strchr (line, '='); - if (!equals) { - fprintf (stderr, "couldn't find `=` sign in entry `%s`\n", line); - continue; - } - - str entry = str (line).substr (0, equals - line); - - // Find the config entry for this. - config* cfg = null; - for (config* i : g_pConfigPointers) - if (entry == i->name) - cfg = i; - - if (!cfg) { - fprintf (stderr, "unknown config `%s`\n", entry.chars()); - continue; - } - - str valstring = str (line).substr (equals - line + 1, -1); - - // Trim the crap off the end - while (~valstring) { - char c = valstring[~valstring - 1]; - if (c <= 32 || c >= 127) - valstring -= 1; - else - break; - } - - switch (cfg->getType()) { - case CONFIG_int: - static_cast<intconfig*> (cfg)->value = atoi (valstring.chars()); - break; - - case CONFIG_str: - static_cast<strconfig*> (cfg)->value = valstring; - break; - - case CONFIG_float: - static_cast<floatconfig*> (cfg)->value = atof (valstring.chars()); - break; - - case CONFIG_bool: - { - bool& val = static_cast<boolconfig*> (cfg)->value; - - if (+valstring == "TRUE" || valstring == "1") - val = true; - else if (+valstring == "FALSE" || valstring == "0") - val = false; - break; - } - - case CONFIG_keyseq: - static_cast<keyseqconfig*> (cfg)->value = keyseq::fromString (valstring.chars ()); - break; - - default: - break; - } - } - - fclose (fp); - return true; -} - -// ============================================================================= -// Write a given formatted string to the given file stream -static size_t writef (FILE* fp, const char* fmt, ...) { - va_list va; - - va_start (va, fmt); - char* buf = vdynformat (fmt, va, 256); - va_end (va); - - size_t len = fwrite (buf, 1, strlen (buf), fp); - delete[] buf; - - return len; -} - -// ============================================================================= -// Save the configuration to disk -bool config::save () { - // The function will write floats, disable the locale now so that they - // are written properly. - setlocale (LC_NUMERIC, "C"); - - // If the directory doesn't exist, create it now. - if (QDir (dirpath ()).exists () == nope) { - fprintf (stderr, "Creating config path %s...\n", dirpath().chars()); - if (!QDir ().mkpath (dirpath().chars())) { - logf (LOG_Warning, "Failed to create the directory. Configuration cannot be saved!\n"); - return false; // Couldn't create directory - } - } - - FILE* fp = fopen (filepath().chars(), "w"); - printf ("writing cfg to %s\n", filepath().chars()); - - if (!fp) { - printf ("Couldn't open %s for writing\n", filepath().chars()); - return false; - } - - const time_t curtime = time (NULL); - const struct tm* timeinfo = localtime (&curtime); - const char* daysuffix = - (timeinfo->tm_mday % 10 == 1) ? "st" : - (timeinfo->tm_mday % 10 == 2) ? "nd" : - (timeinfo->tm_mday % 10 == 3) ? "rd" : "th"; - - writef (fp, "# Configuration file for " APPNAME "\n"); - writef (fp, "# Written on %s, %s %d%s %d %.2d:%.2d:%.2d\n", - g_WeekdayNames[timeinfo->tm_wday], g_MonthNames[timeinfo->tm_mon], - timeinfo->tm_mday, daysuffix, timeinfo->tm_year + 1900, - timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec); - - for (config* cfg : g_pConfigPointers) { - str valstring; - switch (cfg->getType()) { - case CONFIG_int: - valstring.format ("%d", static_cast<intconfig*> (cfg)->value); - break; - case CONFIG_str: - valstring = static_cast<strconfig*> (cfg)->value; - break; - case CONFIG_float: - valstring.format ("%f", static_cast<floatconfig*> (cfg)->value); - - // Trim any trailing zeros - if (valstring.first (".") != -1) { - while (valstring[~valstring - 1] == '0') - valstring -= 1; - - // But don't trim the only one out... - if (valstring[~valstring - 1] == '.') - valstring += '0'; - } - - break; - case CONFIG_bool: - valstring = (static_cast<boolconfig*> (cfg)->value) ? "true" : "false"; - break; - case CONFIG_keyseq: - valstring = static_cast<keyseqconfig*> (cfg)->value.toString (); - break; - default: - break; - } - - const char* sDefault = (cfg->getType() != CONFIG_keyseq) ? cfg->defaultstring : - static_cast<keyseqconfig*> (cfg)->defval.toString ().toUtf8 ().constData (); - - // Write the entry now. - writef (fp, "\n# [%s] default: %s\n", g_ConfigTypeNames[cfg->getType()], sDefault); - writef (fp, "%s=%s\n", cfg->name, valstring.chars()); - } - - fclose (fp); - return true; -} - -// ============================================================================= -void config::reset () { - for (size_t i = 0; i < NUM_CONFIG; i++) - g_pConfigPointers[i]->resetValue (); -} - -// ============================================================================= -str config::filepath () { - str path; - path.format ("%s%s.cfg", dirpath ().chars (), - str (APPNAME).tolower ().chars ()); - return path; -} - -// ============================================================================= -str config::dirpath () { - return fmt ("%s/.%s/", qchars (QDir::homePath ()), - str (APPNAME).tolower ().chars ()); -} \ No newline at end of file
--- a/config.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,227 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef CONFIG_H -#define CONFIG_H - -#include "common.h" -#include "str.h" - -// ============================================================================= -#include <QString> -#include <qkeysequence.h> - -#define MAX_INI_LINE 512 -#define NUM_CONFIG (g_pConfigPointers.size ()) - -#define cfg(T, NAME, DEFAULT) \ - T##config NAME (DEFAULT, #NAME, #T, #DEFAULT) - -#define extern_cfg(T, NAME) \ - extern T##config NAME - -// ============================================================================= -enum configtype_e { - CONFIG_none, - CONFIG_int, - CONFIG_str, - CONFIG_float, - CONFIG_bool, - CONFIG_keyseq, -}; - -// ========================================================= -class config { -public: - const char* name, *typestring, *defaultstring; - - virtual configtype_e getType () { - return CONFIG_none; - } - - virtual void resetValue () {} - - // ------------------------------------------ - static bool load (); - static bool save (); - static void reset (); - static str dirpath (); - static str filepath (); -}; - -extern std::vector<config*> g_pConfigPointers; - -// ============================================================================= -#define DEFINE_UNARY_OPERATOR(T, OP) \ - T operator OP () { \ - return (OP value); \ - } \ - -#define DEFINE_BINARY_OPERATOR(T, OP) \ - T operator OP (const T other) { \ - return (value OP other); \ - } \ - -#define DEFINE_ASSIGN_OPERATOR(T, OP) \ - T& operator OP (const T other) { \ - return (value OP other); \ - } \ - -#define DEFINE_COMPARE_OPERATOR(T, OP) \ - bool operator OP (const T other) { \ - return (value OP other); \ - } \ - -#define DEFINE_CAST_OPERATOR(T) \ - operator T () { \ - return (T) value; \ - } \ - -#define DEFINE_ALL_COMPARE_OPERATORS(T) \ - DEFINE_COMPARE_OPERATOR (T, ==) \ - DEFINE_COMPARE_OPERATOR (T, !=) \ - DEFINE_COMPARE_OPERATOR (T, >) \ - DEFINE_COMPARE_OPERATOR (T, <) \ - DEFINE_COMPARE_OPERATOR (T, >=) \ - DEFINE_COMPARE_OPERATOR (T, <=) \ - -#define DEFINE_INCREMENT_OPERATORS(T) \ - T operator++ () {return ++value;} \ - T operator++ (int) {return value++;} \ - T operator-- () {return --value;} \ - T operator-- (int) {return value--;} - -#define CONFIGTYPE(T) \ -class T##config : public config - -#define IMPLEMENT_CONFIG(T) \ - T value, defval; \ - \ - T##config (T _defval, const char* _name, const char* _typestring, \ - const char* _defaultstring) \ - { \ - value = defval = _defval; \ - name = _name; \ - typestring = _typestring; \ - defaultstring = _defaultstring; \ - g_pConfigPointers.push_back (this); \ - } \ - operator T () { \ - return value; \ - } \ - configtype_e getType () { \ - return CONFIG_##T; \ - } \ - virtual void resetValue () { \ - value = defval; \ - } - -// ============================================================================= -CONFIGTYPE (int) { -public: - IMPLEMENT_CONFIG (int) - - // Int-specific operators - DEFINE_ALL_COMPARE_OPERATORS (int) - DEFINE_INCREMENT_OPERATORS (int) - DEFINE_BINARY_OPERATOR (int, +) - DEFINE_BINARY_OPERATOR (int, -) - DEFINE_BINARY_OPERATOR (int, *) - DEFINE_BINARY_OPERATOR (int, /) - DEFINE_BINARY_OPERATOR (int, %) - DEFINE_BINARY_OPERATOR (int, ^) - DEFINE_BINARY_OPERATOR (int, |) - DEFINE_BINARY_OPERATOR (int, &) - DEFINE_BINARY_OPERATOR (int, >>) - DEFINE_BINARY_OPERATOR (int, <<) - DEFINE_UNARY_OPERATOR (int, !) - DEFINE_UNARY_OPERATOR (int, ~) - DEFINE_UNARY_OPERATOR (int, -) - DEFINE_UNARY_OPERATOR (int, +) - DEFINE_ASSIGN_OPERATOR (int, =) - DEFINE_ASSIGN_OPERATOR (int, +=) - DEFINE_ASSIGN_OPERATOR (int, -=) - DEFINE_ASSIGN_OPERATOR (int, *=) - DEFINE_ASSIGN_OPERATOR (int, /=) - DEFINE_ASSIGN_OPERATOR (int, %=) - DEFINE_ASSIGN_OPERATOR (int, >>=) - DEFINE_ASSIGN_OPERATOR (int, <<=) -}; - -// ============================================================================= -CONFIGTYPE (str) { -public: - IMPLEMENT_CONFIG (str) - - DEFINE_ALL_COMPARE_OPERATORS (str) - DEFINE_BINARY_OPERATOR (str, -) - DEFINE_BINARY_OPERATOR (str, *) - DEFINE_UNARY_OPERATOR (str, !) - DEFINE_ASSIGN_OPERATOR (str, =) - DEFINE_ASSIGN_OPERATOR (str, +=) - DEFINE_ASSIGN_OPERATOR (str, -=) - DEFINE_ASSIGN_OPERATOR (str, *=) - DEFINE_CAST_OPERATOR (char*) - - char operator[] (size_t n) { - return value[n]; - } - -#ifdef CONFIG_WITH_QT - operator QString () { - return QString (value.chars()); - } -#endif // CONFIG_WITH_QT -}; - -// ============================================================================= -CONFIGTYPE (float) { -public: - IMPLEMENT_CONFIG (float) - - DEFINE_ALL_COMPARE_OPERATORS (float) - DEFINE_INCREMENT_OPERATORS (float) - DEFINE_BINARY_OPERATOR (float, +) - DEFINE_BINARY_OPERATOR (float, -) - DEFINE_BINARY_OPERATOR (float, *) - DEFINE_UNARY_OPERATOR (float, !) - DEFINE_ASSIGN_OPERATOR (float, =) - DEFINE_ASSIGN_OPERATOR (float, +=) - DEFINE_ASSIGN_OPERATOR (float, -=) - DEFINE_ASSIGN_OPERATOR (float, *=) -}; - -// ============================================================================= -CONFIGTYPE (bool) { -public: - IMPLEMENT_CONFIG (bool) - DEFINE_ALL_COMPARE_OPERATORS (bool) - DEFINE_ASSIGN_OPERATOR (bool, =) -}; - -// ============================================================================= -typedef QKeySequence keyseq; - -CONFIGTYPE (keyseq) { -public: - IMPLEMENT_CONFIG (keyseq) - DEFINE_ALL_COMPARE_OPERATORS (keyseq) - DEFINE_ASSIGN_OPERATOR (keyseq, =) -}; - -#endif // CONFIG_H \ No newline at end of file
--- a/extprogs.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,461 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <qprocess.h> -#include <qtemporaryfile.h> -#include <qeventloop.h> -#include <qdialog.h> -#include <qdialogbuttonbox.h> -#include <qspinbox.h> -#include <qcheckbox.h> -#include <qcombobox.h> -#include "common.h" -#include "config.h" -#include "misc.h" -#include "extprogs.h" -#include "gui.h" -#include "file.h" -#include "radiobox.h" -#include "history.h" - -// ============================================================================= -cfg (str, prog_isecalc, ""); -cfg (str, prog_intersector, ""); -cfg (str, prog_coverer, ""); -cfg (str, prog_ytruder, ""); -cfg (str, prog_datheader, ""); -cfg (str, prog_rectifier, ""); - -const char* g_extProgNames[] = { - "Isecalc", - "Intersector", - "Coverer", - "Ytruder", - "Rectifier", - "DATHeader", -}; - -// ============================================================================= -static bool checkProgPath (str path, const extprog prog) { - if (~path) - return true; - - const char* name = g_extProgNames[prog]; - - critical (fmt ("Couldn't run %s as no path has " - "been defined for it. Use the configuration dialog's External Programs " - "tab to define a path for %s.", name, name)); - return false; -} - -// ============================================================================= -static void processError (const extprog prog, QProcess& proc) { - const char* name = g_extProgNames[prog]; - str errmsg; - - switch (proc.error ()) { - case QProcess::FailedToStart: - errmsg = fmt ("Failed to launch %s. Check that you have set the proper path " - "to %s and that you have the proper permissions to launch it.", name, name); - break; - - case QProcess::Crashed: - errmsg = fmt ("%s crashed.", name); - break; - - case QProcess::WriteError: - case QProcess::ReadError: - errmsg = fmt ("I/O error while interacting with %s.", name); - break; - - case QProcess::UnknownError: - errmsg = fmt ("Unknown error occurred while executing %s.", name); - break; - - case QProcess::Timedout: - errmsg = fmt ("%s timed out.", name); - break; - } - - critical (errmsg); -} - -// ============================================================================= -static bool mkTempFile (QTemporaryFile& tmp, str& fname) { - if (!tmp.open ()) - return false; - - fname = tmp.fileName (); - tmp.close (); - return true; -} - -// ============================================================================= -void writeObjects (std::vector<LDObject*>& objects, str fname) { - // Write the input file - FILE* fp = fopen (fname, "w"); - if (!fp) { - critical (fmt ("Couldn't open temporary file %s for writing.\n", fname.chars ())); - return; - } - - for (LDObject* obj : objects) { - str line = fmt ("%s\r\n", obj->getContents ().chars ()); - fwrite (line.chars(), 1, ~line, fp); - } - -#ifndef RELEASE - ushort idx = rand (); - printf ("%s -> debug_%u\n", fname.chars (), idx); - QFile::copy (fname.chars (), fmt ("debug_%u", idx)); -#endif // RELEASE - - fclose (fp); -} - -// ============================================================================= -void writeSelection (str fname) { - writeObjects (g_win->sel (), fname); -} - -// ============================================================================= -void writeColorGroup (const short colnum, str fname) { - std::vector<LDObject*> objects; - for (LDObject*& obj : g_curfile->m_objs) { - if (obj->isColored () == false || obj->dColor != colnum) - continue; - - objects.push_back (obj); - } - - writeObjects (objects, fname); -} - -// ============================================================================= -void runUtilityProcess (extprog prog, str path, QString argvstr) { - QTemporaryFile input, output; - str inputname, outputname; - QStringList argv = argvstr.split (" ", QString::SkipEmptyParts); - - printf ("cmdline: %s %s\n", path.chars (), qchars (argvstr)); - - if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname)) - return; - - QProcess proc; - - // Init stdin - FILE* stdinfp = fopen (inputname, "w"); - - // Begin! - proc.setStandardInputFile (inputname); - proc.start (path, argv); - - // Write an enter - one is expected - char enter[2] = "\n"; - enter[1] = '\0'; - fwrite (enter, 1, sizeof enter, stdinfp); - fflush (stdinfp); - - // Wait while it runs - proc.waitForFinished (); - -#ifndef RELASE - printf ("%s", qchars (QString (proc.readAllStandardOutput ()))); -#endif // RELEASE - - if (proc.exitStatus () == QProcess::CrashExit) { - processError (prog, proc); - return; - } -} - -// ======================================================================================================================================== -static void insertOutput (str fname, bool replace, vector<short> colorsToReplace) { -#ifndef RELEASE - QFile::copy (fname, "./debug_lastOutput"); -#endif // RELEASE - - // Read the output file - FILE* fp = fopen (fname, "r"); - if (!fp) { - critical (fmt ("Couldn't open temporary file %s for reading.\n", fname.chars ())); - return; - } - - ComboHistory* cmb = new ComboHistory ({}); - std::vector<LDObject*> objs = loadFileContents (fp, null), - copies; - std::vector<ulong> indices; - - // If we replace the objects, delete the selection now. - if (replace) - *cmb << g_win->deleteSelection (); - - for (const short colnum : colorsToReplace) - *cmb << g_win->deleteByColor (colnum); - - // Insert the new objects - g_win->sel ().clear (); - for (LDObject* obj : objs) { - if (!obj->isSchemantic ()) { - delete obj; - continue; - } - - ulong idx = g_curfile->addObject (obj); - indices.push_back (idx); - copies.push_back (obj->clone ()); - g_win->sel ().push_back (obj); - } - - if (indices.size() > 0) - *cmb << new AddHistory ({indices, copies}); - - if (cmb->paEntries.size () > 0) - History::addEntry (cmb); - else - delete cmb; - - fclose (fp); - g_win->refresh (); -} - -QDialogButtonBox* makeButtonBox (QDialog& dlg) { - QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - QWidget::connect (bbx_buttons, SIGNAL (accepted ()), &dlg, SLOT (accept ())); - QWidget::connect (bbx_buttons, SIGNAL (rejected ()), &dlg, SLOT (reject ())); - return bbx_buttons; -} - -// ============================================================================= -// Interface for Ytruder -MAKE_ACTION (ytruder, "Ytruder", "ytruder", "Extrude selected lines to a given plane", KEY (F4)) { - setlocale (LC_ALL, "C"); - - if (!checkProgPath (prog_ytruder, Ytruder)) - return; - - QDialog dlg; - - RadioBox* rb_mode = new RadioBox ("Extrusion mode", {"Distance", "Symmetry", "Projection", "Radial"}, 0, Qt::Horizontal); - RadioBox* rb_axis = new RadioBox ("Axis", {"X", "Y", "Z"}, 0, Qt::Horizontal); - LabeledWidget<QDoubleSpinBox>* dsb_depth = new LabeledWidget<QDoubleSpinBox> ("Plane depth"), - *dsb_condAngle = new LabeledWidget<QDoubleSpinBox> ("Conditional line threshold"); - - rb_axis->setValue (Y); - dsb_depth->w ()->setMinimum (-10000.0); - dsb_depth->w ()->setMaximum (10000.0); - dsb_depth->w ()->setDecimals (3); - dsb_condAngle->w ()->setValue (30.0f); - - QVBoxLayout* layout = new QVBoxLayout (&dlg); - layout->addWidget (rb_mode); - layout->addWidget (rb_axis); - layout->addWidget (dsb_depth); - layout->addWidget (dsb_condAngle); - layout->addWidget (makeButtonBox (dlg)); - - dlg.setWindowIcon (getIcon ("extrude")); - - if (!dlg.exec ()) - return; - - // Read the user's choices - const enum modetype { Distance, Symmetry, Projection, Radial } mode = (modetype) rb_mode->value (); - const Axis axis = (Axis) rb_axis->value (); - const double depth = dsb_depth->w ()->value (), - condAngle = dsb_condAngle->w ()->value (); - - QTemporaryFile indat, outdat; - str inDATName, outDATName; - - // Make temp files for the input and output files - if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) - return; - - // Compose the command-line arguments - str argv = fmt ("%s %s %f -a %f %s %s", - (axis == X) ? "-x" : (axis == Y) ? "-y" : "-z", - (mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r", - depth, condAngle, inDATName.chars (), outDATName.chars ()); - - writeSelection (inDATName); - runUtilityProcess (Ytruder, prog_ytruder, argv); - insertOutput (outDATName, false, {}); -} - -// ======================================================================================================================================== -// Rectifier interface -MAKE_ACTION (rectifier, "Rectifier", "rectifier", "Optimizes quads into rect primitives.", KEY (F8)) { - setlocale (LC_ALL, "C"); - - if (!checkProgPath (prog_rectifier, Rectifier)) - return; - - QDialog dlg; - QCheckBox* cb_condense = new QCheckBox ("Condense triangles to quads"), - *cb_subst = new QCheckBox ("Substitute rect primitives"), - *cb_condlineCheck = new QCheckBox ("Don't replace quads with adj. condlines"), - *cb_colorize = new QCheckBox ("Colorize resulting objects"); - LabeledWidget<QDoubleSpinBox>* dsb_coplthres = new LabeledWidget<QDoubleSpinBox> ("Coplanarity threshold"); - - dsb_coplthres->w ()->setMinimum (0.0f); - dsb_coplthres->w ()->setMaximum (360.0f); - dsb_coplthres->w ()->setDecimals (3); - dsb_coplthres->w ()->setValue (0.95f); - cb_condense->setChecked (true); - cb_subst->setChecked (true); - - QVBoxLayout* layout = new QVBoxLayout (&dlg); - layout->addWidget (cb_condense); - layout->addWidget (cb_subst); - layout->addWidget (cb_condlineCheck); - layout->addWidget (cb_colorize); - layout->addWidget (dsb_coplthres); - layout->addWidget (makeButtonBox (dlg)); - - if (!dlg.exec ()) - return; - - const bool condense = cb_condense->isChecked (), - subst = cb_subst->isChecked (), - condlineCheck = cb_condlineCheck->isChecked (), - colorize = cb_colorize->isChecked (); - const double coplthres = dsb_coplthres->w ()->value (); - - QTemporaryFile indat, outdat; - str inDATName, outDATName; - - // Make temp files for the input and output files - if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) - return; - - // Compose arguments - str argv = fmt ("%s %s %s %s -t %f %s %s", - (condense == false) ? "-q" : "", - (subst == false) ? "-r" : "", - (condlineCheck) ? "-a" : "", - (colorize) ? "-c" : "", - coplthres, inDATName.chars (), outDATName.chars ()); - - writeSelection (inDATName); - runUtilityProcess (Rectifier, prog_rectifier, argv); - insertOutput (outDATName, true, {}); -} - -// ======================================================================================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ======================================================================================================================================= -// Intersector interface -MAKE_ACTION (intersector, "Intersector", "intersector", "Perform clipping between two input groups.", KEY (F5)) { - setlocale (LC_ALL, "C"); - - if (!checkProgPath (prog_intersector, Intersector)) - return; - - QDialog dlg; - - LabeledWidget<QComboBox>* cmb_incol = new LabeledWidget<QComboBox> ("Input", new QComboBox), - *cmb_cutcol = new LabeledWidget<QComboBox> ("Cutter", new QComboBox); - QCheckBox* cb_colorize = new QCheckBox ("Colorize output"), - *cb_nocondense = new QCheckBox ("No condensing"), - *cb_repeatInverse = new QCheckBox ("Repeat inverse"), - *cb_edges = new QCheckBox ("Add edges"); - LabeledWidget<QDoubleSpinBox>* dsb_prescale = new LabeledWidget<QDoubleSpinBox> ("Prescaling factor"); - - cb_repeatInverse->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."); - cb_edges->setWhatsThis ("Makes " APPNAME " try run Isecalc to create edgelines for the intersection."); - - makeColorSelector (cmb_incol->w ()); - makeColorSelector (cmb_cutcol->w ()); - dsb_prescale->w ()->setMinimum (0.0f); - dsb_prescale->w ()->setMaximum (10000.0f); - dsb_prescale->w ()->setSingleStep (0.01f); - dsb_prescale->w ()->setValue (1.0f); - - QVBoxLayout* layout = new QVBoxLayout (&dlg); - layout->addWidget (cmb_incol); - layout->addWidget (cmb_cutcol); - - QHBoxLayout* cblayout = new QHBoxLayout; - cblayout->addWidget (cb_colorize); - cblayout->addWidget (cb_nocondense); - - QHBoxLayout* cb2layout = new QHBoxLayout; - cb2layout->addWidget (cb_repeatInverse); - cb2layout->addWidget (cb_edges); - - layout->addLayout (cblayout); - layout->addLayout (cb2layout); - layout->addWidget (dsb_prescale); - layout->addWidget (makeButtonBox (dlg)); - -exec: - if (!dlg.exec ()) - return; - - const short inCol = cmb_incol->w ()->itemData (cmb_incol->w ()->currentIndex ()).toInt (), - cutCol = cmb_cutcol->w ()->itemData (cmb_cutcol->w ()->currentIndex ()).toInt (); - const bool repeatInverse = cb_repeatInverse->isChecked (); - - if (inCol == cutCol) { - critical ("Cannot use the same color group for both input and cutter!"); - goto exec; - } - - // Five temporary files! - // indat = input group file - // cutdat = cutter group file - // outdat = primary output - // outdat2 = inverse output - // edgesdat = edges output (isecalc) - QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat; - str inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName; - - if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) || - !mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) || - !mkTempFile (edgesdat, edgesDATName)) - { - return; - } - - str parms = fmt ("%s %s -s %f", - (cb_colorize->isChecked ()) ? "-c" : "", - (cb_nocondense->isChecked ()) ? "-t" : "", - dsb_prescale->w ()->value ()); - - str argv_normal = fmt ("%s %s %s %s", parms.chars (), inDATName.chars (), cutDATName.chars (), outDATName.chars ()); - str argv_inverse = fmt ("%s %s %s %s", parms.chars (), cutDATName.chars (), inDATName.chars (), outDAT2Name.chars ()); - - writeColorGroup (inCol, inDATName); - writeColorGroup (cutCol, cutDATName); - runUtilityProcess (Intersector, prog_intersector, argv_normal); - insertOutput (outDATName, false, {inCol}); - - if (repeatInverse) { - runUtilityProcess (Intersector, prog_intersector, argv_inverse); - insertOutput (outDAT2Name, false, {cutCol}); - } - - if (cb_edges->isChecked ()) { - runUtilityProcess (Isecalc, prog_isecalc, fmt ("%s %s %s", inDATName.chars (), cutDATName.chars (), edgesDATName.chars ())); - insertOutput (edgesDATName, false, {}); - } -} \ No newline at end of file
--- a/extprogs.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef EXTPROGS_H -#define EXTPROGS_H - -#include <qobject.h> - -enum extprog { - Isecalc, - Intersector, - Coverer, - Ytruder, - Rectifier, - DATHeader -}; - -#endif // EXTPROGS_H \ No newline at end of file
--- a/file.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,757 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <vector> -#include <stdio.h> -#include <qmessagebox.h> -#include <QDir> -#include "common.h" -#include "config.h" -#include "file.h" -#include "misc.h" -#include "bbox.h" -#include "gui.h" -#include "history.h" -#include "zz_ldrawPathDialog.h" - -cfg (str, io_ldpath, ""); -cfg (str, io_recentfiles, ""); - -// ============================================================================= -namespace LDPaths { - static str pathError; - - struct { - str LDConfigPath; - str partsPath, primsPath; - } pathInfo; - - void initPaths () { - if (!tryConfigure (io_ldpath)) { - LDrawPathDialog dlg (false); - - if (!dlg.exec ()) - exit (0); - - io_ldpath = dlg.path (); - } - } - - bool tryConfigure (str path) { - QDir dir; - - if (!dir.cd (path)) { - pathError = "Directory does not exist."; - return false; - } - - QStringList mustHave = {"LDConfig.ldr", "parts", "p"}; - QStringList contents = dir.entryList (mustHave); - - if (contents.size () != mustHave.size ()) { - pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/."; - return false; - } - - pathInfo.partsPath = fmt ("%s" DIRSLASH "parts", path.chars ()); - pathInfo.LDConfigPath = fmt ("%s" DIRSLASH "LDConfig.ldr", path.chars ()); - pathInfo.primsPath = fmt ("%s" DIRSLASH "p", path.chars ()); - - return true; - } - - // Accessors - str getError () { return pathError; } - str ldconfig () { return pathInfo.LDConfigPath; } - str prims () { return pathInfo.primsPath; } - str parts () { return pathInfo.partsPath; } -} - -// ============================================================================= -OpenFile::OpenFile () { - m_implicit = true; - savePos = -1; -} - -// ============================================================================= -OpenFile::~OpenFile () { - // Clear everything from the model - for (LDObject* obj : m_objs) - delete obj; - - // Clear the cache as well - for (LDObject* obj : m_objCache) - delete obj; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -OpenFile* findLoadedFile (str zName) { - for (OpenFile* file : g_loadedFiles) - if (file->m_filename == zName) - return file; - - return null; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -FILE* openLDrawFile (str path, bool bSubDirectories) { - str zTruePath = path; - -#ifndef WIN32 - zTruePath.replace ("\\", "/"); -#endif // WIN32 - - FILE* fp = fopen (path.chars (), "r"); - str zFilePath; - - if (fp != null) - return fp; - - if (~io_ldpath.value) { - // Try with just the LDraw path first - zFilePath = fmt ("%s" DIRSLASH "%s", - io_ldpath.value.chars(), zTruePath.chars()); - logf ("Trying %s\n", zFilePath.chars()); - - fp = fopen (zFilePath, "r"); - if (fp != null) - return fp; - - if (bSubDirectories) { - char const* saSubdirectories[] = { - "parts", - "p", - }; - - for (char const* sSubdir : saSubdirectories) { - zFilePath = fmt ("%s" DIRSLASH "%s" DIRSLASH "%s", - io_ldpath.value.chars(), sSubdir, zTruePath.chars()); - printf ("try %s\n", zFilePath.chars()); - - fp = fopen (zFilePath.chars (), "r"); - - if (fp) - return fp; - } - } - } - - return null; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -std::vector<LDObject*> loadFileContents (FILE* fp, ulong* numWarnings) { - char line[1024]; - vector<str> lines; - vector<LDObject*> objs; - ulong lnum = 0; - - if (numWarnings) - *numWarnings = 0; - - while (fgets (line, sizeof line, fp)) { - // Trim the trailing newline - str data = line; - while (data[~data - 1] == '\n' || data[~data - 1] == '\r') - data -= 1; - - LDObject* obj = parseLine (data); - assert (obj != null); - - // Check for parse errors and warn about tthem - if (obj->getType() == LDObject::Gibberish) { - logf (LOG_Warning, "Couldn't parse line #%lu: %s\n", - lnum, static_cast<LDGibberish*> (obj)->zReason.chars()); - - logf (LOG_Warning, "- Line was: %s\n", data.chars()); - - if (numWarnings) - (*numWarnings)++; - } - - objs.push_back (obj); - lnum++; - } - - return objs; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -OpenFile* openDATFile (str path, bool search) { - logf ("Opening %s...\n", path.chars()); - - // Convert the file name to lowercase since some parts contain uppercase - // file names. I'll assume here that the library will always use lowercase - // file names for the actual parts.. - FILE* fp; - if (search) - fp = openLDrawFile (-path, true); - else - fp = fopen (path, "r"); - - if (!fp) { - logf (LOG_Error, "Couldn't open %s: %s\n", path.chars (), strerror (errno)); - return null; - } - - OpenFile* load = new OpenFile; - load->m_filename = path; - ulong numWarnings; - std::vector<LDObject*> objs = loadFileContents (fp, &numWarnings); - - for (LDObject* obj : objs) - load->m_objs.push_back (obj); - - fclose (fp); - g_loadedFiles.push_back (load); - - logf ("File %s parsed successfully (%lu warning%s).\n", - path.chars(), numWarnings, PLURAL (numWarnings)); - - return load; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -bool OpenFile::safeToClose () { - setlocale (LC_ALL, "C"); - - // If we have unsaved changes, warn and give the option of saving. - if (!m_implicit && History::pos () != savePos) { - switch (QMessageBox::question (g_win, "Unsaved Changes", - fmt ("There are unsaved changes to %s. Should it be saved?", m_filename.chars ()), - (QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel), QMessageBox::Cancel)) - { - case QMessageBox::Yes: - if (!save ()) { - str errormsg = fmt ("Failed to save %s: %s\nDo you still want to close?", - m_filename.chars (), strerror (lastError)); - - if (QMessageBox::critical (g_win, "Save Failure", errormsg, - (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::No) - { - return false; - } - } - - break; - - case QMessageBox::Cancel: - return false; - - default: - break; - } - } - - return true; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void closeAll () { - if (!g_loadedFiles.size()) - return; - - // Remove all loaded files and the objects they contain - for (OpenFile* file : g_loadedFiles) - delete file; - - // Clear the array - g_loadedFiles.clear(); - g_curfile = NULL; - - g_win->refresh (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void newFile () { - // Create a new anonymous file and set it to our current - closeAll (); - - OpenFile* f = new OpenFile; - f->m_filename = ""; - g_loadedFiles.push_back (f); - g_curfile = f; - - g_BBox.calculate(); - g_win->refresh (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void addRecentFile (str zPath) { - long lPos = io_recentfiles.value.first (zPath); - - // If this file already is in the list, pop it out. - if (lPos != -1) { - if (~io_recentfiles.value == ~zPath) - return; // only recent file - do nothing - - // Pop it out. - str zFront = io_recentfiles.value.substr (0, lPos); - str zBack = io_recentfiles.value.substr (lPos + ~zPath + 1, -1); - io_recentfiles.value = zFront + zBack; - } - - // If there's too many recent files, drop one out. - while (io_recentfiles.value.count ('@') > 3) - io_recentfiles.value = io_recentfiles.value.substr (io_recentfiles.value.first ("@") + 1, -1); - - // Add the file - if (~io_recentfiles.value > 0) - io_recentfiles.value += "@"; - - io_recentfiles += zPath; - - config::save (); - g_win->updateRecentFilesMenu (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void openMainFile (str path) { - closeAll (); - - OpenFile* file = openDATFile (path, false); - - if (!file) { - // Tell the user loading failed. - setlocale (LC_ALL, "C"); - critical (fmt ("Failed to open %s: %s", path.chars(), strerror (errno))); - return; - } - - file->m_implicit = false; - g_curfile = file; - - // Recalculate the bounding box - g_BBox.calculate(); - - // Rebuild the object tree view now. - g_win->refresh (); - g_win->setTitle (); - g_win->R ()->resetAngles (); - - History::clear (); - - // Add it to the recent files list. - addRecentFile (path); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -bool OpenFile::save (str path) { - if (!~path) - path = m_filename; - - FILE* fp = fopen (path, "w"); - - if (!fp) { - lastError = errno; - return false; - } - - // Write all entries now - for (LDObject* obj : m_objs) { - // LDraw requires lines to have DOS line endings - str zLine = fmt ("%s\r\n", obj->getContents ().chars ()); - - fwrite (zLine.chars(), 1, ~zLine, fp); - } - - fclose (fp); - - // We have successfully saved, update the save position now. - savePos = History::pos (); - - g_win->setTitle (); - - return true; -} - -#define CHECK_TOKEN_COUNT(N) \ - if (tokens.size() != N) \ - return new LDGibberish (zLine, "Bad amount of tokens"); - -#define CHECK_TOKEN_NUMBERS(MIN,MAX) \ - for (ushort i = MIN; i <= MAX; ++i) \ - if (!isNumber (tokens[i])) \ - return new LDGibberish (zLine, fmt ("Token #%u was `%s`, expected a number", \ - (i + 1), tokens[i].chars())); - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -static vertex parseVertex (vector<str>& s, const ushort n) { - // Disable the locale while parsing the line or atof's behavior changes - // between locales (i.e. fails to read decimals properly). That is - // quite undesired... - setlocale (LC_NUMERIC, "C"); - - vertex v; - for (const Axis ax : g_Axes) - v[ax] = atof (s[n + ax]); - - return v; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -LDObject* parseLine (str zLine) { - vector<str> tokens = zLine.split (" ", true); - - if (!tokens.size ()) { - // Line was empty, or only consisted of whitespace - return new LDEmpty; - } - - if (~tokens[0] != 1) - return new LDGibberish (zLine, "Illogical line code"); - - const char c = tokens[0][0]; - switch (c - '0') { - case 0: - { - // Comment - str comm; - for (uint i = 1; i < tokens.size(); ++i) { - comm += tokens[i]; - - if (i != tokens.size() - 1) - comm += ' '; - } - - // Handle BFC statements - if (tokens.size() > 2 && tokens[1] == "BFC") { - for (short i = 0; i < LDBFC::NumStatements; ++i) - if (comm == fmt ("BFC %s", LDBFC::statements [i])) - return new LDBFC ((LDBFC::Type) i); - - // MLCAD is notorious for stuffing these statements in parts it - // creates. The above block only handles valid statements, so we - // need to handle MLCAD-style invertnext separately. - if (comm == "BFC CERTIFY INVERTNEXT") - return new LDBFC (LDBFC::InvertNext); - } - - if (tokens.size() > 2 && tokens[1] == "!LDFORGE") { - // Handle LDForge-specific types, they're embedded into comments - - if (tokens[2] == "VERTEX") { - // Vertex (0 !LDFORGE VERTEX) - CHECK_TOKEN_COUNT (7) - CHECK_TOKEN_NUMBERS (3, 6) - - LDVertex* obj = new LDVertex; - obj->dColor = atol (tokens[3]); - - for (const Axis ax : g_Axes) - obj->vPosition[ax] = atof (tokens[4 + ax]); // 4 - 6 - - return obj; - } - - if (tokens[2] == "RADIAL") { - CHECK_TOKEN_COUNT (20) - CHECK_TOKEN_NUMBERS (4, 19) - - LDRadial::Type eType = LDRadial::NumTypes; - - for (int i = 0; i < LDRadial::NumTypes; ++i) { - if (str (LDRadial::radialTypeName ((LDRadial::Type) i)).toupper ().strip (' ') == tokens[3]) { - eType = (LDRadial::Type) i; - break; - } - } - - if (eType == LDRadial::NumTypes) - return new LDGibberish (zLine, fmt ("Unknown radial type %s", tokens[3].chars ())); - - LDRadial* obj = new LDRadial; - - obj->eRadialType = eType; // 3 - obj->dColor = atol (tokens[4]); // 4 - obj->dSegments = atol (tokens[5]); // 5 - obj->dDivisions = atol (tokens[6]); // 6 - obj->dRingNum = atol (tokens[7]); // 7 - - obj->vPosition = parseVertex (tokens, 8); // 8 - 10 - - for (short i = 0; i < 9; ++i) - obj->mMatrix[i] = atof (tokens[i + 11]); // 11 - 19 - - return obj; - } - } - - LDComment* obj = new LDComment; - obj->text = comm; - return obj; - } - - case 1: - { - // Subfile - CHECK_TOKEN_COUNT (15) - CHECK_TOKEN_NUMBERS (1, 13) - - // Try open the file - OpenFile* pFile = loadSubfile (tokens[14]); - - // If we cannot open the file, mark it an error - if (!pFile) - return new LDGibberish (zLine, "Could not open referred file"); - - LDSubfile* obj = new LDSubfile; - obj->dColor = atol (tokens[1]); - obj->vPosition = parseVertex (tokens, 2); // 2 - 4 - - for (short i = 0; i < 9; ++i) - obj->mMatrix[i] = atof (tokens[i + 5]); // 5 - 13 - - obj->zFileName = tokens[14]; - obj->pFile = pFile; - return obj; - } - - case 2: - { - CHECK_TOKEN_COUNT (8) - CHECK_TOKEN_NUMBERS (1, 7) - - // Line - LDLine* obj = new LDLine; - obj->dColor = atol (tokens[1]); - for (short i = 0; i < 2; ++i) - obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 7 - return obj; - } - - case 3: - { - CHECK_TOKEN_COUNT (11) - CHECK_TOKEN_NUMBERS (1, 10) - - // Triangle - LDTriangle* obj = new LDTriangle; - obj->dColor = atol (tokens[1]); - - for (short i = 0; i < 3; ++i) - obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 10 - - return obj; - } - - case 4: - { - CHECK_TOKEN_COUNT (14) - CHECK_TOKEN_NUMBERS (1, 13) - - // Quadrilateral - LDQuad* obj = new LDQuad; - obj->dColor = atol (tokens[1]); - - for (short i = 0; i < 4; ++i) - obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 13 - - return obj; - } - - case 5: - { - CHECK_TOKEN_COUNT (14) - CHECK_TOKEN_NUMBERS (1, 13) - - // Conditional line - LDCondLine* obj = new LDCondLine; - obj->dColor = atol (tokens[1]); - - for (short i = 0; i < 4; ++i) - obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 13 - - return obj; - } - - default: // Strange line we couldn't parse - return new LDGibberish (zLine, "Unknown line code number"); - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -OpenFile* loadSubfile (str zFile) { - // Try open the file - OpenFile* pFile = findLoadedFile (zFile); - if (!pFile) - pFile = openDATFile (zFile, true); - - return pFile; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void reloadAllSubfiles () { - if (!g_curfile) - return; - - // First, close all but the current open file. - for (OpenFile* file : g_loadedFiles) - if (file != g_curfile) - delete file; - - g_loadedFiles.clear (); - g_loadedFiles.push_back (g_curfile); - - // Go through all objects in the current file and reload the subfiles - for (LDObject* obj : g_curfile->m_objs) { - if (obj->getType() == LDObject::Subfile) { - // Note: ref->pFile is invalid right now since all subfiles were closed. - LDSubfile* ref = static_cast<LDSubfile*> (obj); - OpenFile* pFile = loadSubfile (ref->zFileName); - - if (pFile) - ref->pFile = pFile; - else { - // Couldn't load the file, mark it an error - ref->replace (new LDGibberish (ref->getContents (), "Could not open referred file")); - } - } - - // Reparse gibberish files. It could be that they are invalid because - // the file could not be opened. Circumstances may be different now. - if (obj->getType() == LDObject::Gibberish) - obj->replace (parseLine (static_cast<LDGibberish*> (obj)->zContents)); - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -ulong OpenFile::addObject (LDObject* obj) { - m_objs.push_back (obj); - - if (this == g_curfile) - g_BBox.calcObject (obj); - - return m_objs.size() - 1; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void OpenFile::insertObj (const ulong pos, LDObject* obj) { - m_objs.insert (m_objs.begin () + pos, obj); - - if (this == g_curfile) - g_BBox.calcObject (obj); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void OpenFile::forgetObject (LDObject* obj) { - // Find the index for the given object - ulong ulIndex; - for (ulIndex = 0; ulIndex < (ulong)m_objs.size(); ++ulIndex) - if (m_objs[ulIndex] == obj) - break; // found it - - if (ulIndex >= m_objs.size ()) - return; // was not found - - // Erase it from memory - m_objs.erase (m_objs.begin() + ulIndex); - - // Update the bounding box - if (this == g_curfile) - g_BBox.calculate (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -std::vector<partListEntry> g_PartList; - -void initPartList () { - logf ("%s: initializing parts.lst\n", __func__); - - FILE* fp = openLDrawFile ("parts.lst", false); - - if (!fp) - return; - - char sLine[1024]; - while (fgets (sLine, sizeof sLine, fp)) { - // Locate the first whitespace - char* cpWhite = strstr (sLine, " "); - - char sName[65]; - size_t uLength = (cpWhite - sLine); - - if (uLength >= 64) - continue; // too long - - strncpy (sName, sLine, uLength); - sName[uLength] = '\0'; - - // Surf through the whitespace sea! - while (*cpWhite == ' ') - cpWhite++; - - // Get the end point - char* cpEnd = strstr (sLine, "\r"); - - if (cpEnd == null) { - // must not be DOS-formatted - cpEnd = strstr (sLine, "\n"); - } - - assert (cpEnd != null); - - // Make the file title now - char sTitle[81]; - uLength = (cpEnd - cpWhite); - strncpy (sTitle, cpWhite, uLength); - sTitle[uLength] = '\0'; - - // Add it to the array. - partListEntry entry; - strcpy (entry.sName, sName); - strcpy (entry.sTitle, sTitle); - g_PartList.push_back (entry); - } -} \ No newline at end of file
--- a/file.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef FILE_H -#define FILE_H - -#include "common.h" -#include "ldtypes.h" -#include "str.h" - -namespace LDPaths { - void initPaths (); - bool tryConfigure (str path); - - str ldconfig (); - str prims (); - str parts (); - str getError (); -} - -// ============================================================================= -// OpenFile -// -// The OpenFile class stores a file opened in LDForge either as a editable file -// for the user or for subfile caching. Its methods handle file input and output. -// ============================================================================= -class OpenFile { -public: - str m_filename, m_title; - vector<LDObject*> m_objs; - vector<LDObject*> m_objCache; // Cache of this file's contents, if desired - - int lastError; - - // Is this file implicit? Implicit files are opened automatically for - // caching purposes and are hidden from the user. - bool m_implicit; - - OpenFile (); - ~OpenFile (); - - // Saves this file to disk. - bool save (str zPath = ""); - - // Perform safety checks. Do this before closing any files! - bool safeToClose (); - - // Adds an object to this file at the end of the file. - ulong addObject (LDObject* obj); - - // Deletes the given object from the object chain. - void forgetObject (LDObject* obj); - - // At what point was this file last saved? - long savePos; - - LDObject* object (ulong pos) const { - return m_objs[pos]; - } - - void insertObj (const ulong pos, LDObject* obj); -}; - -// Close all current loaded files and start off blank. -void newFile (); - -// Opens the given file as the main file. Everything is closed first. -void openMainFile (str zPath); - -// Finds an OpenFile by name or null if not open -OpenFile* findLoadedFile (str name); - -// Opens the given file and parses the LDraw code within. Returns a pointer -// to the opened file or null on error. -OpenFile* openDATFile (str path, bool search); - -// Opens the given file and returns a pointer to it, potentially looking in /parts and /p -FILE* openLDrawFile (str path, bool bSubDirectories); - -// Close all open files, whether user-opened or subfile caches. -void closeAll (); - -// Parses a string line containing an LDraw object and returns the object parsed. -LDObject* parseLine (str zLine); - -// Retrieves the pointer to - or loads - the given subfile. -OpenFile* loadSubfile (str zFile); - -// Re-caches all subfiles. -void reloadAllSubfiles (); - -typedef struct { - char sName[65], sTitle[81]; -} partListEntry; - -// Init and parse parts.lst -void initPartList (); - -std::vector< LDObject* > loadFileContents (FILE* fp, ulong* numWarnings); - -extern vector<OpenFile*> g_loadedFiles; -extern vector<partListEntry> g_PartList; - -#endif // FILE_H \ No newline at end of file
--- a/gldraw.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1214 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QtGui> -#include <QGLWidget> -#include <GL/glu.h> -#include "common.h" -#include "config.h" -#include "file.h" -#include "gldraw.h" -#include "bbox.h" -#include "colors.h" -#include "gui.h" -#include "misc.h" -#include "history.h" - -static double g_objOffset[3]; - -static short g_pulseTick = 0; -static const short g_numPulseTicks = 8; -static const short g_pulseInterval = 65; - -static const struct staticCameraMeta { - const char glrotate[3]; - const Axis axisX, axisY; - const bool negX, negY; -} g_staticCameras[6] = { - { { 1, 0, 0 }, X, Z, false, false }, - { { 0, 0, 0 }, X, Y, false, true }, - { { 0, 1, 0 }, Z, Y, true, true }, - { { -1, 0, 0 }, X, Z, false, true }, - { { 0, 0, 0 }, X, Y, true, true }, - { { 0, -1, 0 }, Z, Y, false, true }, -}; - -cfg (str, gl_bgcolor, "#CCCCD9"); -cfg (str, gl_maincolor, "#707078"); -cfg (float, gl_maincolor_alpha, 1.0); -cfg (int, gl_linethickness, 2); -cfg (bool, gl_colorbfc, true); -cfg (bool, gl_selflash, false); -cfg (int, gl_camera, GLRenderer::Free); -cfg (bool, gl_blackedges, true); -cfg (bool, gl_axes, false); - -// CameraIcon::img is a heap-allocated QPixmap because otherwise it gets -// initialized before program gets to main() and constructs a QApplication -// and Qt doesn't like that. -struct CameraIcon { - QPixmap* img; - QRect srcRect, destRect, selRect; - GLRenderer::Camera cam; -} g_CameraIcons[7]; - -const char* g_CameraNames[7] = { "Top", "Front", "Left", "Bottom", "Back", "Right", "Free" }; - -const GLRenderer::Camera g_Cameras[7] = { - GLRenderer::Top, - GLRenderer::Front, - GLRenderer::Left, - GLRenderer::Bottom, - GLRenderer::Back, - GLRenderer::Right, - GLRenderer::Free -}; - -const struct GLAxis { - const QColor col; - const vertex vert; -} g_GLAxes[3] = { - { QColor (255, 0, 0), vertex (10000, 0, 0) }, - { QColor (128, 192, 0), vertex (0, 10000, 0) }, - { QColor (0, 160, 192), vertex (0, 0, 10000) }, -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) { - resetAngles (); - m_picking = m_rangepick = false; - m_camera = (GLRenderer::Camera) gl_camera.value; - m_drawToolTip = false; - m_planeDraw = false; - - m_pulseTimer = new QTimer (this); - connect (m_pulseTimer, SIGNAL (timeout ()), this, SLOT (slot_timerUpdate ())); - - m_toolTipTimer = new QTimer (this); - m_toolTipTimer->setSingleShot (true); - connect (m_toolTipTimer, SIGNAL (timeout ()), this, SLOT (slot_toolTipTimer ())); - - m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); - m_thinBorderPen = m_thickBorderPen; - m_thinBorderPen.setWidth (1); - - // Init camera icons - for (const GLRenderer::Camera cam : g_Cameras) { - str iconname; - iconname.format ("camera-%s", str (g_CameraNames[cam]).tolower ().chars ()); - - CameraIcon* info = &g_CameraIcons[cam]; - info->img = new QPixmap (getIcon (iconname)); - info->cam = cam; - } - - calcCameraIconRects (); -} - -// ============================================================================= -GLRenderer::~GLRenderer() { - for (CameraIcon& info : g_CameraIcons) - delete info.img; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::calcCameraIconRects () { - ushort i = 0; - - for (CameraIcon& info : g_CameraIcons) { - const long x1 = (m_width - (info.cam != Free ? 48 : 16)) + ((i % 3) * 16) - 1, - y1 = ((i / 3) * 16) + 1; - - info.srcRect = QRect (0, 0, 16, 16); - info.destRect = QRect (x1, y1, 16, 16); - info.selRect = QRect (info.destRect.x (), info.destRect.y (), - info.destRect.width () + 1, info.destRect.height () + 1); - ++i; - } -} - -// ============================================================================= -void GLRenderer::resetAngles () { - m_rotX = 30.0f; - m_rotY = 325.f; - m_panX = m_panY = m_rotZ = 0.0f; - - // Set the default zoom based on the bounding box - m_zoom = g_BBox.size () * 6; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::initializeGL () { - setBackground (); - - glEnable (GL_POLYGON_OFFSET_FILL); - glPolygonOffset (1.0f, 1.0f); - - glEnable (GL_DEPTH_TEST); - glShadeModel (GL_SMOOTH); - glEnable (GL_MULTISAMPLE); - - glEnable (GL_BLEND); - glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glEnable (GL_LINE_SMOOTH); - glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); - - glLineWidth (gl_linethickness); - - setAutoFillBackground (false); - setMouseTracking (true); - setFocusPolicy (Qt::WheelFocus); - compileObjects (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -QColor GLRenderer::getMainColor () { - QColor col (gl_maincolor.value.chars()); - - if (!col.isValid ()) - return QColor (0, 0, 0); - - col.setAlpha (gl_maincolor_alpha * 255.f); - return col; -} - -// ----------------------------------------------------------------------------- -void GLRenderer::setBackground () { - QColor col (gl_bgcolor.value.chars()); - - if (!col.isValid ()) - return; - - m_darkbg = luma (col) < 80; - - col.setAlpha (255); - qglClearColor (col); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -static vector<short> g_daWarnedColors; -void GLRenderer::setObjectColor (LDObject* obj) { - QColor qcol; - - if (!obj->isColored()) - return; - - if (m_picking) { - // Make the color by the object's index color if we're picking, so we can - // make the index from the color we get from the picking results. - long i = obj->getIndex (g_curfile); - - // If we couldn't find the index, this object must not be from this file, - // therefore it must be an object inlined from a subfile reference or - // decomposed from a radial. Find the top level parent object and use - // its index. - if (i == -1) - i = obj->topLevelParent ()->getIndex (g_curfile); - - // We should have the index now. - assert (i != -1); - - // Calculate a color based from this index. This method caters for - // 16777216 objects. I don't think that'll be exceeded anytime soon. :) - // ATM biggest is 53588.dat with 12600 lines. - double r = (i / (256 * 256)) % 256, - g = (i / 256) % 256, - b = i % 256; - - qglColor (QColor (r, g, b, 255)); - return; - } - -#if 0 - if (gl_colorbfc && - obj->getType () != LDObject::Line && - obj->getType () != LDObject::CondLine) - { - if (bBackSide) - glColor4f (0.9f, 0.0f, 0.0f, 1.0f); - else - glColor4f (0.0f, 0.8f, 0.0f, 1.0f); - return; - } -#endif - - if (obj->dColor == maincolor) - qcol = getMainColor (); - else { - color* col = getColor (obj->dColor); - qcol = col->qColor; - } - - if (obj->dColor == edgecolor) { - qcol = Qt::black; - color* col; - - if (!gl_blackedges && obj->parent != null && (col = getColor (obj->parent->dColor)) != null) - qcol = col->qEdge; - } - - if (qcol.isValid () == false) { - // The color was unknown. Use main color to make the object at least - // not appear pitch-black. - if (obj->dColor != edgecolor) - qcol = getMainColor (); - - // Warn about the unknown colors, but only once. - for (short i : g_daWarnedColors) - if (obj->dColor == i) - return; - - printf ("%s: Unknown color %d!\n", __func__, obj->dColor); - g_daWarnedColors.push_back (obj->dColor); - return; - } - - long r = qcol.red (), - g = qcol.green (), - b = qcol.blue (), - a = qcol.alpha (); - - // If it's selected, brighten it up, also pulse flash it if desired. - if (g_win->isSelected (obj)) { - short tick, numTicks; - - if (gl_selflash) { - tick = (g_pulseTick < (g_numPulseTicks / 2)) ? g_pulseTick : (g_numPulseTicks - g_pulseTick); - numTicks = g_numPulseTicks; - } else { - tick = 2; - numTicks = 5; - } - - const long add = ((tick * 128) / numTicks); - r = min (r + add, 255l); - g = min (g + add, 255l); - b = min (b + add, 255l); - - // a = 255; - } - - glColor4f ( - ((double) r) / 255.0f, - ((double) g) / 255.0f, - ((double) b) / 255.0f, - ((double) a) / 255.0f); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::refresh () { - update (); - swapBuffers (); -} - -// ============================================================================= -void GLRenderer::hardRefresh () { - compileObjects (); - refresh (); - - glLineWidth (gl_linethickness); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::resizeGL (int w, int h) { - m_width = w; - m_height = h; - - calcCameraIconRects (); - - 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 () const { - if (g_curfile == null) - return; - - glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable (GL_DEPTH_TEST); - - if (m_camera != GLRenderer::Free) { - glMatrixMode (GL_PROJECTION); - glPushMatrix (); - - glLoadIdentity (); - glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f); - glTranslatef (m_panX, m_panY, 0.0f); - glRotatef (90.0f, g_staticCameras[m_camera].glrotate[0], - g_staticCameras[m_camera].glrotate[1], - g_staticCameras[m_camera].glrotate[2]); - - // Back camera needs to be handled differently - if (m_camera == GLRenderer::Back) { - glRotatef (180.0f, 1.0f, 0.0f, 0.0f); - glRotatef (180.0f, 0.0f, 0.0f, 1.0f); - } - } else { - glMatrixMode (GL_MODELVIEW); - glPushMatrix (); - glLoadIdentity (); - - glTranslatef (0.0f, 0.0f, -2.0f); - glTranslatef (m_panX, m_panY, -m_zoom); - glRotatef (m_rotX, 1.0f, 0.0f, 0.0f); - glRotatef (m_rotY, 0.0f, 1.0f, 0.0f); - glRotatef (m_rotZ, 0.0f, 0.0f, 1.0f); - } - - for (LDObject* obj : g_curfile->m_objs) { - if (obj->hidden ()) - continue; // Don't draw hidden objects - - glCallList (m_picking == false ? obj->uGLList : obj->uGLPickList); - } - - if (gl_axes && !m_picking) - glCallList (m_axeslist); - - glPopMatrix (); - glMatrixMode (GL_MODELVIEW); -} - -// ============================================================================= -vertex GLRenderer::coord_2to3 (const QPoint& pos2d, const bool snap) const { - vertex pos3d; - const staticCameraMeta* cam = &g_staticCameras[m_camera]; - const Axis axisX = cam->axisX; - const Axis axisY = cam->axisY; - const short negXFac = cam->negX ? -1 : 1, - negYFac = cam->negY ? -1 : 1; - - // Calculate cx and cy - these are the LDraw unit coords the cursor is at. - double cx = (-m_virtWidth + ((2 * pos2d.x () * m_virtWidth) / m_width) - m_panX) - (negXFac * g_objOffset[axisX]); - double cy = (m_virtHeight - ((2 * pos2d.y () * m_virtHeight) / m_height) - m_panY) - (negYFac * g_objOffset[axisY]); - - if (snap) { - cx = Grid::snap (cx, (Grid::Config) axisX); - cy = Grid::snap (cy, (Grid::Config) axisY); - } - - cx *= negXFac; - cy *= negYFac; - - pos3d = g_origin; - pos3d[axisX] = cx; - pos3d[axisY] = cy; - return pos3d; -} - -// ============================================================================= -QPoint GLRenderer::coord_3to2 (const vertex& pos3d) const { - /* - cx = (-m_virtWidth + ((2 * pos2d.x () * m_virtWidth) / m_width) - m_panX) - (negXFac * g_objOffset[axisX]); - - cx = (-vw + ((2 * x * vw) / w) - panx) - (neg * ofs) - cx + (neg * ofs) = (-vw + ((2 * x * vw) / w) - panx) - cx + (neg * ofs) = ((2 * x * vw) / w) - vw - panx - (cx + (neg * ofs)) + vw + panx = (2 * x * vw) / w - ((cx + (neg * ofs)) + vw + panx) * w = 2 * vw * x - - x = (((cx + (neg * ofs)) + vw + panx) * w) / (2 * vw) - */ - - QPoint pos2d; - const staticCameraMeta* cam = &g_staticCameras[m_camera]; - const Axis axisX = cam->axisX; - const Axis axisY = cam->axisY; - const short negXFac = cam->negX ? -1 : 1, - negYFac = cam->negY ? -1 : 1; - - short x1 = (((pos3d[axisX] + (negXFac * g_objOffset[axisX])) + - m_virtWidth + m_panX) * m_width) / (2 * m_virtWidth); - short y1 = -(((pos3d[axisY] + (negYFac * g_objOffset[axisY])) - - m_virtHeight + m_panY) * m_height) / (2 * m_virtHeight); - - x1 *= negXFac; - y1 *= negYFac; - - pos2d = QPoint (x1, y1); - return pos2d; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::paintEvent (QPaintEvent* ev) { - Q_UNUSED (ev) - m_virtWidth = m_zoom; - m_virtHeight = (m_height * m_virtWidth) / m_width; - drawGLScene (); - - QPainter paint (this); - QFontMetrics metrics = QFontMetrics (QFont ()); - paint.setRenderHint (QPainter::Antialiasing); - - m_hoverpos = g_origin; - - if (m_camera != Free) { - // Calculate 3d position of the cursor - m_hoverpos = coord_2to3 (m_pos, true); - - // Paint the coordinates onto the screen. - str text; - text.format ("X: %s, Y: %s, Z: %s", ftoa (m_hoverpos[X]).chars (), - ftoa (m_hoverpos[Y]).chars (), ftoa (m_hoverpos[Z]).chars ()); - - QFontMetrics metrics = QFontMetrics (font ()); - QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); - - paint.drawText (m_width - textSize.width (), m_height - 16, textSize.width (), - textSize.height (), Qt::AlignCenter, text); - - // If we're plane drawing, draw the vertices onto the screen. - if (m_planeDraw) { - ushort numverts = m_planeDrawVerts.size () + 1; - const short blipsize = 8; - - if (numverts > 0) { - QPoint* poly = new QPoint[numverts]; - - uchar i = 0; - for (vertex& vert : m_planeDrawVerts) { - poly[i] = coord_3to2 (vert); - ++i; - } - - // Draw the cursor vertex as the last one in the list. - if (numverts < 5) - poly[i] = coord_3to2 (m_hoverpos); - else - numverts = 4; - - paint.setPen (m_thinBorderPen); - paint.setBrush (QColor (128, 192, 0)); - - // Draw vertex blips - for (ushort i = 0; i < numverts; ++i) { - QPoint& blip = poly[i]; - paint.drawEllipse (blip.x () - blipsize / 2, blip.y () - blipsize / 2, - blipsize, blipsize); - } - - paint.setPen (m_thickBorderPen); - paint.setBrush (QColor (128, 192, 0, 128)); - paint.drawPolygon (poly, numverts); - - delete[] poly; - } - } - } - - // Camera icons - if (!m_picking && m_planeDraw == false) { - // Draw a background for the selected camera - paint.setPen (m_thinBorderPen); - paint.setBrush (QBrush (QColor (0, 128, 160, 128))); - paint.drawRect (g_CameraIcons[camera ()].selRect); - - // Draw the actual icons - for (CameraIcon& info : g_CameraIcons) - paint.drawPixmap (info.destRect, *info.img, info.srcRect); - - // Draw a label for the current camera in the top left corner - { - const ushort margin = 4; - - str label; - label.format ("%s Camera", g_CameraNames[camera ()]); - paint.setBrush (Qt::black); - paint.drawText (QPoint (margin, margin + metrics.ascent ()), label); - } - - // Tool tips - if (m_drawToolTip) { - if (g_CameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false) - m_drawToolTip = false; - else { - QPen bord = m_thinBorderPen; - bord.setBrush (Qt::black); - - const ushort margin = 2; - ushort x0 = m_pos.x (), - y0 = m_pos.y (); - - str label; - label.format ("%s Camera", g_CameraNames[m_toolTipCamera]); - - const ushort textWidth = metrics.width (label), - textHeight = metrics.height (), - fullWidth = textWidth + (2 * margin), - fullHeight = textHeight + (2 * margin); - - QRect area (m_pos.x (), m_pos.y (), fullWidth, fullHeight); - - if (x0 + fullWidth > m_width) - x0 -= fullWidth; - - if (y0 + fullHeight > m_height) - y0 -= fullHeight; - - paint.setBrush (QColor (0, 128, 255, 208)); - paint.setPen (bord); - paint.drawRect (x0, y0, fullWidth, fullHeight); - - paint.setBrush (Qt::black); - paint.drawText (QPoint (x0 + margin, y0 + margin + metrics.ascent ()), label); - } - } - } - - // If we're range-picking, draw a rectangle encompassing the selection area. - if (m_rangepick && !m_picking) { - const short x0 = m_rangeStart.x (), - y0 = m_rangeStart.y (), - x1 = m_pos.x (), - y1 = m_pos.y (); - - QRect rect (x0, y0, x1 - x0, y1 - y0); - QColor fillColor = (m_addpick ? "#80FF00" : "#00CCFF"); - fillColor.setAlphaF (0.2f); - - paint.setPen (m_thickBorderPen); - paint.setBrush (QBrush (fillColor)); - paint.drawRect (rect); - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::compileObjects () { - if (g_BBox.empty () == false) { - g_objOffset[X] = -(g_BBox.v0 ()[X] + g_BBox.v1 ()[X]) / 2; - g_objOffset[Y] = -(g_BBox.v0 ()[Y] + g_BBox.v1 ()[Y]) / 2; - g_objOffset[Z] = -(g_BBox.v0 ()[Z] + g_BBox.v1 ()[Z]) / 2; - } else { - // use a default bbox if we need - g_objOffset[X] = g_objOffset[Y] = g_objOffset[Z] = 0; - } - - if (!g_curfile) { - printf ("renderer: no files loaded, cannot compile anything\n"); - return; - } - - for (LDObject* obj : g_curfile->m_objs) { - GLuint* upaLists[2] = { - &obj->uGLList, - &obj->uGLPickList - }; - - for (GLuint* upMemberList : upaLists) { - GLuint uList = glGenLists (1); - glNewList (uList, GL_COMPILE); - - m_picking = (upMemberList == &obj->uGLPickList); - compileOneObject (obj); - m_picking = false; - - glEndList (); - *upMemberList = uList; - } - } - - // Compile axes - m_axeslist = glGenLists (1); - glNewList (m_axeslist, GL_COMPILE); - glBegin (GL_LINES); - for (const GLAxis& ax : g_GLAxes) { - qglColor (ax.col); - compileVertex (ax.vert); - compileVertex (-ax.vert); - } - glEnd (); - glEndList (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype) { - glBegin (gltype); - - const short numverts = (obj->getType () != LDObject::CondLine) ? obj->vertices () : 2; - - for (short i = 0; i < numverts; ++i) - compileVertex (obj->vaCoords[i]); - - glEnd (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::compileOneObject (LDObject* obj) { - setObjectColor (obj); - - switch (obj->getType ()) { - case LDObject::Line: - compileSubObject (obj, GL_LINES); - break; - - case LDObject::CondLine: - glLineStipple (1, 0x6666); - glEnable (GL_LINE_STIPPLE); - - compileSubObject (obj, GL_LINES); - - glDisable (GL_LINE_STIPPLE); - break; - - case LDObject::Triangle: - compileSubObject (obj, GL_TRIANGLES); - break; - - case LDObject::Quad: - compileSubObject (obj, GL_QUADS); - break; - - case LDObject::Subfile: - { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - vector<LDObject*> objs = ref->inlineContents (true, true); - - for (LDObject* obj : objs) { - compileOneObject (obj); - delete obj; - } - } - break; - - case LDObject::Radial: - { - LDRadial* pRadial = static_cast<LDRadial*> (obj); - std::vector<LDObject*> objs = pRadial->decompose (true); - - for (LDObject* obj : objs) { - compileOneObject (obj); - delete obj; - } - } - break; - -#if 0 - TODO: find a proper way to draw vertices without having them be affected by zoom. - case LDObject::Vertex: - { - LDVertex* pVert = static_cast<LDVertex*> (obj); - LDTriangle* pPoly; - vertex* vPos = &(pVert->vPosition); - const double fPolyScale = max (fZoom, 1.0); - -#define BIPYRAMID_COORD(N) ((((i + N) % 4) >= 2 ? 1 : -1) * 0.3f * fPolyScale) - - for (int i = 0; i < 8; ++i) { - pPoly = new LDTriangle; - pPoly->vaCoords[0] = {vPos->x, vPos->y + ((i >= 4 ? 1 : -1) * 0.4f * fPolyScale), vPos->z}; - pPoly->vaCoords[1] = { - vPos->x + BIPYRAMID_COORD (0), - vPos->y, - vPos->z + BIPYRAMID_COORD (1) - }; - - pPoly->vaCoords[2] = { - vPos->x + BIPYRAMID_COORD (1), - vPos->y, - vPos->z + BIPYRAMID_COORD (2) - }; - - pPoly->dColor = pVert->dColor; - compileOneObject (pPoly); - delete pPoly; - } - } - break; -#endif // 0 - - default: - break; - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::compileVertex (const vertex& vrt) { - glVertex3d ( - (vrt[X] + g_objOffset[0]), - -(vrt[Y] + g_objOffset[1]), - -(vrt[Z] + g_objOffset[2])); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::clampAngle (double& angle) { - 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) && !(ev->buttons() & Qt::LeftButton); - const bool wasRight = (m_lastButtons & Qt::RightButton) && !(ev->buttons() & Qt::RightButton); - - if (wasLeft) { - if (m_planeDraw) { - // If we picked an already-existing vertex, stop drawing - for (vertex& vert : m_planeDrawVerts) { - if (vert == m_hoverpos) { - endPlaneDraw (true); - return; - } - } - - // Also, if have 4 verts, also stop drawing. - if (m_planeDrawVerts.size () >= 4) - endPlaneDraw (true); - - m_planeDrawVerts.push_back (m_hoverpos); - - update (); - return; - } else { - if (!m_rangepick) - m_addpick = (m_keymods & Qt::ControlModifier); - - if (m_totalmove < 10 || m_rangepick) - pick (ev->x (), ev->y ()); - } - - m_rangepick = false; - m_totalmove = 0; - return; - } - - if (wasRight && m_planeDraw) { - if (m_planeDrawVerts.size () > 0) { - // Remove the last vertex - m_planeDrawVerts.erase (m_planeDrawVerts.end () - 1); - } else { - endPlaneDraw (false); - return; - } - - update (); - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::mousePressEvent (QMouseEvent* ev) { - if (ev->buttons () & Qt::LeftButton) - m_totalmove = 0; - - if (ev->modifiers () & Qt::ShiftModifier) { - m_rangepick = true; - m_rangeStart.setX (ev->x ()); - m_rangeStart.setY (ev->y ()); - - m_addpick = (m_keymods & Qt::ControlModifier); - } - - m_lastButtons = ev->buttons (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::mouseMoveEvent (QMouseEvent* ev) { - int dx = ev->x () - m_pos.x (); - int dy = ev->y () - m_pos.y (); - m_totalmove += abs (dx) + abs (dy); - - if (ev->buttons () & Qt::LeftButton && !m_rangepick) { - m_rotX = m_rotX + (dy); - m_rotY = m_rotY + (dx); - - clampAngle (m_rotX); - clampAngle (m_rotY); - } - - if (ev->buttons () & Qt::MidButton) { - m_panX += 0.03f * dx * (m_zoom / 7.5f); - m_panY -= 0.03f * dy * (m_zoom / 7.5f); - } - - // Start the tool tip timer - if (!m_drawToolTip) - m_toolTipTimer->start (1000); - - m_pos = ev->pos (); - update (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::keyPressEvent (QKeyEvent* ev) { - m_keymods = ev->modifiers (); -} - -void GLRenderer::keyReleaseEvent (QKeyEvent* ev) { - m_keymods = ev->modifiers (); -} - -// ============================================================================= -void GLRenderer::wheelEvent (QWheelEvent* ev) { - m_zoom *= (ev->delta () < 0) ? 1.2f : (1.0f / 1.2f); - m_zoom = clamp (m_zoom, 0.01, 10000.0); - - update (); - ev->accept (); -} - -// ============================================================================= -void GLRenderer::leaveEvent (QEvent* ev) { - Q_UNUSED (ev); - m_drawToolTip = false; - m_toolTipTimer->stop (); - update (); -} - -// ============================================================================= -void GLRenderer::contextMenuEvent (QContextMenuEvent* ev) { - g_win->spawnContextMenu (ev->globalPos ()); -} - -// ============================================================================= -void GLRenderer::setCamera (const GLRenderer::Camera cam) { - m_camera = cam; - gl_camera = (int) cam; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::updateSelFlash () { - if (gl_selflash && g_win->sel ().size() > 0) { - m_pulseTimer->start (g_pulseInterval); - g_pulseTick = 0; - } else - m_pulseTimer->stop (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::pick (uint mouseX, uint mouseY) { - // Check if we selected a camera icon - if (!m_rangepick) { - QPoint pos (mouseX, mouseY); - - for (CameraIcon& info : g_CameraIcons) { - if (info.destRect.contains (pos)) { - setCamera (info.cam); - update (); - return; - } - } - } - - GLint viewport[4]; - LDObject* removedObject = null; - - // Clear the selection if we do not wish to add to it. - if (!m_addpick) { - std::vector<LDObject*> oldsel = g_win->sel (); - g_win->sel ().clear (); - - // Recompile the prior selection to remove the highlight color - for (LDObject* obj : oldsel) - recompileObject (obj); - } - - m_picking = true; - - // Paint the picking scene - glDisable (GL_DITHER); - glClearColor (1.0f, 1.0f, 1.0f, 1.0f); - drawGLScene (); - - glGetIntegerv (GL_VIEWPORT, viewport); - - short x0 = mouseX, - y0 = mouseY; - short x1, y1; - - // Determine how big an area to read - with range picking, we pick by - // the area given, with single pixel picking, we use an 1 x 1 area. - if (m_rangepick) { - x1 = m_rangeStart.x (); - y1 = m_rangeStart.y (); - } else { - x1 = x0 + 1; - y1 = y0 + 1; - } - - // x0 and y0 must be less than x1 and y1, respectively. - if (x0 > x1) - dataswap (x0, x1); - - if (y0 > y1) - dataswap (y0, y1); - - // Clamp the values to ensure they're within bounds - x0 = max<short> (0, x0); - y0 = max<short> (0, y0); - x1 = min<short> (x1, m_width); - y1 = min<short> (y1, m_height); - - const short areawidth = (x1 - x0); - const short areaheight = (y1 - y0); - const long numpixels = areawidth * areaheight; - - // Allocate space for the pixel data. - uchar* const pixeldata = new uchar[4 * numpixels]; - uchar* pixelptr = &pixeldata[0]; - - assert (viewport[3] == m_height); - - // Read pixels from the color buffer. - glReadPixels (x0, viewport[3] - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata); - - // Go through each pixel read and add them to the selection. - for (long i = 0; i < numpixels; ++i) { - uint32 idx = - (*(pixelptr) * 0x10000) + - (*(pixelptr + 1) * 0x00100) + - (*(pixelptr + 2) * 0x00001); - pixelptr += 4; - - if (idx == 0xFFFFFF) - continue; // White is background; skip - - LDObject* obj = g_curfile->object (idx); - - // If this is an additive single pick and the object is currently selected, - // we remove it from selection instead. - if (!m_rangepick && m_addpick) { - bool removed = false; - - for (ulong i = 0; i < g_win->sel ().size(); ++i) { - if (g_win->sel ()[i] == obj) { - g_win->sel ().erase (g_win->sel ().begin () + i); - removedObject = obj; - removed = true; - } - } - - if (removed) - break; - } - - g_win->sel ().push_back (obj); - } - - delete[] pixeldata; - - // Remove duplicate entries. For this to be effective, the vector must be - // sorted first. - std::vector<LDObject*>& sel = g_win->sel (); - std::sort (sel.begin(), sel.end ()); - std::vector<LDObject*>::iterator pos = std::unique (sel.begin (), sel.end ()); - sel.resize (std::distance (sel.begin (), pos)); - - // Update everything now. - g_win->buildObjList (); - - m_picking = false; - m_rangepick = false; - glEnable (GL_DITHER); - - setBackground (); - updateSelFlash (); - - for (LDObject* obj : g_win->sel ()) - recompileObject (obj); - - if (removedObject != null) - recompileObject (removedObject); - - drawGLScene (); - swapBuffers (); - update (); -} - -// ============================================================================= -void GLRenderer::beginPlaneDraw () { - if (m_camera == Free) - return; // Cannot draw with the free camera - - m_planeDraw = true; - - // Disable the context menu - we need the right mouse button - // for removing vertices. - setContextMenuPolicy (Qt::NoContextMenu); - - // Use the crosshair cursor when plane drawing. - setCursor (Qt::CrossCursor); - - // Clear the selection when beginning to draw onto a plane. - // FIXME: make the selection clearing stuff in ::pick a method and use it - // here! This code doesn't update the GL lists. - g_win->sel ().clear (); - g_win->updateSelection (); - update (); -} - -// ============================================================================= -void GLRenderer::endPlaneDraw (bool accept) { - // If we accepted, clean the selection and create the object - if (accept) { - vector<vertex>& verts = m_planeDrawVerts; - LDObject* obj = null; - - switch (verts.size ()) { - case 1: - { - // 1 vertex - add a vertex object - obj = new LDVertex; - static_cast<LDVertex*> (obj)->vPosition = verts[0]; - obj->dColor = maincolor; - } - break; - - case 2: - { - // 2 verts - make a line - obj = new LDLine; - obj->dColor = edgecolor; - for (ushort i = 0; i < 2; ++i) - obj->vaCoords[i] = verts[i]; - } - break; - - case 3: - case 4: - { - obj = (verts.size () == 3) ? - static_cast<LDObject*> (new LDTriangle) : - static_cast<LDObject*> (new LDQuad); - - obj->dColor = maincolor; - for (ushort i = 0; i < obj->vertices (); ++i) - obj->vaCoords[i] = verts[i]; - } - break; - } - - if (obj) { - g_curfile->addObject (obj); - recompileObject (obj); - g_win->refresh (); - - History::addEntry (new AddHistory ({(ulong) obj->getIndex (g_curfile)}, {obj->clone ()})); - } - } - - m_planeDraw = false; - m_planeDrawVerts.clear (); - - unsetCursor (); - - // Restore the context menu - setContextMenuPolicy (Qt::DefaultContextMenu); - - update (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void GLRenderer::recompileObject (LDObject* obj) { - // Replace the old list with the new one. - GLuint uList = glGenLists (1); - glNewList (uList, GL_COMPILE); - - compileOneObject (obj); - - glEndList (); - obj->uGLList = uList; -} - -// ============================================================================= -uchar* GLRenderer::screencap (ushort& w, ushort& 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); - - // Restore the background - setBackground (); - - return cap; -} - -// ============================================================================= -void GLRenderer::slot_timerUpdate () { - ++g_pulseTick %= g_numPulseTicks; - - for (LDObject* obj : g_win->sel ()) - recompileObject (obj); - - update (); -} - -// ============================================================================= -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 : g_CameraIcons) { - if (icon.destRect.contains (m_pos)) { - m_toolTipCamera = icon.cam; - m_drawToolTip = true; - update (); - break; - } - } -} \ No newline at end of file
--- a/gldraw.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef GLDRAW_H -#define GLDRAW_H - -#include <QGLWidget> -#include <qtimer.h> -#include "common.h" -#include "ldtypes.h" - -// ============================================================================= -// GLRenderer -// -// The main renderer object, draws the brick on the screen, manages the camera -// and selection picking. The instance of GLRenderer is accessible as -// g_win->R () -// ============================================================================= -class GLRenderer : public QGLWidget { - Q_OBJECT - -public: - enum Camera { - Top, - Front, - Left, - Bottom, - Back, - Right, - Free - }; - - GLRenderer (QWidget* parent = null); - ~GLRenderer (); - - void hardRefresh (); - void compileObjects (); - void setBackground (); - void pick (uint mouseX, uint mouseY); - QColor getMainColor (); - void recompileObject (LDObject* obj); - void refresh (); - void updateSelFlash (); - void resetAngles (); - uchar* screencap (ushort& w, ushort& h); - void setCamera (const GLRenderer::Camera cam); - void beginPlaneDraw (); - void endPlaneDraw (bool accept); - - GLRenderer::Camera camera () { return m_camera; } - bool picking () { return m_picking; } - void setZoom (const double zoom) { m_zoom = zoom; } - double zoom () const { return m_zoom; } - -protected: - void initializeGL (); - void resizeGL (int w, int h); - - void mousePressEvent (QMouseEvent* ev); - void mouseMoveEvent (QMouseEvent* ev); - void mouseReleaseEvent (QMouseEvent* ev); - void keyPressEvent (QKeyEvent* ev); - void keyReleaseEvent (QKeyEvent* ev); - void wheelEvent (QWheelEvent* ev); - void paintEvent (QPaintEvent* ev); - void leaveEvent (QEvent* ev); - void contextMenuEvent (QContextMenuEvent* ev); - -private: - QTimer* m_pulseTimer, *m_toolTipTimer; - Qt::MouseButtons m_lastButtons; - Qt::KeyboardModifiers m_keymods; - ulong m_totalmove; - vertex m_hoverpos; - double m_virtWidth, m_virtHeight, m_rotX, m_rotY, m_rotZ, m_panX, m_panY, m_zoom; - bool m_darkbg, m_picking, m_rangepick, m_addpick, m_drawToolTip, m_screencap, m_planeDraw; - QPoint m_pos, m_rangeStart; - QPen m_thinBorderPen, m_thickBorderPen; - Camera m_camera, m_toolTipCamera; - uint m_axeslist; - ushort m_width, m_height; - std::vector<vertex> m_planeDrawVerts; - - void compileOneObject (LDObject* obj); - void compileSubObject (LDObject* obj, const GLenum gltype); - void compileVertex (const vertex& vrt); - void clampAngle (double& angle); - void setObjectColor (LDObject* obj); - void drawGLScene () const; - void calcCameraIconRects (); - - vertex coord_2to3 (const QPoint& pos2d, const bool snap) const; - QPoint coord_3to2 (const vertex& pos3d) const; - -private slots: - void slot_timerUpdate (); - void slot_toolTipTimer (); -}; - -#endif // GLDRAW_H \ No newline at end of file
--- a/gui.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1025 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <qgridlayout.h> -#include <qmessagebox.h> -#include <qevent.h> -#include <qmenubar.h> -#include <qstatusbar.h> -#include <qsplitter.h> -#include <qlistwidget.h> -#include <qtoolbutton.h> -#include <qcombobox.h> -#include <qcoreapplication.h> -#include "common.h" -#include "gldraw.h" -#include "gui.h" -#include "file.h" -#include "config.h" -#include "misc.h" -#include "colors.h" -#include "history.h" -#include "config.h" - -vector<actionmeta> g_ActionMeta; - -static bool g_bSelectionLocked = false; - -cfg (bool, lv_colorize, true); -cfg (int, gui_toolbar_iconsize, 24); -cfg (str, gui_colortoolbar, "16:24:|:0:1:2:3:4:5:6:7"); -extern_cfg (str, io_recentfiles); -extern_cfg (bool, gl_axes); - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -ForgeWindow::ForgeWindow () { - g_win = this; - m_renderer = new GLRenderer; - - m_objList = new ObjectList; - m_objList->setSelectionMode (QListWidget::ExtendedSelection); - m_objList->setAlternatingRowColors (true); - connect (m_objList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_selectionChanged ())); - - m_splitter = new QSplitter; - m_splitter->addWidget (m_renderer); - m_splitter->addWidget (m_objList); - setCentralWidget (m_splitter); - - m_colorMeta = parseQuickColorMeta (); - - createMenuActions (); - createMenus (); - createToolbars (); - - slot_selectionChanged (); - - setStatusBar (new QStatusBar); - setWindowIcon (getIcon ("ldforge")); - setTitle (); - setMinimumSize (320, 200); - resize (800, 600); - - connect (QCoreApplication::instance (), SIGNAL (aboutToQuit ()), this, SLOT (slot_lastSecondCleanup ())); -} - -// ============================================================================= -void ForgeWindow::slot_lastSecondCleanup () { - m_renderer->setParent (null); - delete m_renderer; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::createMenuActions () { - // Create the actions based on stored meta. - for (actionmeta meta : g_ActionMeta) { - QAction*& qAct = *meta.qAct; - qAct = new QAction (getIcon (meta.sIconName), meta.sDisplayName, this); - qAct->setStatusTip (meta.sDescription); - qAct->setShortcut (*meta.conf); - - connect (qAct, SIGNAL (triggered ()), this, SLOT (slot_action ())); - } - - // Grid actions and axes are checkable - findAction ("gridCoarse")->setCheckable (true); - findAction ("gridMedium")->setCheckable (true); - findAction ("gridFine")->setCheckable (true); - - findAction ("axes")->setCheckable (true); - findAction ("axes")->setChecked (gl_axes); - - // things not implemented yet - findAction ("help")->setEnabled (false); - - History::updateActions (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -QMenu* g_CurrentMenu; - -QMenu* ForgeWindow::initMenu (const char* name) { - return g_CurrentMenu = menuBar ()->addMenu (tr (name)); -} - -void ForgeWindow::addMenuAction (const char* name) { - g_CurrentMenu->addAction (findAction (name)); -} - - -// ============================================================================= -void ForgeWindow::createMenus () { - m_recentFilesMenu = new QMenu (tr ("Open &Recent")); - m_recentFilesMenu->setIcon (getIcon ("open-recent")); - updateRecentFilesMenu (); - - QMenu*& menu = g_CurrentMenu; - - // File menu - initMenu ("&File"); - addMenuAction ("newFile"); // New - addMenuAction ("open"); // Open - menu->addMenu (m_recentFilesMenu); // Open Recent - addMenuAction ("save"); // Save - addMenuAction ("saveAs"); // Save As - menu->addSeparator (); // ------- - addMenuAction ("settings"); // Settings - addMenuAction ("setLDrawPath"); // Set LDraw Path - menu->addSeparator (); // ------- - addMenuAction ("exit"); // Exit - - // View menu - initMenu ("&View"); - addMenuAction ("resetView"); // Reset View - addMenuAction ("axes"); // Draw Axes - menu->addSeparator (); // ----- - addMenuAction ("screencap"); // Screencap Part - addMenuAction ("showHistory"); // Edit History - - // Insert menu - initMenu ("&Insert"); - addMenuAction ("insertFrom"); // Insert from File - addMenuAction ("insertRaw"); // Insert Raw - menu->addSeparator (); // ------- - addMenuAction ("newSubfile"); // New Subfile - addMenuAction ("newLine"); // New Line - addMenuAction ("newTriangle"); // New Triangle - addMenuAction ("newQuad"); // New Quad - addMenuAction ("newCondLine"); // New Conditional Line - addMenuAction ("newComment"); // New Comment - addMenuAction ("newBFC"); // New BFC Statment - addMenuAction ("newVertex"); // New Vertex - addMenuAction ("newRadial"); // New Radial - menu->addSeparator (); // ----- - addMenuAction ("beginDraw"); // Begin Drawing - addMenuAction ("doneDraw"); // Cancel Drawing - addMenuAction ("cancelDraw"); // Done Drawing - - // Edit menu - initMenu ("&Edit"); - addMenuAction ("undo"); // Undo - addMenuAction ("redo"); // Redo - menu->addSeparator (); // ----- - addMenuAction ("cut"); // Cut - addMenuAction ("copy"); // Copy - addMenuAction ("paste"); // Paste - addMenuAction ("del"); // Delete - menu->addSeparator (); // ----- - - addMenuAction ("selectAll"); // Select All - addMenuAction ("selectByColor"); // Select by Color - addMenuAction ("selectByType"); // Select by Type - - initMenu ("&Tools"); - addMenuAction ("setColor"); // Set Color - addMenuAction ("invert"); // Invert - addMenuAction ("inlineContents"); // Inline - addMenuAction ("deepInline"); // Deep Inline - addMenuAction ("splitQuads"); // Split Quads - addMenuAction ("setContents"); // Set Contents - addMenuAction ("makeBorders"); // Make Borders - addMenuAction ("makeCornerVerts"); // Make Corner Vertices - addMenuAction ("roundCoords"); // Round Coordinates - addMenuAction ("uncolorize"); // Uncolorize - addMenuAction ("visibility"); // Toggle Visibility - - // Move menu - initMenu ("&Move"); - addMenuAction ("moveUp"); // Move Up - addMenuAction ("moveDown"); // Move Down - menu->addSeparator (); // ----- - addMenuAction ("gridCoarse"); // Coarse Grid - addMenuAction ("gridMedium"); // Medium Grid - addMenuAction ("gridFine"); // Fine Grid - menu->addSeparator (); // ----- - addMenuAction ("moveXPos"); // Move +X - addMenuAction ("moveXNeg"); // Move -X - addMenuAction ("moveYPos"); // Move +Y - addMenuAction ("moveYNeg"); // Move -Y - addMenuAction ("moveZPos"); // Move +Z - addMenuAction ("moveZNeg"); // Move -Z - menu->addSeparator (); // ----- - addMenuAction ("rotateXPos"); // Rotate +X - addMenuAction ("rotateXNeg"); // Rotate -X - addMenuAction ("rotateYPos"); // Rotate +Y - addMenuAction ("rotateYNeg"); // Rotate -Y - addMenuAction ("rotateZPos"); // Rotate +Z - addMenuAction ("rotateZNeg"); // Rotate -Z - - initMenu ("E&xternal Programs"); - addMenuAction ("ytruder"); - addMenuAction ("rectifier"); - addMenuAction ("intersector"); - - // Help menu - initMenu ("&Help"); - addMenuAction ("help"); // Help - menu->addSeparator (); // ----- - addMenuAction ("about"); // About - addMenuAction ("aboutQt"); // About Qt -} - - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::updateRecentFilesMenu () { - // First, clear any items in the recent files menu - for (QAction* recent : m_recentFiles) - delete recent; - m_recentFiles.clear (); - - std::vector<str> files = io_recentfiles.value / "@"; - for (long i = files.size() - 1; i >= 0; --i) { - str file = files[i]; - - QAction* recent = new QAction (getIcon ("open-recent"), file, this); - - connect (recent, SIGNAL (triggered ()), this, SLOT (slot_recentFile ())); - m_recentFilesMenu->addAction (recent); - m_recentFiles.push_back (recent); - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -static QToolBar* g_CurrentToolBar; -static Qt::ToolBarArea g_ToolBarArea = Qt::TopToolBarArea; - -void ForgeWindow::initSingleToolBar (const char* name) { - QToolBar* toolbar = new QToolBar (name); - addToolBar (g_ToolBarArea, toolbar); - m_toolBars.push_back (toolbar); - - g_CurrentToolBar = toolbar; -} - -// ============================================================================= -void ForgeWindow::addToolBarAction (const char* name) { - g_CurrentToolBar->addAction (findAction (name)); -} - -// ============================================================================= -void ForgeWindow::createToolbars () { - initSingleToolBar ("File"); - addToolBarAction ("newFile"); - addToolBarAction ("open"); - addToolBarAction ("save"); - addToolBarAction ("saveAs"); - - // ========================================== - initSingleToolBar ("Insert"); - addToolBarAction ("newSubfile"); - addToolBarAction ("newLine"); - addToolBarAction ("newTriangle"); - addToolBarAction ("newQuad"); - addToolBarAction ("newCondLine"); - addToolBarAction ("newComment"); - addToolBarAction ("newBFC"); - addToolBarAction ("newVertex"); - addToolBarAction ("newRadial"); - - // ========================================== - initSingleToolBar ("Edit"); - addToolBarAction ("undo"); - addToolBarAction ("redo"); - addToolBarAction ("cut"); - addToolBarAction ("copy"); - addToolBarAction ("paste"); - addToolBarAction ("del"); - - // ========================================== - initSingleToolBar ("Select"); - addToolBarAction ("selectAll"); - addToolBarAction ("selectByColor"); - addToolBarAction ("selectByType"); - - addToolBarBreak (Qt::TopToolBarArea); - - // ========================================== - initSingleToolBar ("Move"); - addToolBarAction ("moveUp"); - addToolBarAction ("moveDown"); - addToolBarAction ("moveXPos"); - addToolBarAction ("moveXNeg"); - addToolBarAction ("moveYPos"); - addToolBarAction ("moveYNeg"); - addToolBarAction ("moveZPos"); - addToolBarAction ("moveZNeg"); - - // ========================================== - initSingleToolBar ("Rotate"); - addToolBarAction ("rotateXPos"); - addToolBarAction ("rotateXNeg"); - addToolBarAction ("rotateYPos"); - addToolBarAction ("rotateYNeg"); - addToolBarAction ("rotateZPos"); - addToolBarAction ("rotateZNeg"); - - // ========================================== - // Grid toolbar - initSingleToolBar ("Grids"); - addToolBarAction ("gridCoarse"); - addToolBarAction ("gridMedium"); - addToolBarAction ("gridFine"); - addToolBarBreak (Qt::TopToolBarArea); - - // ========================================== - initSingleToolBar ("View"); - addToolBarAction ("axes"); - - // ========================================== - // Color toolbar - m_colorToolBar = new QToolBar ("Quick Colors"); - addToolBar (Qt::RightToolBarArea, m_colorToolBar); - - // ========================================== - // Left area toolbars - //g_ToolBarArea = Qt::LeftToolBarArea; - initSingleToolBar ("Tools"); - addToolBarAction ("setColor"); - addToolBarAction ("invert"); - addToolBarAction ("inlineContents"); - addToolBarAction ("deepInline"); - addToolBarAction ("splitQuads"); - addToolBarAction ("setContents"); - addToolBarAction ("makeBorders"); - addToolBarAction ("makeCornerVerts"); - addToolBarAction ("roundCoords"); - addToolBarAction ("screencap"); - addToolBarAction ("uncolorize"); - addToolBarAction ("visibility"); - - initSingleToolBar ("External Programs"); - addToolBarAction ("ytruder"); - addToolBarAction ("rectifier"); - addToolBarAction ("intersector"); - updateToolBars (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -std::vector<quickColorMetaEntry> parseQuickColorMeta () { - std::vector<quickColorMetaEntry> meta; - - for (str colorname : gui_colortoolbar.value / ":") { - if (colorname == "|") { - meta.push_back ({null, null, true}); - } else { - color* col = getColor (atoi (colorname)); - assert (col != null); - meta.push_back ({col, null, false}); - } - } - - return meta; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::updateToolBars () { - for (QToolBar* bar : m_toolBars) - bar->setIconSize (QSize (gui_toolbar_iconsize, gui_toolbar_iconsize)); - - // Update the quick color toolbar. - for (QPushButton* btn : m_colorButtons) - delete btn; - - m_colorButtons.clear (); - - // Clear the toolbar to remove separators - m_colorToolBar->clear (); - - for (quickColorMetaEntry& entry : m_colorMeta) { - if (entry.bSeparator) - m_colorToolBar->addSeparator (); - else { - QPushButton* colorButton = new QPushButton; - colorButton->setAutoFillBackground (true); - colorButton->setStyleSheet (fmt ("background-color: %s", entry.col->zColorString.chars())); - colorButton->setToolTip (entry.col->zName); - - connect (colorButton, SIGNAL (clicked ()), this, SLOT (slot_quickColor ())); - m_colorToolBar->addWidget (colorButton); - m_colorButtons.push_back (colorButton); - - entry.btn = colorButton; - } - } - - updateGridToolBar (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::updateGridToolBar () { - // Ensure that the current grid - and only the current grid - is selected. - findAction ("gridCoarse")->setChecked (grid == Grid::Coarse); - findAction ("gridMedium")->setChecked (grid == Grid::Medium); - findAction ("gridFine")->setChecked (grid == Grid::Fine); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::setTitle () { - str title = APPNAME " v"; - title += versionString; - - // Append our current file if we have one - if (g_curfile) { - title += fmt (": %s", basename (g_curfile->m_filename.chars())); - - if (g_curfile->m_objs.size() > 0 && - g_curfile->m_objs[0]->getType() == LDObject::Comment) - { - // Append title - LDComment* comm = static_cast<LDComment*> (g_curfile->m_objs[0]); - title += fmt (": %s", comm->text.chars()); - } - - if (History::pos () != g_curfile->savePos) - title += '*'; - } - - setWindowTitle (title); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::slot_action () { - // Get the action that triggered this slot. - QAction* qAct = static_cast<QAction*> (sender ()); - - // Find the meta for the action. - actionmeta* pMeta = null; - - for (actionmeta meta : g_ActionMeta) { - if (*meta.qAct == qAct) { - pMeta = &meta; - break; - } - } - - if (!pMeta) { - logf (LOG_Warning, "unknown signal sender %p!\n", qAct); - return; - } - - // We have the meta, now call the handler. - (*pMeta->handler) (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::deleteSelection (vector<ulong>* ulapIndices, std::vector<LDObject*>* papObjects) { - if (m_sel.size () == 0) - return; - - std::vector<LDObject*> selCopy = m_sel; - - // Delete the objects that were being selected - for (LDObject* obj : selCopy) { - if (papObjects && ulapIndices) { - papObjects->push_back (obj->clone ()); - ulapIndices->push_back (obj->getIndex (g_curfile)); - } - - g_curfile->forgetObject (obj); - delete obj; - } - - refresh (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::buildObjList () { - if (!g_curfile) - return; - - // Lock the selection while we do this so that refreshing the object list - // doesn't trigger selection updating so that the selection doesn't get lost - // while this is done. - g_bSelectionLocked = true; - - m_objList->clear (); - - for (LDObject* obj : g_curfile->m_objs) { - str descr; - switch (obj->getType ()) { - case LDObject::Comment: - descr = static_cast<LDComment*> (obj)->text.chars(); - - // Remove leading whitespace - while (~descr && descr[0] == ' ') - descr -= -1; - break; - - case LDObject::Empty: - break; // leave it empty - - case LDObject::Line: - { - LDLine* line = static_cast<LDLine*> (obj); - descr.format ("%s, %s", - line->vaCoords[0].stringRep (true).chars(), - line->vaCoords[1].stringRep (true).chars()); - } - break; - - case LDObject::Triangle: - { - LDTriangle* triangle = static_cast<LDTriangle*> (obj); - descr.format ("%s, %s, %s", - triangle->vaCoords[0].stringRep (true).chars(), - triangle->vaCoords[1].stringRep (true).chars(), - triangle->vaCoords[2].stringRep (true).chars()); - } - break; - - case LDObject::Quad: - { - LDQuad* quad = static_cast<LDQuad*> (obj); - descr.format ("%s, %s, %s, %s", - quad->vaCoords[0].stringRep (true).chars(), - quad->vaCoords[1].stringRep (true).chars(), - quad->vaCoords[2].stringRep (true).chars(), - quad->vaCoords[3].stringRep (true).chars()); - } - break; - - case LDObject::CondLine: - { - LDCondLine* line = static_cast<LDCondLine*> (obj); - descr.format ("%s, %s, %s, %s", - line->vaCoords[0].stringRep (true).chars(), - line->vaCoords[1].stringRep (true).chars(), - line->vaCoords[2].stringRep (true).chars(), - line->vaCoords[3].stringRep (true).chars()); - } - break; - - case LDObject::Gibberish: - descr.format ("ERROR: %s", - static_cast<LDGibberish*> (obj)->zContents.chars()); - break; - - case LDObject::Vertex: - descr.format ("%s", static_cast<LDVertex*> (obj)->vPosition.stringRep (true).chars()); - break; - - case LDObject::Subfile: - { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - - descr.format ("%s %s, (", - ref->zFileName.chars(), ref->vPosition.stringRep (true).chars()); - - for (short i = 0; i < 9; ++i) - descr.appendformat ("%s%s", - ftoa (ref->mMatrix[i]).chars(), - (i != 8) ? " " : ""); - - descr += ')'; - } - break; - - case LDObject::BFC: - { - LDBFC* bfc = static_cast<LDBFC*> (obj); - descr = LDBFC::statements[bfc->type]; - } - break; - - case LDObject::Radial: - { - LDRadial* pRad = static_cast<LDRadial*> (obj); - descr.format ("%d / %d %s", pRad->dSegments, pRad->dDivisions, pRad->radialTypeName()); - - if (pRad->eRadialType == LDRadial::Ring || pRad->eRadialType == LDRadial::Cone) - descr.appendformat (" %d", pRad->dRingNum); - - descr.appendformat (" %s", pRad->vPosition.stringRep (true).chars ()); - } - break; - - default: - descr = g_saObjTypeNames[obj->getType ()]; - break; - } - - // Put it into brackets if it's hidden - if (obj->hidden ()) { - str copy = descr.chars (); - descr.format ("[[ %s ]]", copy.chars ()); - } - - QListWidgetItem* item = new QListWidgetItem (descr.chars()); - item->setIcon (getIcon (g_saObjTypeIcons[obj->getType ()])); - - // Color gibberish orange on red so it stands out. - if (obj->getType() == LDObject::Gibberish) { - item->setBackground (QColor ("#AA0000")); - item->setForeground (QColor ("#FFAA00")); - } else if (lv_colorize && obj->isColored () && - obj->dColor != maincolor && obj->dColor != edgecolor) - { - // If the object isn't in the main or edge color, draw this - // list entry in said color. - color* col = getColor (obj->dColor); - if (col) - item->setForeground (col->qColor); - } - - obj->qObjListEntry = item; - m_objList->insertItem (m_objList->count (), item); - } - - g_bSelectionLocked = false; - updateSelection (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::scrollToSelection () { - if (m_sel.size() == 0) - return; - - LDObject* obj = m_sel[m_sel.size () - 1]; - m_objList->scrollToItem (obj->qObjListEntry); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::slot_selectionChanged () { - if (g_bSelectionLocked == true || g_curfile == null) - return; - - /* - // If the selection isn't 1 exact, disable setting contents - findAction ("setContents")->setEnabled (qObjList->selectedItems().size() == 1); - - // If we have no selection, disable splitting quads - findAction ("splitQuads")->setEnabled (qObjList->selectedItems().size() > 0); - */ - - // Update the shared selection array, though don't do this if this was - // called during GL picking, in which case the GL renderer takes care - // of the selection. - if (m_renderer->picking ()) - return; - - std::vector<LDObject*> priorSelection = m_sel; - - // Get the objects from the object list selection - m_sel.clear (); - const QList<QListWidgetItem*> items = m_objList->selectedItems (); - - for (LDObject* obj : g_curfile->m_objs) - for (QListWidgetItem* qItem : items) { - if (qItem == obj->qObjListEntry) { - m_sel.push_back (obj); - break; - } - } - - // Update the GL renderer - for (LDObject* obj : m_sel) - m_renderer->recompileObject (obj); - - for (LDObject* obj : priorSelection) - m_renderer->recompileObject (obj); - - m_renderer->updateSelFlash (); - m_renderer->refresh (); -} - -// ============================================================================= -void ForgeWindow::slot_recentFile () { - QAction* qAct = static_cast<QAction*> (sender ()); - openMainFile (qAct->text ()); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::slot_quickColor () { - QPushButton* button = static_cast<QPushButton*> (sender ()); - color* col = null; - - for (quickColorMetaEntry entry : m_colorMeta) { - if (entry.btn == button) { - col = entry.col; - break; - } - } - - if (col == null) - return; - - std::vector<ulong> indices; - std::vector<short> colors; - short newColor = col->index (); - - for (LDObject* obj : m_sel) { - if (obj->dColor == -1) - continue; // uncolored object - - indices.push_back (obj->getIndex (g_curfile)); - colors.push_back (obj->dColor); - - obj->dColor = newColor; - } - - History::addEntry (new SetColorHistory (indices, colors, newColor)); - refresh (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -ulong ForgeWindow::getInsertionPoint () { - if (m_sel.size () > 0) { - // If we have a selection, put the item after it. - return (m_sel[m_sel.size() - 1]->getIndex (g_curfile)) + 1; - } - - // Otherwise place the object at the end. - return g_curfile->m_objs.size(); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::refresh () { - buildObjList (); - m_renderer->hardRefresh (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::updateSelection () { - g_bSelectionLocked = true; - - m_objList->clearSelection (); - for (LDObject* obj : m_sel) - obj->qObjListEntry->setSelected (true); - - g_bSelectionLocked = false; - slot_selectionChanged (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -bool ForgeWindow::isSelected (LDObject* obj) { - LDObject* needle = obj->topLevelParent (); - - if (needle == null) - needle = obj; - - for (LDObject* hay : m_sel) - if (hay == needle) - return true; - - return false; -} - -short ForgeWindow::getSelectedColor() { - short result = -1; - - for (LDObject* obj : m_sel) { - if (obj->dColor == -1) - continue; // doesn't use color - - if (result != -1 && obj->dColor != result) - return -1; // No consensus in object color - - if (result == -1) - result = obj->dColor; - } - - return result; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -LDObject::Type ForgeWindow::uniformSelectedType () { - LDObject::Type eResult = LDObject::Unidentified; - - for (LDObject* obj : m_sel) { - if (eResult != LDObject::Unidentified && obj->dColor != eResult) - return LDObject::Unidentified; - - if (eResult == LDObject::Unidentified) - eResult = obj->getType (); - } - - return eResult; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::closeEvent (QCloseEvent* ev) { - // Check whether it's safe to close all files. - for (OpenFile* f : g_loadedFiles) { - if (!f->safeToClose ()) { - ev->ignore (); - return; - } - } - - // Save the configuration before leaving so that, for instance, grid choice - // is preserved across instances. - config::save (); - - ev->accept (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ForgeWindow::spawnContextMenu (const QPoint pos) { - const bool single = (g_win->sel ().size () == 1); - LDObject* singleObj = (single) ? g_win->sel ()[0] : null; - - QMenu* contextMenu = new QMenu; - - if (single && singleObj->getType () != LDObject::Empty) { - contextMenu->addAction (findAction ("editObject")); - contextMenu->addSeparator (); - } - - contextMenu->addAction (findAction ("cut")); - contextMenu->addAction (findAction ("copy")); - contextMenu->addAction (findAction ("paste")); - contextMenu->addAction (findAction ("del")); - contextMenu->addSeparator (); - contextMenu->addAction (findAction ("setColor")); - if (single) - contextMenu->addAction (findAction ("setContents")); - contextMenu->addAction (findAction ("makeBorders")); - - contextMenu->exec (pos); -} - -// ======================================================================================================================================== -DelHistory* ForgeWindow::deleteObjVector (const std::vector<LDObject*> objs) { - vector<ulong> indices; - vector<LDObject*> cache; - - for (LDObject* obj : objs) { - indices.push_back (obj->getIndex (g_curfile)); - cache.push_back (obj->clone ()); - - g_curfile->forgetObject (obj); - delete obj; - } - - if (indices.size () > 0) - return new DelHistory (indices, cache); - - return null; -} - -// ======================================================================================================================================== -DelHistory* ForgeWindow::deleteSelection () { - return deleteObjVector (sel ()); -} - -// ======================================================================================================================================== -DelHistory* ForgeWindow::deleteByColor (const short colnum) { - vector<LDObject*> objs; - for (LDObject* obj : g_curfile->m_objs) { - if (!obj->isColored () || obj->dColor != colnum) - continue; - - objs.push_back (obj); - } - - return deleteObjVector (objs); -} - -// ======================================================================================================================================== -void ObjectList::contextMenuEvent (QContextMenuEvent* ev) { - g_win->spawnContextMenu (ev->globalPos ()); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -QPixmap getIcon (const char* iconName) { - return (QPixmap (fmt (":/icons/%s.png", iconName))); -} - -// ============================================================================= -bool confirm (str msg) { - return confirm ("Confirm", msg); -} - -bool confirm (str title, str msg) { - return QMessageBox::question (g_win, title, msg, - (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes; -} - -// ============================================================================= -void critical (str msg) { - QMessageBox::critical (g_win, "Error", msg, - (QMessageBox::Close), QMessageBox::Close); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -// Print to message log -// TODO: I don't think that the message log being a widget in the window -// is a very good idea... maybe this should log into the renderer? Or into -// another dialog that can be popped up? -void ForgeWindow::logVA (LogType type, const char* fmtstr, va_list va) { - (void) type; - (void) fmtstr; - (void) va; -} - -// ============================================================================= -QAction* findAction (str name) { - for (actionmeta& meta : g_ActionMeta) - if (name == meta.name) - return *meta.qAct; - - fprintf (stderr, "%s: couldn't find action named `%s'!\n", __func__, name.chars ()); - assert (false); - return null; -} - -// ============================================================================= -void makeColorSelector (QComboBox* box) { - std::map<short, ulong> counts; - - for (LDObject* obj : g_curfile->m_objs) { - if (!obj->isColored ()) - continue; - - if (counts.find (obj->dColor) == counts.end ()) - counts[obj->dColor] = 1; - else - counts[obj->dColor]++; - } - - box->clear (); - ulong row = 0; - for (const auto& pair : counts) { - const ushort size = 16; - color* col = getColor (pair.first); - assert (col != null); - - // Paint an icon for this color - QIcon ico; - QImage img (size, size, QImage::Format_ARGB32); - QPainter paint (&img); - paint.fillRect (QRect (0, 0, size, size), Qt::black); - paint.drawPixmap (QRect (1, 1, size - 2, size - 2), getIcon ("checkerboard"), QRect (0, 0, 8, 8)); - paint.fillRect (QRect (1, 1, size - 2, size - 2), QColor (col->qColor)); - ico = QIcon (QPixmap::fromImage (img)); - - box->addItem (ico, fmt ("[%d] %s (%lu object%s)", - pair.first, col->zName.chars (), pair.second, PLURAL (pair.second))); - box->setItemData (row, pair.first); - - ++row; - } -} \ No newline at end of file
--- a/gui.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,253 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef GUI_H -#define GUI_H - -#include <QMainWindow> -#include <QMenu> -#include <QToolBar> -#include <QAction> -#include <QToolBar> -#include <QTextEdit> -#include <qpushbutton.h> -#include <qlistwidget.h> -#include <qlabel.h> -#include <qboxlayout.h> -#include "gldraw.h" -#include "config.h" - -class QComboBox; -class ForgeWindow; -class color; -class QSplitter; -class DelHistory; - -// Stuff for dialogs -#define IMPLEMENT_DIALOG_BUTTONS \ - bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); \ - connect (bbx_buttons, SIGNAL (accepted ()), this, SLOT (accept ())); \ - connect (bbx_buttons, SIGNAL (rejected ()), this, SLOT (reject ())); \ - -// ============================================================================= -// Metadata for actions -typedef struct { - QAction** const qAct; - keyseqconfig* const conf; - const char* const name, *sDisplayName, *sIconName, *sDescription; - void (*const handler) (); -} actionmeta; - -extern vector<actionmeta> g_ActionMeta; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -#define MAKE_ACTION(NAME, DISPLAYNAME, ICONNAME, DESCR, DEFSHORTCUT) \ - QAction* ACTION (NAME); \ - cfg (keyseq, key_##NAME, DEFSHORTCUT); \ - static void actionHandler_##NAME (); \ - static ActionAdder ActionAdderInstance_##NAME (&ACTION(NAME), DISPLAYNAME, \ - ICONNAME, DESCR, &key_##NAME, actionHandler_##NAME, #NAME); \ - static void actionHandler_##NAME () - -#define EXTERN_ACTION(NAME) extern QAction* ACTION (NAME); -#define ACTION(N) LDForgeAction_##N - -// Convenience macros for key sequences. -#define KEY(N) (Qt::Key_##N) -#define CTRL(N) (Qt::CTRL | Qt::Key_##N) -#define SHIFT(N) (Qt::SHIFT | Qt::Key_##N) -#define CTRL_SHIFT(N) (Qt::CTRL | Qt::SHIFT | Qt::Key_##N) - -// ============================================================================= -typedef struct { - color* col; - QPushButton* btn; - bool bSeparator; -} quickColorMetaEntry; - -// ============================================================================= -// ActionAdder -// -// The ACTION macro expands into - among other stuff - into an instance of this. -// This class' constructor creates meta for the newly defined action and stores -// it in g_ActionMeta. It is not supposed to be used directly! -// ============================================================================= -class ActionAdder { -public: - ActionAdder (QAction** qAct, const char* sDisplayName, const char* sIconName, - const char* sDescription, keyseqconfig* conf, void (*const handler) (), - const char* name) - { - actionmeta meta = {qAct, conf, name, sDisplayName, sIconName, sDescription, handler}; - g_ActionMeta.push_back (meta); - } -}; - -// ============================================================================= -// ObjectList -// -// Object list class for ForgeWindow -// ============================================================================= -class ObjectList : public QListWidget { - Q_OBJECT - -protected: - void contextMenuEvent (QContextMenuEvent* ev); -}; - -// ============================================================================= -// ForgeWindow -// -// The one main GUI class. Hosts the renderer, object list, message log. Contains -// slot_action, which is what all actions connect to. Manages menus and toolbars. -// Large and in charge. -// ============================================================================= -class ForgeWindow : public QMainWindow { - Q_OBJECT - -public: - ForgeWindow (); - void buildObjList (); - void setTitle (); - void refresh (); - ulong getInsertionPoint (); - void deleteSelection (vector<ulong>* ulapIndices, std::vector<LDObject*>* papObjects); - void updateToolBars (); - void updateRecentFilesMenu (); - void updateSelection (); - void updateGridToolBar (); - bool isSelected (LDObject* obj); - short getSelectedColor(); - LDObject::Type uniformSelectedType (); - void scrollToSelection (); - void spawnContextMenu (const QPoint pos); - DelHistory* deleteObjVector (const std::vector<LDObject*> objs); - DelHistory* deleteSelection (); - DelHistory* deleteByColor (const short colnum); - GLRenderer* R () { return m_renderer; } - std::vector<LDObject*>& sel () { return m_sel; } - void setQuickColorMeta (std::vector<quickColorMetaEntry>& quickColorMeta) { - m_colorMeta = quickColorMeta; - } - -protected: - void closeEvent (QCloseEvent* ev); - void logVA (LogType eType, const char* fmtstr, va_list va); - - friend void logf (const char* fmt, ...); - friend void logf (LogType eType, const char* fmt, ...); - friend void warnf (const char* fmt, ...); - friend void infof (const char* fmt, ...); - friend void succf (const char* fmt, ...); - friend void errf (const char* fmt, ...); - friend void devf (const char* fmt, ...); - -private: - GLRenderer* m_renderer; - ObjectList* m_objList; - QMenu* m_recentFilesMenu; - QSplitter* m_splitter; - str m_msglogHTML; - QToolBar* m_colorToolBar; - std::vector<QToolBar*> m_toolBars; - std::vector<LDObject*> m_sel; - std::vector<quickColorMetaEntry> m_colorMeta; - std::vector<QPushButton*> m_colorButtons; - std::vector<QAction*> m_recentFiles; - - void createMenuActions (); - void createMenus (); - void createToolbars (); - void initSingleToolBar (const char* name); - void addToolBarAction (const char* name); - - QMenu* initMenu (const char* name); - void addMenuAction (const char* name); - -private slots: - void slot_selectionChanged (); - void slot_action (); - void slot_recentFile (); - void slot_quickColor (); - void slot_lastSecondCleanup (); -}; - -// ============================================================================= -// LabeledWidget -// -// Convenience class for a widget with a label beside it. -// ============================================================================= -template<class R> class LabeledWidget : public QWidget { -public: - explicit LabeledWidget (const char* labelstr, QWidget* parent = null) : QWidget (parent) { - m_widget = new R (this); - commonInit (labelstr); - } - - explicit LabeledWidget (const char* labelstr, R* widget, QWidget* parent = null) : - QWidget (parent), m_widget (widget) - { - commonInit (labelstr); - } - - explicit LabeledWidget (QWidget* parent = 0, Qt::WindowFlags f = 0) { - m_widget = new R (this); - commonInit (""); - } - - R* widget () const { return m_widget; } - R* w () const { return m_widget; } - QLabel* label () const { return m_label; } - QLabel* l () const { return m_label; } - void setWidget (R* widget) { m_widget = widget; } - void setLabel (QLabel* label) { m_label = label; } - operator R* () { return m_widget; } - -private: - Q_DISABLE_COPY (LabeledWidget<R>) - - void commonInit (const char* labelstr) { - m_label = new QLabel (labelstr, this); - m_layout = new QHBoxLayout; - m_layout->addWidget (m_label); - m_layout->addWidget (m_widget); - setLayout (m_layout); - } - - R* m_widget; - QLabel* m_label; - QHBoxLayout* m_layout; -}; - -// ----------------------------------------------------------------------------- -// Other GUI-related stuff not directly part of ForgeWindow: -QPixmap getIcon (const char* sIconName); -std::vector<quickColorMetaEntry> parseQuickColorMeta (); -bool confirm (str title, str msg); -bool confirm (str msg); -void critical (str msg); -QAction* findAction (str name); -void makeColorSelector (QComboBox* box); - -// ----------------------------------------------------------------------------- -// Pointer to the instance of ForgeWindow. -extern ForgeWindow* g_win; - -#endif // GUI_H \ No newline at end of file
--- a/gui_actions.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,419 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <qfiledialog.h> -#include <qmessagebox.h> -#include "gui.h" -#include "file.h" -#include "history.h" -#include "zz_newPartDialog.h" -#include "zz_configDialog.h" -#include "zz_addObjectDialog.h" -#include "zz_aboutDialog.h" -#include "misc.h" -#include "zz_ldrawPathDialog.h" - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (newFile, "&New", "brick", "Create a new part model.", CTRL (N)) { - NewPartDialog::StaticDialog (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (open, "&Open", "file-open", "Load a part model from a file.", CTRL (O)) { - str zName; - zName += QFileDialog::getOpenFileName (g_win, "Open File", - "", "LDraw files (*.dat *.ldr)"); - - if (~zName) - openMainFile (zName); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void doSave (bool saveAs) { - str path = g_curfile->m_filename; - - if (~path == 0 || saveAs) { - path = QFileDialog::getSaveFileName (g_win, "Save As", - "", "LDraw files (*.dat *.ldr)"); - - if (~path == 0) { - // User didn't give a file name. This happens if the user cancelled - // saving in the save file dialog. Abort. - return; - } - } - - if (g_curfile->save (path)) { - g_curfile->m_filename = path; - g_win->setTitle (); - - logf ("Saved successfully to %s\n", path.chars ()); - } else { - setlocale (LC_ALL, "C"); - - // Tell the user the save failed, and give the option for saving as with it. - QMessageBox dlg (QMessageBox::Critical, "Save Failure", - fmt ("Failed to save to %s\nReason: %s", path.chars(), strerror (g_curfile->lastError)), - QMessageBox::Close, g_win); - - QPushButton* saveAsBtn = new QPushButton ("Save As"); - saveAsBtn->setIcon (getIcon ("file-save-as")); - dlg.addButton (saveAsBtn, QMessageBox::ActionRole); - dlg.setDefaultButton (QMessageBox::Close); - dlg.exec (); - - if (dlg.clickedButton () == saveAsBtn) - doSave (true); // yay recursion! - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (save, "&Save", "file-save", "Save the part model.", CTRL (S)) { - doSave (false); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (saveAs, "Save &As", "file-save-as", "Save the part model to a specific file.", CTRL_SHIFT (S)) { - doSave (true); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (settings, "Settin&gs", "settings", "Edit the settings of " APPNAME ".", (0)) { - ConfigDialog::staticDialog (); -} - -MAKE_ACTION (setLDrawPath, "Set LDraw Path", "settings", "Change the LDraw directory path.", (0)) { - LDrawPathDialog dlg (true); - dlg.exec (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (exit, "&Exit", "exit", "Close " APPNAME ".", CTRL (Q)) { - exit (0); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (newSubfile, "New Subfile", "add-subfile", "Creates a new subfile reference.", 0) { - AddObjectDialog::staticDialog (LDObject::Subfile, null); -} - -MAKE_ACTION (newLine, "New Line", "add-line", "Creates a new line.", 0) { - AddObjectDialog::staticDialog (LDObject::Line, null); -} - -MAKE_ACTION (newTriangle, "New Triangle", "add-triangle", "Creates a new triangle.", 0) { - AddObjectDialog::staticDialog (LDObject::Triangle, null); -} - -MAKE_ACTION (newQuad, "New Quadrilateral", "add-quad", "Creates a new quadrilateral.", 0) { - AddObjectDialog::staticDialog (LDObject::Quad, null); -} - -MAKE_ACTION (newCondLine, "New Conditional Line", "add-condline", "Creates a new conditional line.", 0) { - AddObjectDialog::staticDialog (LDObject::CondLine, null); -} - -MAKE_ACTION (newComment, "New Comment", "add-comment", "Creates a new comment.", 0) { - AddObjectDialog::staticDialog (LDObject::Comment, null); -} - -MAKE_ACTION (newBFC, "New BFC Statement", "add-bfc", "Creates a new BFC statement.", 0) { - AddObjectDialog::staticDialog (LDObject::BFC, null); -} - -MAKE_ACTION (newVertex, "New Vertex", "add-vertex", "Creates a new vertex.", 0) { - AddObjectDialog::staticDialog (LDObject::Vertex, null); -} - -MAKE_ACTION (newRadial, "New Radial", "add-radial", "Creates a new radial.", 0) { - AddObjectDialog::staticDialog (LDObject::Radial, null); -} - -MAKE_ACTION (editObject, "Edit Object", "edit-object", "Edits this object.", 0) { - if (g_win->sel ().size () != 1) - return; - - LDObject* obj = g_win->sel ()[0]; - AddObjectDialog::staticDialog (obj->getType (), obj); -} - -MAKE_ACTION (help, "Help", "help", "Shows the " APPNAME " help manual.", KEY (F1)) { - -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (about, "About " APPNAME, "ldforge", - "Shows information about " APPNAME ".", (0)) -{ - AboutDialog dlg; - dlg.exec (); -} - -MAKE_ACTION (aboutQt, "About Qt", "qt", "Shows information about Qt.", (0)) { - QMessageBox::aboutQt (g_win); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (selectAll, "Select All", "select-all", "Selects all objects.", CTRL (A)) { - g_win->sel ().clear (); - - for (LDObject* obj : g_curfile->m_objs) - g_win->sel ().push_back (obj); - - g_win->updateSelection (); -} - -// ============================================================================= -MAKE_ACTION (selectByColor, "Select by Color", "select-color", - "Select all objects by the given color.", CTRL_SHIFT (A)) -{ - short dColor = g_win->getSelectedColor (); - - if (dColor == -1) - return; // no consensus on color - - g_win->sel ().clear (); - for (LDObject* obj : g_curfile->m_objs) - if (obj->dColor == dColor) - g_win->sel ().push_back (obj); - - g_win->updateSelection (); -} - -// ============================================================================= -MAKE_ACTION (selectByType, "Select by Type", "select-type", - "Select all objects by the given type.", (0)) -{ - if (g_win->sel ().size () == 0) - return; - - LDObject::Type eType = g_win->uniformSelectedType (); - - if (eType == LDObject::Unidentified) - return; - - // If we're selecting subfile references, the reference filename must also - // be uniform. - str zRefName; - - if (eType == LDObject::Subfile) { - zRefName = static_cast<LDSubfile*> (g_win->sel ()[0])->zFileName; - - for (LDObject* pObj : g_win->sel ()) - if (static_cast<LDSubfile*> (pObj)->zFileName != zRefName) - return; - } - - g_win->sel ().clear (); - for (LDObject* obj : g_curfile->m_objs) { - if (obj->getType() != eType) - continue; - - if (eType == LDObject::Subfile && static_cast<LDSubfile*> (obj)->zFileName != zRefName) - continue; - - g_win->sel ().push_back (obj); - } - - g_win->updateSelection (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (gridCoarse, "Coarse Grid", "grid-coarse", "Set the grid to Coarse", CTRL (1)) { - grid = Grid::Coarse; - g_win->updateGridToolBar (); -} - -MAKE_ACTION (gridMedium, "Medium Grid", "grid-medium", "Set the grid to Medium", CTRL (2)) { - grid = Grid::Medium; - g_win->updateGridToolBar (); -} - -MAKE_ACTION (gridFine, "Fine Grid", "grid-fine", "Set the grid to Fine", CTRL (3)) { - grid = Grid::Fine; - g_win->updateGridToolBar (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (resetView, "Reset View", "reset-view", "Reset view angles, pan and zoom", CTRL (0)) { - g_win->R ()->resetAngles (); - g_win->R ()->update (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (insertFrom, "Insert from File", "insert-from", "Insert LDraw data from a file.", (0)) { - str fname = QFileDialog::getOpenFileName (); - ulong idx = g_win->getInsertionPoint (); - - if (!~fname) - return; - - FILE* fp = fopen (fname, "r"); - if (!fp) { - critical (fmt ("Couldn't open %s\n%s", fname.chars(), strerror (errno))); - return; - } - - std::vector<LDObject*> historyCopies; - std::vector<ulong> historyIndices; - std::vector<LDObject*> objs = loadFileContents (fp, null); - - g_win->sel ().clear (); - - for (LDObject* obj : objs) { - historyCopies.push_back (obj->clone ()); - historyIndices.push_back (idx); - g_curfile->insertObj (idx, obj); - g_win->sel ().push_back (obj); - - idx++; - } - - if (historyCopies.size() > 0) { - History::addEntry (new AddHistory (historyIndices, historyCopies)); - g_win->refresh (); - g_win->scrollToSelection (); - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (insertRaw, "Insert Raw", "insert-raw", "Type in LDraw code to insert.", (0)) { - ulong idx = g_win->getInsertionPoint (); - - QDialog* const dlg = new QDialog; - QVBoxLayout* const layout = new QVBoxLayout; - QTextEdit* const te_edit = new QTextEdit; - QDialogButtonBox* const bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - std::vector<LDObject*> historyCopies; - std::vector<ulong> historyIndices; - - layout->addWidget (te_edit); - layout->addWidget (bbx_buttons); - dlg->setLayout (layout); - dlg->setWindowTitle (APPNAME ": Insert Raw"); - dlg->connect (bbx_buttons, SIGNAL (accepted ()), dlg, SLOT (accept ())); - dlg->connect (bbx_buttons, SIGNAL (rejected ()), dlg, SLOT (reject ())); - - if (dlg->exec () == false) - return; - - g_win->sel ().clear (); - - for (str line : str (te_edit->toPlainText ()).split ("\n")) { - LDObject* obj = parseLine (line); - - g_curfile->insertObj (idx, obj); - historyIndices.push_back (idx); - historyCopies.push_back (obj->clone ()); - g_win->sel ().push_back (obj); - idx++; - } - - if (historyCopies.size () > 0) { - History::addEntry (new AddHistory (historyIndices, historyCopies)); - g_win->refresh (); - g_win->scrollToSelection (); - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (screencap, "Screencap Part", "screencap", "Save a picture of the model", (0)) { - setlocale (LC_ALL, "C"); - - ushort w, h; - uchar* imagedata = g_win->R ()->screencap (w, h); - - // GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well. - QImage img = QImage (imagedata, w, h, QImage::Format_ARGB32).rgbSwapped ().mirrored (); - - str root = basename (g_curfile->m_filename.chars ()); - if (root.substr (~root - 4, -1) == ".dat") - root -= 4; - - str defaultname = (~root > 0) ? fmt ("%s.png", root.chars ()) : ""; - str fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname, - "PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)"); - - if (~fname > 0 && !img.save (fname)) - critical (fmt ("Couldn't open %s for writing to save screencap: %s", fname.chars(), strerror (errno))); - - delete[] imagedata; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -extern_cfg (bool, gl_axes); -MAKE_ACTION (axes, "Draw Axes", "axes", "Toggles drawing of axes", (0)) { - gl_axes = !gl_axes; - ACTION (axes)->setChecked (gl_axes); - g_win->R ()->update (); -} - -// ============================================================================= -MAKE_ACTION (beginDraw, "Begin Drawing", "draw", "Begin drawing geometry", KEY (Insert)) { - g_win->R ()->beginPlaneDraw (); -} - -MAKE_ACTION (cancelDraw, "Cancel Drawing", "draw-cancel", "Cancel drawing geometry", KEY (Escape)) { - g_win->R ()->endPlaneDraw (false); -} - -MAKE_ACTION (doneDraw, "Done Drawing", "draw-done", "Done drawing geometry", KEY (Enter)) { - g_win->R ()->endPlaneDraw (true); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (visibility, "Toggle Visibility", "visibility", "Toggles visibility/hiding on objects.", (0)) { - for (LDObject* obj : g_win->sel ()) - obj->setHidden (!obj->hidden ()); - - g_win->refresh (); -} \ No newline at end of file
--- a/gui_editactions.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,687 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "gui.h" -#include "common.h" -#include "file.h" -#include "history.h" -#include "zz_colorSelectDialog.h" -#include "zz_historyDialog.h" -#include "zz_setContentsDialog.h" -#include "misc.h" -#include "bbox.h" -#include "radiobox.h" -#include "extprogs.h" -#include <qspinbox.h> -#include <qcheckbox.h> - -vector<LDObject*> g_Clipboard; - -cfg (bool, edit_schemanticinline, false); - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -static bool copyToClipboard () { - vector<LDObject*> objs = g_win->sel (); - - if (objs.size() == 0) - return false; - - // Clear the clipboard first. - for (LDObject* obj : g_Clipboard) - delete obj; - - g_Clipboard.clear (); - - // Now, copy the contents into the clipboard. The objects should be - // separate objects so that modifying the existing ones does not affect - // the clipboard. Thus, we add clones of the objects to the clipboard, not - // the objects themselves. - for (ulong i = 0; i < objs.size(); ++i) - g_Clipboard.push_back (objs[i]->clone ()); - - return true; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (cut, "Cut", "cut", "Cut the current selection to clipboard.", CTRL (X)) { - vector<ulong> ulaIndices; - vector<LDObject*> copies; - - if (!copyToClipboard ()) - return; - - g_win->deleteSelection (&ulaIndices, &copies); - History::addEntry (new DelHistory (ulaIndices, copies, DelHistory::Cut)); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (copy, "Copy", "copy", "Copy the current selection to clipboard.", CTRL (C)) { - copyToClipboard (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (paste, "Paste", "paste", "Paste clipboard contents.", CTRL (V)) { - vector<ulong> historyIndices; - vector<LDObject*> historyCopies; - - ulong idx = g_win->getInsertionPoint (); - g_win->sel ().clear (); - - for (LDObject* obj : g_Clipboard) { - historyIndices.push_back (idx); - historyCopies.push_back (obj->clone ()); - - LDObject* copy = obj->clone (); - g_curfile->insertObj (idx, copy); - g_win->sel ().push_back (copy); - } - - History::addEntry (new AddHistory (historyIndices, historyCopies, AddHistory::Paste)); - g_win->refresh (); - g_win->scrollToSelection (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (del, "Delete", "delete", "Delete the selection", KEY (Delete)) { - vector<ulong> ulaIndices; - vector<LDObject*> copies; - - g_win->deleteSelection (&ulaIndices, &copies); - - if (copies.size ()) - History::addEntry (new DelHistory (ulaIndices, copies)); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -static void doInline (bool bDeep) { - vector<LDObject*> sel = g_win->sel (); - - // History stuff - vector<LDSubfile*> paRefs; - vector<ulong> ulaRefIndices, ulaBitIndices; - - for (LDObject* obj : sel) { - if (obj->getType() != LDObject::Subfile) - continue; - - ulaRefIndices.push_back (obj->getIndex (g_curfile)); - paRefs.push_back (static_cast<LDSubfile*> (obj)->clone ()); - } - - for (LDObject* obj : sel) { - // Get the index of the subfile so we know where to insert the - // inlined contents. - long idx = obj->getIndex (g_curfile); - if (idx == -1) - continue; - - vector<LDObject*> objs; - - if (obj->getType() == LDObject::Subfile) - objs = static_cast<LDSubfile*> (obj)->inlineContents (bDeep, true); - else if (obj->getType() == LDObject::Radial) - objs = static_cast<LDRadial*> (obj)->decompose (true); - else - continue; - - // Merge in the inlined objects - for (LDObject* inlineobj : objs) { - ulaBitIndices.push_back (idx); - - // This object is now inlined so it has no parent anymore. - inlineobj->parent = null; - - g_curfile->insertObj (idx++, inlineobj); - } - - // Delete the subfile now as it's been inlined. - g_curfile->forgetObject (obj); - delete obj; - } - - History::addEntry (new InlineHistory (ulaBitIndices, ulaRefIndices, paRefs, bDeep)); - g_win->refresh (); -} - -MAKE_ACTION (inlineContents, "Inline", "inline", "Inline selected subfiles.", CTRL (I)) { - doInline (false); -} - -MAKE_ACTION (deepInline, "Deep Inline", "inline-deep", "Recursively inline selected subfiles " - "down to polygons only.", CTRL_SHIFT (I)) -{ - doInline (true); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (splitQuads, "Split Quads", "quad-split", "Split quads into triangles.", (0)) { - vector<LDObject*> objs = g_win->sel (); - - vector<ulong> ulaIndices; - vector<LDQuad*> paCopies; - - // Store stuff first for history archival - for (LDObject* obj : objs) { - if (obj->getType() != LDObject::Quad) - continue; - - ulaIndices.push_back (obj->getIndex (g_curfile)); - paCopies.push_back (static_cast<LDQuad*> (obj)->clone ()); - } - - for (LDObject* obj : objs) { - if (obj->getType() != LDObject::Quad) - continue; - - // Find the index of this quad - long lIndex = obj->getIndex (g_curfile); - - if (lIndex == -1) - return; - - std::vector<LDTriangle*> triangles = static_cast<LDQuad*> (obj)->splitToTriangles (); - - // Replace the quad with the first triangle and add the second triangle - // after the first one. - g_curfile->m_objs[lIndex] = triangles[0]; - g_curfile->insertObj (lIndex + 1, triangles[1]); - - // Delete this quad now, it has been split. - delete obj; - } - - History::addEntry (new QuadSplitHistory (ulaIndices, paCopies)); - g_win->refresh (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (setContents, "Set Contents", "set-contents", "Set the raw code of this object.", KEY (F9)) { - if (g_win->sel ().size() != 1) - return; - - LDObject* obj = g_win->sel ()[0]; - SetContentsDialog::staticDialog (obj); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (setColor, "Set Color", "palette", "Set the color on given objects.", KEY (F10)) { - if (g_win->sel ().size() <= 0) - return; - - short dColor; - short dDefault = -1; - - std::vector<LDObject*> objs = g_win->sel (); - - // If all selected objects have the same color, said color is our default - // value to the color selection dialog. - dDefault = g_win->getSelectedColor (); - - // Show the dialog to the user now and ask for a color. - if (ColorSelectDialog::staticDialog (dColor, dDefault, g_win)) { - std::vector<ulong> ulaIndices; - std::vector<short> daColors; - - for (LDObject* obj : objs) { - if (obj->dColor != -1) { - ulaIndices.push_back (obj->getIndex (g_curfile)); - daColors.push_back (obj->dColor); - - obj->dColor = dColor; - } - } - - History::addEntry (new SetColorHistory (ulaIndices, daColors, dColor)); - g_win->refresh (); - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (makeBorders, "Make Borders", "make-borders", "Add borders around given polygons.", - CTRL_SHIFT (B)) -{ - vector<LDObject*> objs = g_win->sel (); - - vector<ulong> ulaIndices; - vector<LDObject*> paObjs; - - for (LDObject* obj : objs) { - if (obj->getType() != LDObject::Quad && obj->getType() != LDObject::Triangle) - continue; - - short dNumLines; - LDLine* lines[4]; - - if (obj->getType() == LDObject::Quad) { - dNumLines = 4; - - LDQuad* quad = static_cast<LDQuad*> (obj); - lines[0] = new LDLine (quad->vaCoords[0], quad->vaCoords[1]); - lines[1] = new LDLine (quad->vaCoords[1], quad->vaCoords[2]); - lines[2] = new LDLine (quad->vaCoords[2], quad->vaCoords[3]); - lines[3] = new LDLine (quad->vaCoords[3], quad->vaCoords[0]); - } else { - dNumLines = 3; - - LDTriangle* tri = static_cast<LDTriangle*> (obj); - lines[0] = new LDLine (tri->vaCoords[0], tri->vaCoords[1]); - lines[1] = new LDLine (tri->vaCoords[1], tri->vaCoords[2]); - lines[2] = new LDLine (tri->vaCoords[2], tri->vaCoords[0]); - } - - for (short i = 0; i < dNumLines; ++i) { - ulong idx = obj->getIndex (g_curfile) + i + 1; - - lines[i]->dColor = edgecolor; - g_curfile->insertObj (idx, lines[i]); - - ulaIndices.push_back (idx); - paObjs.push_back (lines[i]->clone ()); - } - } - - History::addEntry (new AddHistory (ulaIndices, paObjs)); - g_win->refresh (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (makeCornerVerts, "Make Corner Vertices", "corner-verts", - "Adds vertex objects to the corners of the given polygons", (0)) -{ - vector<ulong> ulaIndices; - vector<LDObject*> paObjs; - - for (LDObject* obj : g_win->sel ()) { - if (obj->vertices () < 2) - continue; - - ulong idx = obj->getIndex (g_curfile); - for (short i = 0; i < obj->vertices(); ++i) { - LDVertex* vert = new LDVertex; - vert->vPosition = obj->vaCoords[i]; - vert->dColor = obj->dColor; - - g_curfile->insertObj (++idx, vert); - ulaIndices.push_back (idx); - paObjs.push_back (vert->clone ()); - } - } - - if (ulaIndices.size() > 0) { - History::addEntry (new AddHistory (ulaIndices, paObjs)); - g_win->refresh (); - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -static void doMoveSelection (const bool bUp) { - vector<LDObject*> objs = g_win->sel (); - - // Get the indices of the objects for history archival - vector<ulong> ulaIndices; - for (LDObject* obj : objs) - ulaIndices.push_back (obj->getIndex (g_curfile)); - - LDObject::moveObjects (objs, bUp); - History::addEntry (new ListMoveHistory (ulaIndices, bUp)); - g_win->buildObjList (); -} - -MAKE_ACTION (moveUp, "Move Up", "arrow-up", "Move the current selection up.", SHIFT (Up)) { - doMoveSelection (true); -} - -MAKE_ACTION (moveDown, "Move Down", "arrow-down", "Move the current selection down.", SHIFT (Down)) { - doMoveSelection (false); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (undo, "Undo", "undo", "Undo a step.", CTRL (Z)) { - History::undo (); -} - -MAKE_ACTION (redo, "Redo", "redo", "Redo a step.", CTRL_SHIFT (Z)) { - History::redo (); -} - -MAKE_ACTION (showHistory, "Edit History", "history", "Show the history dialog.", (0)) { - HistoryDialog dlg; - dlg.exec (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void doMoveObjects (vertex vVector) { - vector<ulong> ulaIndices; - - // Apply the grid values - vVector[X] *= currentGrid ().confs[Grid::X]->value; - vVector[Y] *= currentGrid ().confs[Grid::Y]->value; - vVector[Z] *= currentGrid ().confs[Grid::Z]->value; - - for (LDObject* obj : g_win->sel ()) { - ulaIndices.push_back (obj->getIndex (g_curfile)); - obj->move (vVector); - } - - History::addEntry (new MoveHistory (ulaIndices, vVector)); - g_win->refresh (); -} - -MAKE_ACTION (moveXNeg, "Move -X", "move-x-neg", "Move selected objects negative on the X axis.", KEY (Left)) { - doMoveObjects ({-1, 0, 0}); -} - -MAKE_ACTION (moveYNeg, "Move -Y", "move-y-neg", "Move selected objects negative on the Y axis.", KEY (PageUp)) { - doMoveObjects ({0, -1, 0}); -} - -MAKE_ACTION (moveZNeg, "Move -Z", "move-z-neg", "Move selected objects negative on the Z axis.", KEY (Down)) { - doMoveObjects ({0, 0, -1}); -} - -MAKE_ACTION (moveXPos, "Move +X", "move-x-pos", "Move selected objects positive on the X axis.", KEY (Right)) { - doMoveObjects ({1, 0, 0}); -} - -MAKE_ACTION (moveYPos, "Move +Y", "move-y-pos", "Move selected objects positive on the Y axis.", KEY (PageDown)) { - doMoveObjects ({0, 1, 0}); -} - -MAKE_ACTION (moveZPos, "Move +Z", "move-z-pos", "Move selected objects positive on the Z axis.", KEY (Up)) { - doMoveObjects ({0, 0, 1}); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// invert - reverse winding -// -// NOTE: History management gets a little tricky here. For lines, cond-lines, -// triangles and quads, we edit the object, thus we record an EditHistory. For -// subfiles and radials we create or delete invertnext objects. Since we have -// multiple actions of different types, we store a history entry for each of -// them and wrap them into a ComboHistory, which allows storage of multiple -// simultaneous edits with different type. This is what we ultimately store into -// History. -// ============================================================================= -MAKE_ACTION (invert, "Invert", "invert", "Reverse the winding of given objects.", CTRL_SHIFT (W)) { - std::vector<LDObject*> paSelection = g_win->sel (); - std::vector<HistoryEntry*> paHistory; - - for (LDObject* obj : paSelection) { - // For the objects we end up editing, we store information into these - // variables and we store them into an EditHistory after the switch - // block. Subfile and radial management is stored into the history - // list immediately. - ulong ulHistoryIndex = obj->getIndex (g_curfile); - LDObject* pOldCopy, *pNewCopy; - bool bEdited = false; - - switch (obj->getType ()) { - case LDObject::Line: - case LDObject::CondLine: - { - // For lines, we swap the vertices. I don't think that a - // cond-line's control points need to be swapped, do they? - LDLine* pLine = static_cast<LDLine*> (obj); - vertex vTemp = pLine->vaCoords[0]; - - pOldCopy = pLine->clone (); - pLine->vaCoords[0] = pLine->vaCoords[1]; - pLine->vaCoords[1] = vTemp; - pNewCopy = pLine->clone (); - bEdited = true; - } - break; - - case LDObject::Triangle: - { - // Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1. - // Thus, we swap 1 and 2. - LDTriangle* pTri = static_cast<LDTriangle*> (obj); - vertex vTemp = pTri->vaCoords[1]; - - pOldCopy = pTri->clone (); - pTri->vaCoords[1] = pTri->vaCoords[2]; - pTri->vaCoords[2] = vTemp; - pNewCopy = pTri->clone (); - bEdited = true; - } - break; - - case LDObject::Quad: - { - // Quad: 0 -> 1 -> 2 -> 3 - // rev: 0 -> 3 -> 2 -> 1 - // Thus, we swap 1 and 3. - LDQuad* pQuad = static_cast<LDQuad*> (obj); - vertex vTemp = pQuad->vaCoords[1]; - - pOldCopy = pQuad->clone (); - pQuad->vaCoords[1] = pQuad->vaCoords[3]; - pQuad->vaCoords[3] = vTemp; - pNewCopy = pQuad->clone (); - bEdited = true; - } - break; - - case LDObject::Subfile: - case LDObject::Radial: - { - // Subfiles and radials are inverted when they're prefixed with - // a BFC INVERTNEXT statement. Thus we need to toggle this status. - // For flat primitives it's sufficient that the determinant is - // flipped but I don't have a method for checking flatness yet. - // Food for thought... - - bool inverted = false; - ulong idx = obj->getIndex (g_curfile); - - if (idx > 0) { - LDObject* prev = g_curfile->object (idx - 1); - LDBFC* bfc = dynamic_cast<LDBFC*> (prev); - - if (bfc && bfc->type == LDBFC::InvertNext) { - // Object is prefixed with an invertnext, thus remove it. - paHistory.push_back (new DelHistory ({idx - 1}, {bfc->clone ()})); - - inverted = true; - g_curfile->forgetObject (bfc); - delete bfc; - } - } - - if (!inverted) { - // Not inverted, thus prefix it with a new invertnext. - LDBFC* bfc = new LDBFC (LDBFC::InvertNext); - g_curfile->insertObj (idx, bfc); - - paHistory.push_back (new AddHistory ({idx}, {bfc->clone ()})); - } - } - break; - - default: - break; - } - - // If we edited this object, store the EditHistory based on collected - // information now. - if (bEdited == true) - paHistory.push_back (new EditHistory ({ulHistoryIndex}, {pOldCopy}, {pNewCopy})); - } - - if (paHistory.size () > 0) { - History::addEntry (new ComboHistory (paHistory)); - g_win->refresh (); - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -static void doRotate (const short l, const short m, const short n) { - std::vector<LDObject*> sel = g_win->sel (); - bbox box; - vertex origin; - std::vector<vertex*> queue; - const double angle = (pi * currentGrid ().confs[Grid::Angle]->value) / 360; - - // ref: http://en.wikipedia.org/wiki/Transformation_matrix#Rotation_2 - matrix<3> transform ({ - (l * l * (1 - cos (angle))) + cos (angle), - (m * l * (1 - cos (angle))) - (n * sin (angle)), - (n * l * (1 - cos (angle))) + (m * sin (angle)), - - (l * m * (1 - cos (angle))) + (n * sin (angle)), - (m * m * (1 - cos (angle))) + cos (angle), - (n * m * (1 - cos (angle))) - (l * sin (angle)), - - (l * n * (1 - cos (angle))) - (m * sin (angle)), - (m * n * (1 - cos (angle))) + (l * sin (angle)), - (n * n * (1 - cos (angle))) + cos (angle) - }); - - // Calculate center vertex - for (LDObject* obj : sel) { - if (obj->getType () == LDObject::Subfile) - box << static_cast<LDSubfile*> (obj)->vPosition; - else if (obj->getType () == LDObject::Radial) - box << static_cast<LDRadial*> (obj)->vPosition; - else - box << obj; - } - - origin = box.center (); - - // Apply the above matrix to everything - for (LDObject* obj : sel) { - if (obj->vertices ()) - for (short i = 0; i < obj->vertices (); ++i) - queue.push_back (&obj->vaCoords[i]); - else if (obj->getType () == LDObject::Subfile) { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - - queue.push_back (&ref->vPosition); - ref->mMatrix = ref->mMatrix * transform; - } else if (obj->getType () == LDObject::Radial) { - LDRadial* rad = static_cast<LDRadial*> (obj); - - queue.push_back (&rad->vPosition); - rad->mMatrix = rad->mMatrix * transform; - } else if (obj->getType () == LDObject::Vertex) - queue.push_back (&static_cast<LDVertex*> (obj)->vPosition); - } - - for (vertex* v : queue) { - v->move (-origin); - v->transform (transform, g_origin); - v->move (origin); - } - - g_win->refresh (); -} - -MAKE_ACTION (rotateXPos, "Rotate +X", "rotate-x-pos", "Rotate objects around X axis", CTRL (Right)) { - doRotate (1, 0, 0); -} - -MAKE_ACTION (rotateYPos, "Rotate +Y", "rotate-y-pos", "Rotate objects around Y axis", CTRL (PageDown)) { - doRotate (0, 1, 0); -} - -MAKE_ACTION (rotateZPos, "Rotate +Z", "rotate-z-pos", "Rotate objects around Z axis", CTRL (Up)) { - doRotate (0, 0, 1); -} - -MAKE_ACTION (rotateXNeg, "Rotate -X", "rotate-x-neg", "Rotate objects around X axis", CTRL (Left)) { - doRotate (-1, 0, 0); -} - -MAKE_ACTION (rotateYNeg, "Rotate -Y", "rotate-y-neg", "Rotate objects around Y axis", CTRL (PageUp)) { - doRotate (0, -1, 0); -} - -MAKE_ACTION (rotateZNeg, "Rotate -Z", "rotate-z-neg", "Rotate objects around Z axis", CTRL (Down)) { - doRotate (0, 0, -1); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (roundCoords, "Round Coordinates", "round-coords", "Round coordinates down to 3/4 decimals", (0)) { - setlocale (LC_ALL, "C"); - - for (LDObject* obj : g_win->sel ()) - for (short i = 0; i < obj->vertices (); ++i) - for (const Axis ax : g_Axes) - obj->vaCoords[i][ax] = atof (fmt ("%.3f", obj->vaCoords[i][ax])); - - g_win->refresh (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MAKE_ACTION (uncolorize, "Uncolorize", "uncolorize", "Reduce colors of everything selected to main and edge colors", (0)) { - vector<LDObject*> oldCopies, newCopies; - vector<ulong> indices; - - for (LDObject* obj : g_win->sel ()) { - if (obj->isColored () == false) - continue; - - indices.push_back (obj->getIndex (g_curfile)); - oldCopies.push_back (obj->clone ()); - - obj->dColor = (obj->getType () == LDObject::Line || obj->getType () == LDObject::CondLine) ? edgecolor : maincolor; - newCopies.push_back (obj->clone ()); - } - - if (indices.size () > 0) { - History::addEntry (new EditHistory (indices, oldCopies, newCopies)); - g_win->refresh (); - } -} \ No newline at end of file
--- a/history.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,351 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "history.h" -#include "ldtypes.h" -#include "file.h" -#include "misc.h" -#include "gui.h" - -EXTERN_ACTION (undo) -EXTERN_ACTION (redo) - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -namespace History { - std::vector<HistoryEntry*> s_entries; - static long s_pos = -1; - - // ========================================================================= - void addEntry (HistoryEntry* entry) { - // If there's any entries ahead the current position, we need to - // remove them now - for (ulong i = s_pos + 1; i < s_entries.size(); ++i) { - delete s_entries[i]; - s_entries.erase (s_entries.begin() + i); - } - - s_entries.push_back (entry); - s_pos++; - - updateActions (); - } - - // ========================================================================= - void undo () { - if (s_pos == -1) - return; // nothing to undo - - s_entries[s_pos--]->undo (); - updateActions (); - } - - // ========================================================================= - void redo () { - if (s_pos == (long) s_entries.size () - 1) - return; // nothing to redo; - - s_entries[++s_pos]->redo (); - updateActions (); - } - - // ========================================================================= - void clear () { - for (HistoryEntry* entry : s_entries) - delete entry; - - s_entries.clear (); - s_pos = -1; - updateActions (); - } - - // ========================================================================= - void updateActions () { - ACTION (undo)->setEnabled (s_pos > -1); - ACTION (redo)->setEnabled (s_pos < (long) s_entries.size () - 1); - - // Update the window title as well - g_win->setTitle (); - } - - // ========================================================================= - long pos () { - return s_pos; - } - - // ========================================================================= - std::vector<HistoryEntry*>& entries () { - return s_entries; - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void DelHistory::undo () { - for (ulong i = 0; i < cache.size(); ++i) { - ulong idx = cache.size() - i - 1; - LDObject* obj = cache[idx]->clone (); - g_curfile->insertObj (indices[idx], obj); - } - - g_win->refresh (); -} - -// ============================================================================= -void DelHistory::redo () { - for (ulong i = 0; i < cache.size(); ++i) { - LDObject* obj = g_curfile->m_objs[indices[i]]; - - g_curfile->forgetObject (obj); - delete obj; - } - - g_win->refresh (); -} - -// ============================================================================= -DelHistory::~DelHistory () { - for (LDObject* obj : cache) - delete obj; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void SetColorHistory::undo () { - // Restore colors - for (ulong i = 0; i < ulaIndices.size (); ++i) - g_curfile->m_objs[ulaIndices[i]]->dColor = daColors[i]; - - g_win->refresh (); -} - -void SetColorHistory::redo () { - // Re-set post color - for (ulong i = 0; i < ulaIndices.size (); ++i) - g_curfile->m_objs[ulaIndices[i]]->dColor = dNewColor; - - g_win->refresh (); -} - -SetColorHistory::~SetColorHistory () {} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void EditHistory::undo () { - for (ulong idx : ulaIndices) - g_curfile->object (idx)->replace (paOldObjs[idx]->clone ()); - - g_win->refresh (); -} - -void EditHistory::redo () { - for (ulong idx : ulaIndices) - g_curfile->object (idx)->replace (paNewObjs[idx]->clone ()); - - g_win->refresh (); -} - -EditHistory::~EditHistory () { - for (ulong idx : ulaIndices) { - delete paOldObjs[idx]; - delete paNewObjs[idx]; - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -std::vector<LDObject*> ListMoveHistory::getObjects (short ofs) { - std::vector<LDObject*> objs; - - for (ulong idx : ulaIndices) - objs.push_back (g_curfile->m_objs[idx + ofs]); - - return objs; -} - -void ListMoveHistory::undo () { - std::vector<LDObject*> objs = getObjects (bUp ? -1 : 1); - LDObject::moveObjects (objs, !bUp); - g_win->buildObjList (); -} - -void ListMoveHistory::redo () { - std::vector<LDObject*> objs = getObjects (0); - LDObject::moveObjects (objs, bUp); - g_win->buildObjList (); -} - -ListMoveHistory::~ListMoveHistory() {} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -AddHistory::~AddHistory () { - for (LDObject* pObj : paObjs) - delete pObj; -} - -void AddHistory::undo () { - for (ulong i = 0; i < paObjs.size(); ++i) { - ulong idx = ulaIndices[ulaIndices.size() - i - 1]; - LDObject* obj = g_curfile->m_objs[idx]; - - g_curfile->forgetObject (obj); - delete obj; - } - - g_win->refresh (); -} - -void AddHistory::redo () { - for (ulong i = 0; i < paObjs.size(); ++i) { - ulong idx = ulaIndices[i]; - LDObject* obj = paObjs[i]->clone (); - - g_curfile->insertObj (idx, obj); - } - - g_win->refresh (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -QuadSplitHistory::~QuadSplitHistory () { - for (LDQuad* pQuad : paQuads) - delete pQuad; -} - -void QuadSplitHistory::undo () { - for (ulong i = 0; i < paQuads.size(); ++i) { - // The quad was replaced by the first triangle and a second one was - // added after it. Thus, we remove the second one here and replace - // the first with a copy of the quad. - ulong idx = ulaIndices[i]; - - LDTriangle* tri1 = static_cast<LDTriangle*> (g_curfile->m_objs[idx]), - *tri2 = static_cast<LDTriangle*> (g_curfile->m_objs[idx + 1]); - LDQuad* pCopy = paQuads[i]->clone (); - - tri1->replace (pCopy); - g_curfile->forgetObject (tri2); - delete tri2; - } - - g_win->refresh (); -} - -void QuadSplitHistory::redo () { - for (long i = paQuads.size() - 1; i >= 0; --i) { - ulong idx = ulaIndices[i]; - - LDQuad* pQuad = static_cast<LDQuad*> (g_curfile->m_objs[idx]); - std::vector<LDTriangle*> paTriangles = pQuad->splitToTriangles (); - - g_curfile->m_objs[idx] = paTriangles[0]; - g_curfile->insertObj (idx + 1, paTriangles[1]); - delete pQuad; - } - - g_win->refresh (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void InlineHistory::undo () { - for (long i = ulaBitIndices.size() - 1; i >= 0; --i) { - LDObject* obj = g_curfile->m_objs [ulaBitIndices[i]]; - g_curfile->forgetObject (obj); - delete obj; - } - - for (ulong i = 0; i < ulaRefIndices.size(); ++i) { - LDSubfile* obj = paRefs[i]->clone (); - g_curfile->insertObj (ulaRefIndices[i], obj); - } - - g_win->refresh (); -} - -void InlineHistory::redo () { - for (long i = ulaRefIndices.size() - 1; i >= 0; --i) { - ulong idx = ulaRefIndices[i]; - - assert (g_curfile->object (idx)->getType () == LDObject::Subfile); - LDSubfile* ref = static_cast<LDSubfile*> (g_curfile->object (idx)); - vector<LDObject*> objs = ref->inlineContents (bDeep, false); - - for (LDObject* obj : objs) - g_curfile->insertObj (idx++, obj); - - g_curfile->forgetObject (ref); - delete ref; - } - - g_win->refresh (); -} - -InlineHistory::~InlineHistory () { - for (LDSubfile* ref : paRefs) - delete ref; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -MoveHistory::~MoveHistory () {} - -void MoveHistory::undo () { - const vertex vInverse = -vVector; - - for (ulong i : ulaIndices) - g_curfile->object (i)->move (vInverse); - g_win->refresh (); -} - -void MoveHistory::redo () { - for (ulong i : ulaIndices) - g_curfile->object (i)->move (vVector); - g_win->refresh (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -ComboHistory::~ComboHistory () { - for (HistoryEntry* pEntry : paEntries) - delete pEntry; -} - -void ComboHistory::undo () { - for (long i = paEntries.size() - 1; i >= 0; --i) { - HistoryEntry* pEntry = paEntries[i]; - pEntry->undo (); - } -} - -void ComboHistory::redo () { - for (HistoryEntry* pEntry : paEntries) - pEntry->redo (); -} \ No newline at end of file
--- a/history.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,215 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef HISTORY_H -#define HISTORY_H - -#include "common.h" -#include "ldtypes.h" - -#define IMPLEMENT_HISTORY_TYPE(N) \ - virtual ~N##History (); \ - virtual void undo (); \ - virtual void redo (); \ - virtual HistoryType type () { return HISTORY_##N; } - -// ============================================================================= -enum HistoryType { - HISTORY_Del, - HISTORY_SetColor, - HISTORY_Edit, - HISTORY_ListMove, - HISTORY_Add, - HISTORY_QuadSplit, - HISTORY_Inline, - HISTORY_Move, - HISTORY_Combo, -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class HistoryEntry { -public: - virtual void undo () {} - virtual void redo () {} - virtual ~HistoryEntry () {} - virtual HistoryType type () { return (HistoryType)(0); } -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class DelHistory : public HistoryEntry { -public: - enum Type { - Cut, // were deleted with a cut operation - Other, // were deleted witout specific reason - }; - - IMPLEMENT_HISTORY_TYPE (Del) - - vector<ulong> indices; - vector<LDObject*> cache; - const Type eType; - - DelHistory (vector<ulong> indices, vector<LDObject*> cache, const Type eType = Other) : - indices (indices), cache (cache), eType (eType) {} -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class SetColorHistory : public HistoryEntry { -public: - IMPLEMENT_HISTORY_TYPE (SetColor) - - vector<ulong> ulaIndices; - vector<short> daColors; - short dNewColor; - - SetColorHistory (vector<ulong> ulaIndices, vector<short> daColors, short dNewColor) : - ulaIndices (ulaIndices), daColors (daColors), dNewColor (dNewColor) {} -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class EditHistory : public HistoryEntry { -public: - IMPLEMENT_HISTORY_TYPE (Edit) - - const std::vector<ulong> ulaIndices; - const std::vector<LDObject*> paOldObjs, paNewObjs; - - EditHistory (std::vector<ulong> ulaIndices, std::vector<LDObject*> paOldObjs, - std::vector<LDObject*> paNewObjs) : - ulaIndices (ulaIndices), paOldObjs (paOldObjs), paNewObjs (paNewObjs) {} -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class ListMoveHistory : public HistoryEntry { -public: - IMPLEMENT_HISTORY_TYPE (ListMove) - - std::vector<ulong> ulaIndices; - bool bUp; - - std::vector<LDObject*> getObjects (short ofs); - ListMoveHistory (vector<ulong> ulaIndices, const bool bUp) : - ulaIndices (ulaIndices), bUp (bUp) {} -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class AddHistory : public HistoryEntry { -public: - enum Type { - Other, // was "just added" - Paste, // was added through a paste operation - }; - - IMPLEMENT_HISTORY_TYPE (Add) - - std::vector<ulong> ulaIndices; - std::vector<LDObject*> paObjs; - const Type eType; - - AddHistory (std::vector<ulong> ulaIndices, std::vector<LDObject*> paObjs, - const Type eType = Other) : - ulaIndices (ulaIndices), paObjs (paObjs), eType (eType) {} -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class QuadSplitHistory : public HistoryEntry { -public: - IMPLEMENT_HISTORY_TYPE (QuadSplit) - - std::vector<ulong> ulaIndices; - std::vector<LDQuad*> paQuads; - - QuadSplitHistory (std::vector<ulong> ulaIndices, std::vector<LDQuad*> paQuads) : - ulaIndices (ulaIndices), paQuads (paQuads) {} -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class InlineHistory : public HistoryEntry { -public: - IMPLEMENT_HISTORY_TYPE (Inline) - - const std::vector<ulong> ulaBitIndices, ulaRefIndices; - const std::vector<LDSubfile*> paRefs; - const bool bDeep; - - InlineHistory (const std::vector<ulong> ulaBitIndices, const std::vector<ulong> ulaRefIndices, - const std::vector<LDSubfile*> paRefs, const bool bDeep) : - ulaBitIndices (ulaBitIndices), ulaRefIndices (ulaRefIndices), paRefs (paRefs), bDeep (bDeep) {} -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class MoveHistory : public HistoryEntry { -public: - IMPLEMENT_HISTORY_TYPE (Move) - - const std::vector<ulong> ulaIndices; - const vertex vVector; - - MoveHistory (const std::vector<ulong> ulaIndices, const vertex vVector) : - ulaIndices (ulaIndices), vVector (vVector) {} -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class ComboHistory : public HistoryEntry { -public: - IMPLEMENT_HISTORY_TYPE (Combo) - - std::vector<HistoryEntry*> paEntries; - - ComboHistory (std::vector<HistoryEntry*> paEntries) : paEntries (paEntries) {} - - ComboHistory& operator<< (HistoryEntry* entry) { - paEntries.push_back (entry); - return *this; - } -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -namespace History { - void addEntry (HistoryEntry* entry); - void undo (); - void redo (); - void clear (); - void updateActions (); - long pos (); - std::vector<HistoryEntry*>& entries (); -}; - -#endif // HISTORY_H \ No newline at end of file
--- a/ldforge.pro Wed May 08 14:57:48 2013 +0300 +++ b/ldforge.pro Wed May 08 15:19:06 2013 +0300 @@ -11,56 +11,8 @@ RESOURCES = ldforge.qrc MOC_DIR = ./autogen/ RCC_DIR = ./autogen/ - -# Input -HEADERS += bbox.h \ - colors.h \ - common.h \ - config.h \ - extprogs.h \ - file.h \ - gldraw.h \ - gui.h \ - history.h \ - ldtypes.h \ - misc.h \ - radiobox.h \ - str.h \ - types.h \ - zz_aboutDialog.h \ - zz_addObjectDialog.h \ - zz_colorSelectDialog.h \ - zz_configDialog.h \ - zz_ldrawPathDialog.h \ - zz_historyDialog.h \ - zz_newPartDialog.h \ - zz_setContentsDialog.h - -SOURCES += \ - config.cpp \ - gui.cpp \ - gldraw.cpp \ - bbox.cpp \ - colors.cpp \ - extprogs.cpp \ - file.cpp \ - gui_actions.cpp \ - gui_editactions.cpp \ - history.cpp \ - ldtypes.cpp \ - main.cpp \ - misc.cpp \ - radiobox.cpp \ - str.cpp \ - types.cpp \ - zz_aboutDialog.cpp \ - zz_addObjectDialog.cpp \ - zz_colorSelectDialog.cpp \ - zz_configDialog.cpp \ - zz_ldrawPathDialog.cpp \ - zz_historyDialog.cpp \ - zz_newPartDialog.cpp \ - zz_setContentsDialog.cpp +SOURCES = ./src/*.cpp +HEADERS = ./src/*.h QMAKE_CXXFLAGS += -std=c++0x QT += opengl
--- a/ldtypes.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,675 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "common.h" -#include "ldtypes.h" -#include "file.h" -#include "misc.h" -#include "gui.h" - -char const* g_saObjTypeNames[] = { - "subfile", - "radial", - "quadrilateral", - "triangle", - "line", - "condline", - "vertex", - "bfc", - "comment", - "unknown", - "empty", - "unidentified", -}; - -// Should probably get rid of this array sometime -char const* g_saObjTypeIcons[] = { - "subfile", - "radial", - "quad", - "triangle", - "line", - "condline", - "vertex", - "bfc", - "comment", - "error", - "empty", - "error", -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -// LDObject constructors -LDObject::LDObject () { - qObjListEntry = null; - parent = null; - m_hidden = false; -} - -LDGibberish::LDGibberish (str _zContent, str _zReason) { - zContents = _zContent; - zReason = _zReason; -} - -// ============================================================================= -str LDComment::getContents () { - return fmt ("0 %s", text.chars ()); -} - -str LDSubfile::getContents () { - str val = fmt ("1 %d %s ", dColor, vPosition.stringRep (false).chars ()); - val += mMatrix.stringRep (); - val += ' '; - val += zFileName; - return val; -} - -str LDLine::getContents () { - str val = fmt ("2 %d", dColor); - - for (ushort i = 0; i < 2; ++i) - val.appendformat (" %s", vaCoords[i].stringRep (false).chars ()); - - return val; -} - -str LDTriangle::getContents () { - str val = fmt ("3 %d", dColor); - - for (ushort i = 0; i < 3; ++i) - val.appendformat (" %s", vaCoords[i].stringRep (false).chars ()); - - return val; -} - -str LDQuad::getContents () { - str val = fmt ("4 %d", dColor); - - for (ushort i = 0; i < 4; ++i) - val.appendformat (" %s", vaCoords[i].stringRep (false).chars ()); - - return val; -} - -str LDCondLine::getContents () { - str val = fmt ("5 %d", dColor); - - // Add the coordinates - for (ushort i = 0; i < 4; ++i) - val.appendformat (" %s", vaCoords[i].stringRep (false).chars ()); - - return val; -} - -str LDGibberish::getContents () { - return zContents; -} - -str LDVertex::getContents () { - return fmt ("0 !LDFORGE VERTEX %d %s", dColor, vPosition.stringRep (false).chars()); -} - -str LDEmpty::getContents () { - return str (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -const char* LDBFC::statements[] = { - "CERTIFY CCW", - "CCW", - "CERTIFY CW", - "CW", - "NOCERTIFY", - "INVERTNEXT", -}; - -str LDBFC::getContents () { - return fmt ("0 BFC %s", LDBFC::statements[type]); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -vector<LDTriangle*> LDQuad::splitToTriangles () { - // Create the two triangles based on this quadrilateral: - // 0---3 0---3 3 - // | | | / /| - // | | = | / / | - // | | |/ / | - // 1---2 1 1---2 - LDTriangle* tri1 = new LDTriangle; - tri1->vaCoords[0] = vaCoords[0]; - tri1->vaCoords[1] = vaCoords[1]; - tri1->vaCoords[2] = vaCoords[3]; - - LDTriangle* tri2 = new LDTriangle; - tri2->vaCoords[0] = vaCoords[1]; - tri2->vaCoords[1] = vaCoords[2]; - tri2->vaCoords[2] = vaCoords[3]; - - // The triangles also inherit the quad's color - tri1->dColor = tri2->dColor = dColor; - - vector<LDTriangle*> triangles; - triangles.push_back (tri1); - triangles.push_back (tri2); - return triangles; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void LDObject::replace (LDObject* replacement) { - // Replace all instances of the old object with the new object - for (LDObject*& obj : g_curfile->m_objs) - if (obj == this) - obj = replacement; - - // Remove the old object - delete this; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void LDObject::swap (LDObject* other) { - for (LDObject*& obj : g_curfile->m_objs) { - if (obj == this) - obj = other; - else if (obj == other) - obj = this; - } -} - -LDLine::LDLine (vertex v1, vertex v2) { - vaCoords[0] = v1; - vaCoords[1] = v2; -} - -LDObject::~LDObject () { - // Remove this object from the selection array if it is there. - for (ulong i = 0; i < g_win->sel ().size(); ++i) - if (g_win->sel ()[i] == this) - g_win->sel ().erase (g_win->sel ().begin() + i); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -static void transformObject (LDObject* obj, matrix<3> transform, vertex pos, short parentcolor) { - switch (obj->getType()) { - case LDObject::Line: - case LDObject::CondLine: - case LDObject::Triangle: - case LDObject::Quad: - for (short i = 0; i < obj->vertices (); ++i) - obj->vaCoords[i].transform (transform, pos); - break; - - case LDObject::Subfile: - { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - - matrix<3> newMatrix = transform * ref->mMatrix; - ref->vPosition.transform (transform, pos); - ref->mMatrix = newMatrix; - } - break; - - default: - break; - } - - if (obj->dColor == maincolor) - obj->dColor = parentcolor; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -vector<LDObject*> LDSubfile::inlineContents (bool bDeepInline, bool bCache) { - vector<LDObject*> objs, cache; - - // If we have this cached, just clone that - if (bDeepInline && pFile->m_objCache.size ()) { - for (LDObject* obj : pFile->m_objCache) - objs.push_back (obj->clone ()); - } else { - if (!bDeepInline) - bCache = false; - - for (LDObject* obj : pFile->m_objs) { - // Skip those without schemantic meaning - switch (obj->getType ()) { - case LDObject::Comment: - case LDObject::Empty: - case LDObject::Gibberish: - case LDObject::Unidentified: - case LDObject::Vertex: - continue; - - case LDObject::BFC: - // Filter non-INVERTNEXT statements - if (static_cast<LDBFC*> (obj)->type != LDBFC::InvertNext) - continue; - break; - - default: - break; - } - - // Got another sub-file reference, inline it if we're deep-inlining. If not, - // just add it into the objects normally. Also, we only cache immediate - // subfiles and this is not one. Yay, recursion! - if (bDeepInline && obj->getType() == LDObject::Subfile) { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - - vector<LDObject*> otherobjs = ref->inlineContents (true, false); - - for (LDObject* otherobj : otherobjs) { - // Cache this object, if desired - if (bCache) - cache.push_back (otherobj->clone ()); - - objs.push_back (otherobj); - } - } else { - if (bCache) - cache.push_back (obj->clone ()); - - objs.push_back (obj->clone ()); - } - } - - if (bCache) - pFile->m_objCache = cache; - } - - // Transform the objects - for (LDObject* obj : objs) { - // Set the parent now so we know what inlined this. - obj->parent = this; - - transformObject (obj, mMatrix, vPosition, dColor); - } - - return objs; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -long LDObject::getIndex (OpenFile* pFile) { - for (ulong i = 0; i < pFile->m_objs.size(); ++i) - if (pFile->m_objs[i] == this) - return i; - - return -1; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void LDObject::moveObjects (std::vector<LDObject*> objs, const bool bUp) { - // If we move down, we need to iterate the array in reverse order. - const long start = bUp ? 0 : (objs.size() - 1); - const long end = bUp ? objs.size() : -1; - const long incr = bUp ? 1 : -1; - - for (long i = start; i != end; i += incr) { - LDObject* obj = objs[i]; - - const long lIndex = obj->getIndex (g_curfile), - lTarget = lIndex + (bUp ? -1 : 1); - - if ((bUp == true and lIndex == 0) or - (bUp == false and lIndex == (long)(g_curfile->m_objs.size() - 1))) - { - // One of the objects hit the extrema. If this happens, this should be the first - // object to be iterated on. Thus, nothing has changed yet and it's safe to just - // abort the entire operation. - assert (i == start); - return; - } - - obj->swap (g_curfile->m_objs[lTarget]); - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -str LDObject::objectListContents (const std::vector<LDObject*>& objs) { - bool firstDetails = true; - str text = ""; - - if (objs.size() == 0) - return "nothing"; // :) - - for (long i = 0; i < LDObject::NumTypes; ++i) { - LDObject::Type objType = (LDObject::Type) i; - ulong objCount = 0; - - for (LDObject* obj : objs) - if (obj->getType() == objType) - objCount++; - - if (objCount == 0) - continue; - - if (!firstDetails) - text += ", "; - - str noun = fmt ("%s%s", g_saObjTypeNames[objType], PLURAL (objCount)); - - // Plural of "vertex" is "vertices". Stupid English. - if (objType == LDObject::Vertex && objCount != 1) - noun = "vertices"; - - text.appendformat ("%lu %s", objCount, noun.chars ()); - firstDetails = false; - } - - return text; -} - -// ============================================================================= -LDObject* LDObject::topLevelParent () { - if (!parent) - return null; - - LDObject* it = this; - - while (it->parent) - it = it->parent; - - return it; -} - - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void LDObject::move (vertex vVector) { vVector = vVector; /* to shut up GCC */ } -void LDEmpty::move (vertex vVector) { vVector = vVector; } -void LDBFC::move (vertex vVector) { vVector = vVector; } -void LDComment::move (vertex vVector) { vVector = vVector; } -void LDGibberish::move (vertex vVector) { vVector = vVector; } - -void LDVertex::move (vertex vVector) { - vPosition += vVector; -} - -void LDSubfile::move (vertex vVector) { - vPosition += vVector; -} - -void LDRadial::move (vertex vVector) { - vPosition += vVector; -} - -void LDLine::move (vertex vVector) { - for (short i = 0; i < 2; ++i) - vaCoords[i] += vVector; -} - -void LDTriangle::move (vertex vVector) { - for (short i = 0; i < 3; ++i) - vaCoords[i] += vVector; -} - -void LDQuad::move (vertex vVector) { - for (short i = 0; i < 4; ++i) - vaCoords[i] += vVector; -} - -void LDCondLine::move (vertex vVector) { - for (short i = 0; i < 4; ++i) - vaCoords[i] += vVector; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -static char const* g_saRadialTypeNames[] = { - "Circle", - "Cylinder", - "Disc", - "Disc Negative", - "Ring", - "Cone", - null -}; - -char const* LDRadial::radialTypeName () { - return g_saRadialTypeNames[eRadialType]; -} - -char const* LDRadial::radialTypeName (const LDRadial::Type eType) { - return g_saRadialTypeNames[eType]; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -std::vector<LDObject*> LDRadial::decompose (bool bTransform) { - std::vector<LDObject*> paObjects; - - for (short i = 0; i < dSegments; ++i) { - double x0 = cos ((i * 2 * pi) / dDivisions), - x1 = cos (((i + 1) * 2 * pi) / dDivisions), - z0 = sin ((i * 2 * pi) / dDivisions), - z1 = sin (((i + 1) * 2 * pi) / dDivisions); - - switch (eRadialType) { - case LDRadial::Circle: - { - vertex v0 (x0, 0.0f, z0), - v1 (x1, 0.0f, z1); - - if (bTransform) { - v0.transform (mMatrix, vPosition); - v1.transform (mMatrix, vPosition); - } - - LDLine* pLine = new LDLine; - pLine->vaCoords[0] = v0; - pLine->vaCoords[1] = v1; - pLine->dColor = edgecolor; - pLine->parent = this; - - paObjects.push_back (pLine); - } - break; - - case LDRadial::Cylinder: - case LDRadial::Ring: - case LDRadial::Cone: - { - double x2, x3, z2, z3; - double y0, y1, y2, y3; - - if (eRadialType == LDRadial::Cylinder) { - x2 = x1; - x3 = x0; - z2 = z1; - z3 = z0; - - y0 = y1 = 0.0f; - y2 = y3 = 1.0f; - } else { - x2 = x1 * (dRingNum + 1); - x3 = x0 * (dRingNum + 1); - z2 = z1 * (dRingNum + 1); - z3 = z0 * (dRingNum + 1); - - x0 *= dRingNum; - x1 *= dRingNum; - z0 *= dRingNum; - z1 *= dRingNum; - - if (eRadialType == LDRadial::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); - - if (bTransform) { - v0.transform (mMatrix, vPosition); - v1.transform (mMatrix, vPosition); - v2.transform (mMatrix, vPosition); - v3.transform (mMatrix, vPosition); - } - - LDQuad* pQuad = new LDQuad; - pQuad->vaCoords[0] = v0; - pQuad->vaCoords[1] = v1; - pQuad->vaCoords[2] = v2; - pQuad->vaCoords[3] = v3; - pQuad->dColor = dColor; - pQuad->parent = this; - - paObjects.push_back (pQuad); - } - break; - - case LDRadial::Disc: - case LDRadial::DiscNeg: - { - double x2, z2; - - if (eRadialType == LDRadial::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); - - if (bTransform) { - v0.transform (mMatrix, vPosition); - v1.transform (mMatrix, vPosition); - v2.transform (mMatrix, vPosition); - } - - LDTriangle* pSeg = new LDTriangle; - pSeg->vaCoords[0] = v0; - pSeg->vaCoords[1] = v1; - pSeg->vaCoords[2] = v2; - pSeg->dColor = dColor; - pSeg->parent = this; - - paObjects.push_back (pSeg); - } - break; - - default: - break; - } - } - - return paObjects; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -str LDRadial::getContents () { - return fmt ("0 !LDFORGE RADIAL %s %d %d %d %d %s %s", - str (radialTypeName()).toupper ().strip (' ').chars (), - dColor, dSegments, dDivisions, dRingNum, - vPosition.stringRep (false).chars(), mMatrix.stringRep().chars()); -} - -char const* g_saRadialNameRoots[] = { - "edge", - "cyli", - "disc", - "ndis", - "ring", - "cone", - null -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -str LDRadial::makeFileName () { - short numer = dSegments, - denom = dDivisions; - - // Simplify the fractional part, but the denominator must be at least 4. - simplify (numer, denom); - - if (denom < 4) { - const short factor = (4 / denom); - - numer *= factor; - denom *= factor; - } - - // Compose some general information: prefix, fraction, root, ring number - str prefix = (dDivisions == 16) ? "" : fmt ("%d/", dDivisions); - str frac = fmt ("%d-%d", numer, denom); - str root = g_saRadialNameRoots[eRadialType]; - str ringNum = (eRadialType == Ring || eRadialType == Cone) ? fmt ("%d", dRingNum) : ""; - - // Truncate the root if necessary (7-16rin4.dat for instance). - // However, always keep the root at least 2 characters. - short extra = (~frac + ~ringNum + ~root) - 8; - root -= min<short> (max<short> (extra, 0), 2); - - // Stick them all together and return the result. - return fmt ("%s%s%s%s", prefix.chars(), frac.chars (), root.chars (), ringNum.chars ()); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -#define CHECK_FOR_OBJ(N) \ - if (type == LDObject::N) \ - return new LD##N; -LDObject* LDObject::getDefault (const LDObject::Type type) { - CHECK_FOR_OBJ (Comment) - CHECK_FOR_OBJ (BFC) - CHECK_FOR_OBJ (Line) - CHECK_FOR_OBJ (CondLine) - CHECK_FOR_OBJ (Radial) - CHECK_FOR_OBJ (Subfile) - CHECK_FOR_OBJ (Triangle) - CHECK_FOR_OBJ (Quad) - CHECK_FOR_OBJ (Empty) - CHECK_FOR_OBJ (BFC) - CHECK_FOR_OBJ (Gibberish) - CHECK_FOR_OBJ (Vertex) - return null; -} \ No newline at end of file
--- a/ldtypes.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,387 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef LDTYPES_H -#define LDTYPES_H - -#include "common.h" -#include "types.h" - -#define IMPLEMENT_LDTYPE(T, NUMVERTS) \ - LD##T () {} \ - virtual ~LD##T () {} \ - virtual LDObject::Type getType () const { \ - return LDObject::T; \ - } \ - virtual str getContents (); \ - virtual LD##T* clone () { \ - return new LD##T (*this); \ - } \ - virtual void move (vertex vVector); \ - virtual short vertices () const { return NUMVERTS; } \ - -#define LDOBJ_SETCOLORED(V) virtual bool isColored () const { return V; } -#define LDOBJ_COLORED LDOBJ_SETCOLORED (true) -#define LDOBJ_UNCOLORED LDOBJ_SETCOLORED (false) - -#define LDOBJ_CUSTOM_SCHEMANTIC virtual bool isSchemantic () const -#define LDOBJ_SCHEMANTIC LDOBJ_CUSTOM_SCHEMANTIC { return true; } -#define LDOBJ_NON_SCHEMANTIC LDOBJ_CUSTOM_SCHEMANTIC { return false; } - -class QListWidgetItem; -class LDSubfile; - -// ============================================================================= -// LDObject -// -// Base class object for all LD* types. Each LDObject represents a single line -// in the LDraw code file. The virtual method getType returns an enumerator -// which is a token of the object's type. The object can be casted into -// sub-classes based on this enumerator. -// ============================================================================= -class LDObject { -public: - // Object type codes. Codes are sorted in order of significance. - enum Type { - Subfile, // Object represents a sub-file reference - Radial, // Object represents a generic radial - Quad, // Object represents a quadrilateral - Triangle, // Object represents a triangle - Line, // Object represents a line - CondLine, // Object represents a conditional line - Vertex, // Object is a vertex, LDForge extension object - BFC, // Object represents a BFC statement - Comment, // Object represents a comment - Gibberish, // Object is the result of failed parsing - Empty, // Object represents an empty line - Unidentified, // Object is an uninitialized (SHOULD NEVER HAPPEN) - NumTypes // Amount of object types - }; - - LDObject (); - virtual ~LDObject (); - - // Index (i.e. line number) of this object - long getIndex (OpenFile* pFile); - - // Color used by this object. Comments, gibberish and empty entries - // do not use this field. - short dColor; - - // OpenGL list for this object - uint uGLList, uGLPickList; - - // Vertices of this object - vertex vaCoords[4]; - - // Object this object was referenced from, if any - LDObject* parent; - - // Type enumerator of this object - virtual LDObject::Type getType () const { - return LDObject::Unidentified; - }; - - // A string that represents this line - virtual str getContents () { - return ""; - } - - // Creates a new LDObject identical to this one and returns a pointer to it. - virtual LDObject* clone () { - return new LDObject (*this); - } - - // Replace this LDObject with another LDObject. This method deletes the - // object and any pointers to it become invalid. - void replace (LDObject* replacement); - - // Swap this object with another. - void swap (LDObject* other); - - // Moves this object using the given vertex as a movement vector - virtual void move (vertex vVector); - - // What object in the current file ultimately references this? - LDObject* topLevelParent (); - - // Number of vertices this object has - virtual short vertices () const { return 0; } - - // Is this object colored? - virtual bool isColored () const { return false; } - - // Does this object have meaning in the part model? - virtual bool isSchemantic () const { return false; } - - // Returns a sample object by the given value - static LDObject* getDefault (const LDObject::Type type); - - static void moveObjects (std::vector<LDObject*> objs, const bool bUp); - static str objectListContents (const std::vector<LDObject*>& objs); - - // Object list entry for this object - QListWidgetItem* qObjListEntry; - - bool hidden () const { return m_hidden; } - void setHidden (const bool hidden) { m_hidden = hidden; } - -private: - bool m_hidden; -}; - -// ============================================================================= -// LDGibberish -// -// Represents a line in the LDraw file that could not be properly parsed. It is -// represented by a (!) ERROR in the code view. It exists for the purpose of -// allowing garbage lines be debugged and corrected within LDForge. The member -// zContent contains the contents of the unparsable line. -// ============================================================================= -class LDGibberish : public LDObject { -public: - IMPLEMENT_LDTYPE (Gibberish, 0) - LDOBJ_UNCOLORED - LDOBJ_SCHEMANTIC - - LDGibberish (str _zContent, str _zReason); - - // Content of this unknown line - str zContents; - - // Why is this gibberish? - str zReason; -}; - -// ============================================================================= -// LDEmptyLine -// -// Represents an empty line in the LDraw code file. -// ============================================================================= -class LDEmpty : public LDObject { -public: - IMPLEMENT_LDTYPE (Empty, 0) - LDOBJ_UNCOLORED - LDOBJ_NON_SCHEMANTIC -}; - -// ============================================================================= -// LDComment -// -// Represents a code-0 comment in the LDraw code file. Member zText contains -// the text of the comment. -// ============================================================================= -class LDComment : public LDObject { -public: - IMPLEMENT_LDTYPE (Comment, 0) - LDOBJ_UNCOLORED - LDOBJ_NON_SCHEMANTIC - - LDComment (str zText) : text (zText) {} - - str text; // The text of this comment -}; - -// ============================================================================= -// LDBFC -// -// Represents a 0 BFC statement in the LDraw code. eStatement contains the type -// of this statement. -// ============================================================================= -class LDBFC : public LDComment { -public: - enum Type { - CertifyCCW, - CCW, - CertifyCW, - CW, - NoCertify, // Winding becomes disabled (0 BFC NOCERTIFY) - InvertNext, // Winding is inverted for next object (0 BFC INVERTNEXT) - NumStatements - }; - - IMPLEMENT_LDTYPE (BFC, 0) - LDOBJ_UNCOLORED - LDOBJ_CUSTOM_SCHEMANTIC { return (type == InvertNext); } - - LDBFC (const LDBFC::Type eType) : type (eType) {} - - // Statement strings - static const char* statements[]; - - Type type; -}; - -// ============================================================================= -// LDSubfile -// -// Represents a single code-1 subfile reference. -// ============================================================================= -class LDSubfile : public LDObject { -public: - IMPLEMENT_LDTYPE (Subfile, 0) - LDOBJ_COLORED - LDOBJ_SCHEMANTIC - - vertex vPosition; // Position of the subpart (FIXME: should get rid of this) - matrix<3> mMatrix; // Transformation matrix for the subpart - str zFileName; // Filename of the subpart - OpenFile* pFile; // Pointer to opened file for this subfile. null if unopened. - - // Inlines this subfile. Note that return type is an array of heap-allocated - // LDObject-clones, they must be deleted one way or another. - std::vector<LDObject*> inlineContents (bool bDeepInline, bool bCache); -}; - -// ============================================================================= -// LDLine -// -// Represents a single code-2 line in the LDraw code file. v0 and v1 are the end -// points of the line. The line is colored with dColor unless uncolored mode is -// set. -// ============================================================================= -class LDLine : public LDObject { -public: - IMPLEMENT_LDTYPE (Line, 2) - LDOBJ_COLORED - LDOBJ_SCHEMANTIC - - LDLine (vertex v1, vertex v2); -}; - -// ============================================================================= -// LDCondLine -// -// Represents a single code-5 conditional line. The end-points v0 and v1 are -// inherited from LDLine, c0 and c1 are the control points of this line. -// ============================================================================= -class LDCondLine : public LDLine { -public: - IMPLEMENT_LDTYPE (CondLine, 4) - LDOBJ_COLORED - LDOBJ_SCHEMANTIC -}; - -// ============================================================================= -// LDTriangle -// -// Represents a single code-3 triangle in the LDraw code file. Vertices v0, v1 -// and v2 contain the end-points of this triangle. dColor is the color the -// triangle is colored with. -// ============================================================================= -class LDTriangle : public LDObject { -public: - IMPLEMENT_LDTYPE (Triangle, 3) - LDOBJ_COLORED - LDOBJ_SCHEMANTIC - - LDTriangle (vertex _v0, vertex _v1, vertex _v2) { - vaCoords[0] = _v0; - vaCoords[1] = _v1; - vaCoords[2] = _v2; - } -}; - -// ============================================================================= -// LDQuad -// -// Represents a single code-4 quadrilateral. v0, v1, v2 and v3 are the end points -// of the quad, dColor is the color used for the quad. -// ============================================================================= -class LDQuad : public LDObject { -public: - IMPLEMENT_LDTYPE (Quad, 4) - LDOBJ_COLORED - LDOBJ_SCHEMANTIC - - // Split this quad into two triangles (note: heap-allocated) - vector<LDTriangle*> splitToTriangles (); -}; - -// ============================================================================= -// LDVertex -// -// The vertex is an LDForce-specific extension which represents a single -// vertex which can be used as a parameter to tools or to store coordinates -// with. Vertices are a part authoring tool and they should not appear in -// finished parts. -// ============================================================================= -class LDVertex : public LDObject { -public: - IMPLEMENT_LDTYPE (Vertex, 0) // TODO: move vPosition to vaCoords[0] - LDOBJ_COLORED - LDOBJ_NON_SCHEMANTIC - - vertex vPosition; -}; - -// ============================================================================= -// LDRadial -// -// The generic radial primitive (radial for short) is another LDforge-specific -// extension which represents an arbitrary circular primitive. Radials can appear -// as circles, cylinders, rings, cones, discs and disc negatives; the point is to -// allow part authors to add radial primitives to parts without much hassle about -// non-existant primitive parts. -// ============================================================================= -class LDRadial : public LDObject { -public: - enum Type { - Circle, - Cylinder, - Disc, - DiscNeg, - Ring, - Cone, - NumTypes - }; - - IMPLEMENT_LDTYPE (Radial, 0) - LDOBJ_COLORED - LDOBJ_SCHEMANTIC - - LDRadial::Type eRadialType; - vertex vPosition; - matrix<3> mMatrix; - short dDivisions, dSegments, dRingNum; - - LDRadial (LDRadial::Type eRadialType, vertex vPosition, matrix<3> mMatrix, - short dDivisions, short dSegments, short dRingNum) : - eRadialType (eRadialType), vPosition (vPosition), mMatrix (mMatrix), - dDivisions (dDivisions), dSegments (dSegments), dRingNum (dRingNum) {} - - // Returns a set of objects that provide the equivalent of this radial. - // Note: objects are heap-allocated. - std::vector<LDObject*> decompose (bool bTransform); - - // Compose a file name for this radial. - str makeFileName (); - - char const* radialTypeName (); - static char const* radialTypeName (const LDRadial::Type eType); -}; - -// ============================================================================= -// Object type names. Pass the return value of getType as the index to get a -// string representation of the object's type. -extern const char* g_saObjTypeNames[]; - -// Icons for these types -extern const char* g_saObjTypeIcons[]; - -#endif // LDTYPES_H \ No newline at end of file
--- a/main.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QApplication> -#include "gui.h" -#include "file.h" -#include "bbox.h" -#include "misc.h" -#include "config.h" -#include "colors.h" -#include "types.h" - -vector<OpenFile*> g_loadedFiles; -OpenFile* g_curfile = null; -ForgeWindow* g_win = null; -bbox g_BBox; -const QApplication* g_app = null; - -const vertex g_origin (0.0f, 0.0f, 0.0f); -const matrix<3> g_identity ({1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}); - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -int main (int argc, char* argv[]) { - // Load or create the configuration - if (!config::load()) { - printf ("Creating configuration file...\n"); - if (config::save ()) - printf ("Configuration file successfully created.\n"); - else - printf ("failed to create configuration file!\n"); - } - - const QApplication app (argc, argv); - LDPaths::initPaths (); - - initColors (); - initPartList (); - - ForgeWindow* win = new ForgeWindow; - - g_app = &app; - - newFile (); - - win->show (); - return app.exec (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void logf (const char* fmtstr, ...) { - va_list va; - va_start (va, fmtstr); - g_win->logVA (LOG_Normal, fmtstr, va); - va_end (va); -} - -void logf (LogType type, const char* fmtstr, ...) { - va_list va; - va_start (va, fmtstr); - g_win->logVA (type, fmtstr, va); - va_end (va); -} - -void warnf (const char* fmtstr, ...) { - va_list va; - va_start (va, fmtstr); - g_win->logVA (LOG_Warning, fmtstr, va); - va_end (va); -} - -void errf (const char* fmtstr, ...) { - va_list va; - va_start (va, fmtstr); - g_win->logVA (LOG_Error, fmtstr, va); - va_end (va); -} - -#ifndef RELEASE -void devf (const char* fmtstr, ...) { - va_list va; - va_start (va, fmtstr); - g_win->logVA (LOG_Dev, fmtstr, va); - va_end (va); -} -#endif // RELEASE \ No newline at end of file
--- a/misc.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,277 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <math.h> -#include <locale.h> -#include <qcolor.h> -#include "common.h" -#include "misc.h" -#include "gui.h" - -// Prime number table. -const ushort g_uaPrimes[NUM_PRIMES] = { - 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, - 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, - 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, - 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, - 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, - 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, - 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, - 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, - 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, - 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, - 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, - 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, - 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, - 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, - 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, - 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, - 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, - 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, - 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, - 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, - 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, - 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, - 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, - 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, - 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, - 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, - 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, - 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, - 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, - 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, - 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, - 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, - 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, - 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, - 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, - 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, - 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, - 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, - 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, - 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, - 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, - 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, - 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, - 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, - 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, - 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, - 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, - 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, - 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, - 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -// Grid stuff -cfg (int, grid, Grid::Medium); -EXTERN_ACTION (gridCoarse) -EXTERN_ACTION (gridMedium) -EXTERN_ACTION (gridFine) - -cfg (float, grid_coarse_x, 5.0f); -cfg (float, grid_coarse_y, 5.0f); -cfg (float, grid_coarse_z, 5.0f); -cfg (float, grid_coarse_angle, 45.0f); -cfg (float, grid_medium_x, 1.0f); -cfg (float, grid_medium_y, 1.0f); -cfg (float, grid_medium_z, 1.0f); -cfg (float, grid_medium_angle, 22.5f); -cfg (float, grid_fine_x, 0.1f); -cfg (float, grid_fine_y, 0.1f); -cfg (float, grid_fine_z, 0.1f); -cfg (float, grid_fine_angle, 7.5f); - -const gridinfo g_GridInfo[3] = { - { "Coarse", { &grid_coarse_x, &grid_coarse_y, &grid_coarse_z, &grid_coarse_angle }, &ACTION (gridCoarse) }, - { "Medium", { &grid_medium_x, &grid_medium_y, &grid_medium_z, &grid_medium_angle }, &ACTION (gridMedium) }, - { "Fine", { &grid_fine_x, &grid_fine_y, &grid_fine_z, &grid_fine_angle }, &ACTION (gridFine) } -}; - -// ============================================================================= -double Grid::snap (double in, const Grid::Config axis) { - const double gridval = currentGrid ().confs[axis]->value; - const long mult = abs (in / gridval); - const bool neg = (in < 0); - - double out = mult * gridval; - - if (abs<double> (in) - (mult * gridval) > gridval / 2) - out += gridval; - - if (neg && out != 0) - out *= -1; - - return out; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -str ftoa (double fCoord) { - // Disable the locale first so that the decimal point will not - // turn into anything weird (like commas) - setlocale (LC_NUMERIC, "C"); - - str zRep = fmt ("%f", fCoord); - - // Remove trailing zeroes - while (zRep[~zRep - 1] == '0') - zRep -= 1; - - // If there was only zeroes in the decimal place, remove - // the decimal point now. - if (zRep[~zRep - 1] == '.') - zRep -= 1; - - return zRep; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -bool isNumber (str& zToken) { - char* cpPointer = &zToken[0]; - bool bGotDot = false; - - // Allow leading hyphen for negatives - if (*cpPointer == '-') - cpPointer++; - - while (*cpPointer != '\0') { - if (*cpPointer == '.' && !bGotDot) { - // Decimal point - bGotDot = true; - cpPointer++; - continue; - } - - if (*cpPointer >= '0' && *cpPointer <= '9') { - cpPointer++; - continue; // Digit - } - - // If the above cases didn't catch this character, it was - // illegal and this is therefore not a number. - return false; - } - - return true; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void simplify (short& dNum, short& dDenom) { - bool bRepeat; - - do { - bRepeat = false; - - for (ulong x = 0; x < NUM_PRIMES; x++) { - ulong i = NUM_PRIMES - x - 1; - ushort uPrime = g_uaPrimes[i]; - - if (dNum <= uPrime || dDenom <= uPrime) - continue; - - if ((dNum % uPrime == 0) && (dDenom % uPrime == 0)) { - dNum /= uPrime; - dDenom /= uPrime; - bRepeat = true; - break; - } - } - } while (bRepeat); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -StringParser::StringParser (str zInText, char cSeparator) { - zaTokens = zInText.split (cSeparator, true); - dPos = -1; -} - -// ----------------------------------------------------------------------------- -bool StringParser::atBeginning () { - return (dPos == -1); -} - -// ----------------------------------------------------------------------------- -bool StringParser::atEnd () { - return (dPos == (signed) zaTokens.size () - 1); -} - -// ----------------------------------------------------------------------------- -bool StringParser::getToken (str& zVal, const ushort uInPos) { - if (uInPos >= zaTokens.size()) - return false; - - zVal = zaTokens[uInPos]; - return true; -} - -// ----------------------------------------------------------------------------- -bool StringParser::next (str& zVal) { - return getToken (zVal, ++dPos); -} - -// ----------------------------------------------------------------------------- -bool StringParser::peekNext (str& zVal) { - return getToken (zVal, dPos + 1); -} - -// ----------------------------------------------------------------------------- -bool StringParser::findToken (short& dResult, char const* sNeedle, short dArgs) { - for (ushort i = 0; i < (zaTokens.size () - dArgs); ++i) { - if (zaTokens[i] == sNeedle) { - dResult = i; - return true; - } - } - - return false; -} - -// ----------------------------------------------------------------------------- -void StringParser::rewind () { - dPos = -1; -} - -// ----------------------------------------------------------------------------- -void StringParser::seek (short int dAmount, bool bRelative) { - dPos = (bRelative ? dPos : 0) + dAmount; -} - -// ----------------------------------------------------------------------------- -size_t StringParser::size () { - return zaTokens.size(); -} - -// ----------------------------------------------------------------------------- -bool StringParser -::tokenCompare (short int dInPos, const char* sOther) { - str tok; - if (!getToken (tok, dInPos)) - return false; - - return (tok == sOther); -} \ No newline at end of file
--- a/misc.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,110 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef MISC_H -#define MISC_H - -#include "common.h" -#include "str.h" -#include "config.h" - -#define NUM_PRIMES 500 - -class QColor; -class QAction; - -// Prime numbers -extern const ushort g_uaPrimes[NUM_PRIMES]; - -// Returns whether a given string represents a floating point number. -bool isNumber (str& zToken); - -// Converts a float value to a string value. -str ftoa (double fCoord); - -// Simplifies the given fraction. -void simplify (short& dNum, short& dDenom); - -// Grid stuff -typedef struct { - const char* const name; - floatconfig* const confs[4]; - QAction** const actionptr; -} gridinfo; - -extern_cfg (int, grid); -static const short g_NumGrids = 3; -extern const gridinfo g_GridInfo[3]; - -inline const gridinfo& currentGrid () { - return g_GridInfo[grid]; -} - -namespace Grid { - enum Type { - Coarse, - Medium, - Fine - }; - - enum Config { - X, - Y, - Z, - Angle - }; - - double snap (double value, const Grid::Config axis); -}; - -// ============================================================================= -template<class T> void dataswap (T& a, T& b) { - T c = a; - a = b; - b = c; -} - -// ============================================================================= -// StringParser -// -// String parsing utility -// ============================================================================= -class StringParser { -public: - std::vector<str> zaTokens; - short dPos; - - StringParser (str zInText, char cSeparator); - - bool atEnd (); - bool atBeginning (); - bool next (str& zVal); - bool peekNext (str& zVal); - bool getToken (str& zVal, const ushort uInPos); - bool findToken (short& dResult, char const* sNeedle, short dArgs); - size_t size (); - void rewind (); - void seek (short dAmount, bool bRelative); - bool tokenCompare (short int dInPos, const char* sOther); - - str operator[] (const size_t uIndex) { - return zaTokens[uIndex]; - } -}; - -#endif // MISC_H \ No newline at end of file
--- a/radiobox.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <qboxlayout.h> -#include <qradiobutton.h> -#include "radiobox.h" - -static QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false) { - return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom; -} - -void RadioBox::init (Qt::Orientation orient) { - dir = makeDirection (orient); - - buttonGroup = new QButtonGroup; - currentId = 0; - coreLayout = null; - - coreLayout = new QBoxLayout (makeDirection (orient, true)); - setLayout (coreLayout); - - // Init the first row with a break - rowBreak (); - - connect (buttonGroup, SIGNAL (buttonPressed (QAbstractButton*)), this, SLOT (slot_buttonPressed (QAbstractButton*))); - connect (buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int))); -} - -RadioBox::RadioBox (const QString& title, initlist<char const*> entries, int const defaultId, - const Qt::Orientation orient, QWidget* parent) : QGroupBox (title, parent), defaultId (defaultId) -{ - init (orient); - - for (char const* entry : entries) - addButton (entry); -} - -void RadioBox::rowBreak () { - QBoxLayout* newLayout = new QBoxLayout (dir); - currentLayout = newLayout; - layouts.push_back (newLayout); - - coreLayout->addLayout (newLayout); -} - -void RadioBox::addButton (const char* entry) { - QRadioButton* button = new QRadioButton (entry); - addButton (button); -} - -void RadioBox::addButton (QRadioButton* button) { - bool const selectThis = (currentId == defaultId); - - objects.push_back (button); - buttonGroup->addButton (button, currentId++); - currentLayout->addWidget (button); - - if (selectThis) - button->setChecked (true); -} - -RadioBox& RadioBox::operator<< (QRadioButton* button) { - addButton (button); - return *this; -} - -RadioBox& RadioBox::operator<< (const char* entry) { - addButton (entry); - return *this; -} - -void RadioBox::setCurrentRow (uint row) { - currentLayout = layouts[row]; -} - -void RadioBox::slot_buttonPressed (int btn) { - emit sig_buttonPressed (btn); -} - -void RadioBox::slot_buttonPressed (QAbstractButton* btn) { - emit sig_buttonPressed (btn); -} \ No newline at end of file
--- a/radiobox.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef RADIOBOX_H -#define RADIOBOX_H - -#include "common.h" -#include <qwidget.h> -#include <QGroupBox> -#include <qradiobutton.h> -#include <qboxlayout.h> -#include <qbuttongroup.h> - -// ============================================================================= -// RadioBox -// -// Convenience widget - is a groupbox of radio buttons. -// ============================================================================= -class RadioBox : public QGroupBox { - Q_OBJECT - -public: - void init (Qt::Orientation orient); - - explicit RadioBox (QWidget* parent = null) : QGroupBox (parent) { - init (Qt::Vertical); - } - - explicit RadioBox (const QString& title, QWidget* parent = null) : QGroupBox (title, parent) { - init (Qt::Vertical); - } - - explicit RadioBox () { - init (Qt::Vertical); - } - - explicit RadioBox (const QString& title, initlist<char const*> entries, int const defaultId, - const Qt::Orientation orient = Qt::Vertical, QWidget* parent = null); - - void rowBreak (); - void setCurrentRow (uint row); - void addButton (const char* entry); - void addButton (QRadioButton* button); - RadioBox& operator<< (QRadioButton* button); - RadioBox& operator<< (const char* entry); - - int value () const { - return buttonGroup->checkedId (); - } - - void setValue (int val) { - buttonGroup->button (val)->setChecked (true); - } - - std::vector<QRadioButton*>::iterator begin () { - return objects.begin (); - } - - std::vector<QRadioButton*>::iterator end () { - return objects.end (); - } - - QRadioButton* operator[] (uint n) const { - return objects[n]; - } - - bool exclusive () const { - return buttonGroup->exclusive (); - } - - void setExclusive (bool val) { - buttonGroup->setExclusive (val); - } - - bool isChecked (int n) const { - return buttonGroup->checkedId () == n; - } - -signals: - void sig_buttonPressed (int btn); - void sig_buttonPressed (QAbstractButton* btn); - -private: - std::vector<QRadioButton*> objects; - std::vector<QBoxLayout*> layouts; - QBoxLayout* coreLayout; - QBoxLayout* currentLayout; - QBoxLayout::Direction dir; - int currentId; - int defaultId; - QButtonGroup* buttonGroup; - - Q_DISABLE_COPY (RadioBox) - -private slots: - void slot_buttonPressed (int btn); - void slot_buttonPressed (QAbstractButton* btn); -}; - -#endif // RADIOBOX_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/aboutDialog.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,156 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <qlabel.h> +#include <qboxlayout.h> +#include <qdialogbuttonbox.h> +#include <qdesktopservices.h> +#include <qurl.h> +#include "common.h" +#include "aboutDialog.h" + +AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) { + QWidget* mainTab, *licenseTab; + QTabWidget* tabs = new QTabWidget; + + { + mainTab = new QWidget; + + // Application icon - in full 64 x 64 glory. + QLabel* icon = new QLabel; + icon->setPixmap (getIcon ("ldforge")); + + // Heading - application label and copyright information + QLabel* title = new QLabel (fmt ("<b>" APPNAME " v%d.%d</b><br />" + "Copyright (C) 2013 Santeri Piippo", + VERSION_MAJOR, VERSION_MINOR)); + + // Body text + QLabel* info = new QLabel ( + "<p>This software is intended for usage as a parts<br />" + "authoring tool for the <a href=\"http://ldraw.org/\">LDraw</a> parts library.</p>" + + "<p>" APPNAME " is free software, and you are welcome<br />" + "to redistribute it under the terms of GPL v3. See the LICENSE<br />" + "text file or the license tab in this dialog for details. If the<br />" + "license text is not available for some reason, see<br />" + "<a href=\"http://www.gnu.org/licenses/\">http://www.gnu.org/licenses/</a>" + "for the license terms.</p>" + + "<p>The application icon is derived from " + "<a href=\"http://en.wikipedia.org/wiki/File:Anvil,_labelled_en.svg\">this image</a>.</p>" + ); + + // Rest in peace, James. + QLabel* memorial = new QLabel ("In living memory of James Jessiman."); + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget (icon); + layout->addWidget (title); + layout->addWidget (info); + layout->addWidget (memorial); + + // Align everything to the center. + for (QLabel* label : vector<QLabel*> ({icon, title, info, memorial})) + label->setAlignment (Qt::AlignCenter); + + mainTab->setLayout (layout); + tabs->addTab (mainTab, "About " APPNAME); + } + + { + licenseTab = new QWidget; + + QTextEdit* license = new QTextEdit; + license->setReadOnly (true); + + QFont font ("Monospace"); + font.setStyleHint (QFont::TypeWriter); + font.setPixelSize (10); + + license->setFont (font); + + // Make the text view wide enough to display the license text. + // Why isn't 80 sufficient here? + license->setMinimumWidth (license->fontMetrics ().width ('a') * 85); + + // Try open the license text + FILE* fp = fopen ("LICENSE", "r"); + + if (fp == null) { + // Failed; tell the user how to get the license text instead. + setlocale (LC_ALL, "C"); + char const* text = "Couldn't open LICENSE: %s.<br />" + "See <a href=\"http://www.gnu.org/licenses/\">http://www.gnu.org/licenses/</a> for the GPLv3 text."; + + license->setHtml (fmt (text, strerror (errno))); + } else { + // Figure out file size + fseek (fp, 0, SEEK_END); + const size_t length = ftell (fp); + rewind (fp); + + // Init text buffer and write pointer + char* licenseText = new char[length]; + char* writePtr = &licenseText[0]; + + // Read in the license text + while (true) { + *writePtr = fgetc (fp); + + if (feof (fp)) + break; + + writePtr++; + } + + // Add terminating null character and add the license text to the + // license dialog text view. + *writePtr = '\0'; + license->setText (licenseText); + + // And dump the trash on the way out. + delete[] licenseText; + } + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget (license); + licenseTab->setLayout (layout); + tabs->addTab (licenseTab, "License"); + } + + QDialogButtonBox* buttons = new QDialogButtonBox (QDialogButtonBox::Close); + QPushButton* helpButton = new QPushButton; + + helpButton->setText ("Mail Author"); + helpButton->setIcon (getIcon ("mail")); + buttons->addButton (static_cast<QAbstractButton*> (helpButton), QDialogButtonBox::HelpRole); + connect (buttons, SIGNAL (helpRequested ()), this, SLOT (slot_mail ())); + connect (buttons, SIGNAL (rejected ()), this, SLOT (reject ())); + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget (tabs); + layout->addWidget (buttons); + setLayout (layout); + setWindowTitle ("About " APPNAME); +} + +void AboutDialog::slot_mail () { + QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <arezey@gmail.com>?subject=LDForge")); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/aboutDialog.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,36 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ZZ_ABOUTDIALOG_H +#define ZZ_ABOUTDIALOG_H + +#include <qdialog.h> +#include "gui.h" + +class AboutDialog : public QDialog { + Q_OBJECT + +public: + AboutDialog (QWidget* parent = nullptr, Qt::WindowFlags f = 0); + QPushButton* pb_mailAuthor; + +private slots: + void slot_mail (); +}; + +#endif // ZZ_ABOUTDIALOG_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/addObjectDialog.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,511 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <qgridlayout.h> +#include <qradiobutton.h> +#include <qcheckbox.h> +#include "gui.h" +#include "addObjectDialog.h" +#include "file.h" +#include "colors.h" +#include "colorSelectDialog.h" +#include "history.h" +#include "setContentsDialog.h" + +#define APPLY_COORDS(OBJ, N) \ + for (short i = 0; i < N; ++i) \ + for (const Axis ax : g_Axes) \ + OBJ->vaCoords[i][ax] = dlg.dsb_coords[(i * 3) + ax]->value (); + +// ============================================================================= +class SubfileListItem : public QTreeWidgetItem { +public: + SubfileListItem (QTreeWidgetItem* parent, int subfileID) : + QTreeWidgetItem (parent), subfileID (subfileID) {} + SubfileListItem (QTreeWidget* parent, int subfileID) : + QTreeWidgetItem (parent), subfileID (subfileID) {} + + int subfileID; +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) : + QDialog (parent) +{ + setlocale (LC_ALL, "C"); + + short coordCount = 0; + + switch (type) { + case LDObject::Comment: + le_comment = new QLineEdit; + if (obj) + le_comment->setText (static_cast<LDComment*> (obj)->text); + break; + + case LDObject::Line: + coordCount = 6; + break; + + case LDObject::Triangle: + coordCount = 9; + break; + + case LDObject::Quad: + case LDObject::CondLine: + coordCount = 12; + break; + + case LDObject::Vertex: + coordCount = 3; + break; + + case LDObject::BFC: + rb_bfcType = new RadioBox ("Statement", {}, 0, Qt::Vertical); + + for (int i = 0; i < LDBFC::NumStatements; ++i) + rb_bfcType->addButton (new QRadioButton (LDBFC::statements[i])); + + if (obj) + rb_bfcType->setValue ((int) static_cast<LDBFC*> (obj)->type); + break; + + case LDObject::Subfile: + coordCount = 3; + + enum { + Parts, + Subparts, + Primitives, + HiRes, + }; + + tw_subfileList = new QTreeWidget (); + for (int i : vector<int> ({Parts, Subparts, Primitives, HiRes})) { + SubfileListItem* parentItem = new SubfileListItem (tw_subfileList, -1); + parentItem->setText (0, (i == Parts) ? "Parts" : + (i == Subparts) ? "Subparts" : + (i == Primitives) ? "Primitives" : + "Hi-Res"); + + ulong j = 0; + for (partListEntry& part : g_PartList) { + QList<QTreeWidgetItem*> subfileItems; + + str fileName = part.sName; + const bool isSubpart = fileName.substr (0, 2) == "s\\"; + const bool isPrimitive = str (part.sTitle).substr (0, 9) == "Primitive"; + const bool isHiRes = fileName.substr (0, 3) == "48\\"; + + if ((i == Subparts && isSubpart) || + (i == Primitives && isPrimitive) || + (i == HiRes && isHiRes) || + (i == Parts && !isSubpart && !isPrimitive && !isHiRes)) + { + SubfileListItem* item = new SubfileListItem (parentItem, j); + item->setText (0, fmt ("%s - %s", part.sName, part.sTitle)); + subfileItems.append (item); + } + + j++; + } + + tw_subfileList->addTopLevelItem (parentItem); + } + + connect (tw_subfileList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_subfileTypeChanged ())); + lb_subfileName = new QLabel ("File:"); + le_subfileName = new QLineEdit; + le_subfileName->setFocus (); + + if (obj) { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + le_subfileName->setText (ref->zFileName); + } + break; + + case LDObject::Radial: + coordCount = 3; + + lb_radType = new QLabel ("Type:"); + lb_radResolution = new QLabel ("Resolution:"); + lb_radSegments = new QLabel ("Segments:"); + lb_radRingNum = new QLabel ("Ring number:"); + + rb_radType = new RadioBox ("Type", {}, 0, Qt::Vertical); + + for (int i = 0; i < LDRadial::NumTypes; ++i) { + if (i % (LDRadial::NumTypes / 2) == 0) + rb_radType->rowBreak (); + + rb_radType->addButton (new QRadioButton (LDRadial::radialTypeName ((LDRadial::Type) i))); + } + + connect (rb_radType, SIGNAL (sig_buttonPressed (int)), this, SLOT (slot_radialTypeChanged (int))); + + cb_radHiRes = new QCheckBox ("Hi-Res"); + + sb_radSegments = new QSpinBox; + sb_radSegments->setMinimum (1); + + sb_radRingNum = new QSpinBox; + sb_radRingNum->setEnabled (false); + + if (obj) { + LDRadial* rad = static_cast<LDRadial*> (obj); + + rb_radType->setValue (rad->eRadialType); + sb_radSegments->setValue (rad->dSegments); + cb_radHiRes->setChecked ((rad->dDivisions == 48) ? Qt::Checked : Qt::Unchecked); + sb_radRingNum->setValue (rad->dRingNum); + } + break; + + default: + assert (false); + return; + } + + QPixmap icon = getIcon (fmt ("add-%s", g_saObjTypeIcons[type])); + LDObject* defaults = LDObject::getDefault (type); + + lb_typeIcon = new QLabel; + lb_typeIcon->setPixmap (icon); + + // Show a color edit dialog for the types that actually use the color + if (defaults->isColored ()) { + if (obj != null) + dColor = obj->dColor; + else + dColor = (type == LDObject::CondLine || type == LDObject::Line) ? edgecolor : maincolor; + + pb_color = new QPushButton; + setButtonBackground (pb_color, dColor); + connect (pb_color, SIGNAL (clicked ()), this, SLOT (slot_colorButtonClicked ())); + } + + for (short i = 0; i < coordCount; ++i) { + dsb_coords[i] = new QDoubleSpinBox; + dsb_coords[i]->setDecimals (5); + dsb_coords[i]->setMinimum (-10000.0); + dsb_coords[i]->setMaximum (10000.0); + } + + IMPLEMENT_DIALOG_BUTTONS + + QGridLayout* const layout = new QGridLayout; + layout->addWidget (lb_typeIcon, 0, 0); + + switch (type) { + case LDObject::Line: + case LDObject::CondLine: + case LDObject::Triangle: + case LDObject::Quad: + // Apply coordinates + if (obj) { + for (short i = 0; i < coordCount / 3; ++i) + for (short j = 0; j < 3; ++j) + dsb_coords[(i * 3) + j]->setValue (obj->vaCoords[i].coord (j)); + } + break; + + case LDObject::Comment: + layout->addWidget (le_comment, 0, 1); + break; + + case LDObject::BFC: + layout->addWidget (rb_bfcType, 0, 1); + break; + + case LDObject::Radial: + layout->addWidget (rb_radType, 1, 1, 3, 2); + layout->addWidget (cb_radHiRes, 1, 3); + layout->addWidget (lb_radSegments, 2, 3); + layout->addWidget (sb_radSegments, 2, 4); + layout->addWidget (lb_radRingNum, 3, 3); + layout->addWidget (sb_radRingNum, 3, 4); + + if (obj) + for (short i = 0; i < 3; ++i) + dsb_coords[i]->setValue (static_cast<LDRadial*> (obj)->vPosition.coord (i)); + break; + + case LDObject::Subfile: + layout->addWidget (tw_subfileList, 1, 1, 1, 2); + layout->addWidget (lb_subfileName, 2, 1); + layout->addWidget (le_subfileName, 2, 2); + + if (obj) + for (short i = 0; i < 3; ++i) + dsb_coords[i]->setValue (static_cast<LDSubfile*> (obj)->vPosition.coord (i)); + break; + + default: + break; + } + + if (type == LDObject::Subfile || type == LDObject::Radial) { + QLabel* lb_matrix = new QLabel ("Matrix:"); + le_matrix = new QLineEdit; + // le_matrix->setValidator (new QDoubleValidator); + matrix<3> defval = g_identity; + + if (obj) { + if (obj->getType () == LDObject::Subfile) + defval = static_cast<LDSubfile*> (obj)->mMatrix; + else + defval = static_cast<LDRadial*> (obj)->mMatrix; + } + + le_matrix->setText (defval.stringRep ()); + layout->addWidget (lb_matrix, 4, 1); + layout->addWidget (le_matrix, 4, 2, 1, 3); + } + + if (defaults->isColored ()) + layout->addWidget (pb_color, 1, 0); + + if (coordCount > 0) { + QGridLayout* const qCoordLayout = new QGridLayout; + + for (short i = 0; i < coordCount; ++i) + qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3)); + + layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3); + } + + layout->addWidget (bbx_buttons, 5, 0, 1, 4); + setLayout (layout); + setWindowTitle (fmt (APPNAME ": New %s", + g_saObjTypeNames[type]).chars()); + + setWindowIcon (icon); + delete defaults; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void AddObjectDialog::setButtonBackground (QPushButton* button, short color) { + button->setIcon (getIcon ("palette")); + button->setAutoFillBackground (true); + button->setStyleSheet ( + fmt ("background-color: %s", getColor (color)->zColorString.chars()).chars() + ); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +char* AddObjectDialog::currentSubfileName() { + SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem ()); + + if (item->subfileID == -1) + return null; // selected a heading + + return g_PartList[item->subfileID].sName; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void AddObjectDialog::slot_colorButtonClicked () { + ColorSelectDialog::staticDialog (dColor, dColor, this); + setButtonBackground (pb_color, dColor); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void AddObjectDialog::slot_radialTypeChanged (int dType) { + LDRadial::Type eType = (LDRadial::Type) dType; + sb_radRingNum->setEnabled (eType == LDRadial::Ring || eType == LDRadial::Cone); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void AddObjectDialog::slot_subfileTypeChanged () { + char* name = currentSubfileName (); + + if (name) + le_subfileName->setText (name); +} + +// ============================================================================= +template<class T> T* initObj (LDObject*& obj) { + if (obj == null) + obj = new T; + + return static_cast<T*> (obj); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void AddObjectDialog::staticDialog (const LDObject::Type type, LDObject* obj) { + setlocale (LC_ALL, "C"); + + // Redirect editing of gibberish to the set contents dialog + if (obj && obj->getType () == LDObject::Gibberish) { + SetContentsDialog::staticDialog (obj); + return; + } + + if (type == LDObject::Empty) + return; // Nothing to edit with empties + + const bool newObject = (obj == null); + AddObjectDialog dlg (type, obj); + + if (obj) + assert (obj->getType () == type); + + if (dlg.exec () == false) + return; + + LDObject* backup = null; + if (!newObject) + backup = obj->clone (); + + matrix<3> transform = g_identity; + if (type == LDObject::Subfile || type == LDObject::Radial) { + vector<str> matrixstrvals = str (dlg.le_matrix->text ()).split (" ", true); + + if (matrixstrvals.size () == 9) { + double matrixvals[9]; + int i = 0; + + for (str val : matrixstrvals) + matrixvals[i++] = atof (val); + + transform = matrix<3> (matrixvals); + } + } + + switch (type) { + case LDObject::Comment: + { + LDComment* comm = initObj<LDComment> (obj); + comm->text = dlg.le_comment->text (); + } + break; + + case LDObject::Line: + { + LDLine* line = initObj<LDLine> (obj); + line->dColor = dlg.dColor; + APPLY_COORDS (line, 2) + } + break; + + case LDObject::Triangle: + { + LDTriangle* tri = initObj<LDTriangle> (obj); + tri->dColor = dlg.dColor; + APPLY_COORDS (tri, 3) + } + break; + + case LDObject::Quad: + { + LDQuad* quad = initObj<LDQuad> (obj); + quad->dColor = dlg.dColor; + APPLY_COORDS (quad, 4) + } + break; + + case LDObject::CondLine: + { + LDCondLine* line = initObj<LDCondLine> (obj); + line->dColor = dlg.dColor; + APPLY_COORDS (line, 4) + } + break; + + case LDObject::BFC: + { + LDBFC* bfc = initObj<LDBFC> (obj); + bfc->type = (LDBFC::Type) dlg.rb_bfcType->value (); + } + break; + + case LDObject::Vertex: + { + LDVertex* vert = initObj<LDVertex> (obj); + vert->dColor = dlg.dColor; + + for (const Axis ax : g_Axes) + vert->vPosition[ax] = dlg.dsb_coords[ax]->value (); + } + break; + + case LDObject::Radial: + { + LDRadial* pRad = initObj<LDRadial> (obj); + pRad->dColor = dlg.dColor; + + for (const Axis ax : g_Axes) + pRad->vPosition[ax] = dlg.dsb_coords[ax]->value (); + + pRad->dDivisions = (dlg.cb_radHiRes->checkState () != Qt::Checked) ? 16 : 48; + pRad->dSegments = min<short> (dlg.sb_radSegments->value (), pRad->dDivisions); + pRad->eRadialType = (LDRadial::Type) dlg.rb_radType->value (); + pRad->dRingNum = dlg.sb_radRingNum->value (); + pRad->mMatrix = transform; + } + break; + + case LDObject::Subfile: + { + str name = dlg.le_subfileName->text (); + if (~name == 0) + return; // no subfile filename + + OpenFile* file = loadSubfile (name); + if (!file) + return; + + LDSubfile* ref = initObj<LDSubfile> (obj); + ref->dColor = dlg.dColor; + + for (const Axis ax : g_Axes) + ref->vPosition[ax] = dlg.dsb_coords[ax]->value (); + + ref->zFileName = name; + ref->mMatrix = transform; + ref->pFile = file; + } + break; + + default: + break; + } + + if (newObject) { + ulong idx = g_win->getInsertionPoint (); + g_curfile->insertObj (idx, obj); + History::addEntry (new AddHistory ({(ulong) idx}, {obj->clone ()})); + } else { + History::addEntry (new EditHistory ({(ulong) obj->getIndex (g_curfile)}, {backup}, {obj->clone ()})); + } + + g_win->refresh (); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/addObjectDialog.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,83 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ZZ_ADDOBJECTDIALOG_H +#define ZZ_ADDOBJECTDIALOG_H + +#include "gui.h" +#include "radiobox.h" +#include <qdialog.h> +#include <qlineedit.h> +#include <qdialogbuttonbox.h> +#include <qcheckbox.h> +#include <qspinbox.h> +#include <qlabel.h> +#include <qradiobutton.h> +#include <qlistwidget.h> +#include <qtreewidget.h> + +class AddObjectDialog : public QDialog { + Q_OBJECT + +public: + AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent = null); + static void staticDialog (const LDObject::Type type, LDObject* obj); + + QLabel* lb_typeIcon; + + // Comment line edit + QLineEdit* le_comment; + + // Coordinate edits for.. anything with coordinates, really. + QDoubleSpinBox* dsb_coords[12]; + + // Color selection dialog button + QPushButton* pb_color; + + // BFC-related widgets + RadioBox* rb_bfcType; + + // Subfile stuff + QTreeWidget* tw_subfileList; + QLineEdit* le_subfileName; + QLabel* lb_subfileName; + + // Radial stuff + QCheckBox* cb_radHiRes; + RadioBox* rb_radType; + QSpinBox* sb_radSegments, *sb_radRingNum; + QLabel* lb_radType, *lb_radResolution, *lb_radSegments, + *lb_radRingNum; + + QLineEdit* le_matrix; + + QDialogButtonBox* bbx_buttons; + +private: + void setButtonBackground (QPushButton* button, short color); + char* currentSubfileName (); + + short dColor; + +private slots: + void slot_colorButtonClicked (); + void slot_radialTypeChanged (int type); + void slot_subfileTypeChanged (); +}; + +#endif // ZZ_ADDOBJECTDIALOG_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/bbox.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,167 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "common.h" +#include "bbox.h" +#include "ldtypes.h" +#include "file.h" + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +bbox::bbox () { + reset (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void bbox::calculate () { + reset (); + + if (!g_curfile) + return; + + for (LDObject* obj : g_curfile->m_objs) + calcObject (obj); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void bbox::calcObject (LDObject* obj) { + switch (obj->getType ()) { + case LDObject::Line: + { + LDLine* line = static_cast<LDLine*> (obj); + for (short i = 0; i < 2; ++i) + calcVertex (line->vaCoords[i]); + } + break; + + case LDObject::Triangle: + { + LDTriangle* tri = static_cast<LDTriangle*> (obj); + for (short i = 0; i < 3; ++i) + calcVertex (tri->vaCoords[i]); + } + break; + + case LDObject::Quad: + { + LDQuad* quad = static_cast<LDQuad*> (obj); + for (short i = 0; i < 4; ++i) + calcVertex (quad->vaCoords[i]); + } + break; + + case LDObject::CondLine: + { + LDCondLine* line = static_cast<LDCondLine*> (obj); + for (short i = 0; i < 4; ++i) + calcVertex (line->vaCoords[i]); + } + break; + + case LDObject::Subfile: + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + vector<LDObject*> objs = ref->inlineContents (true, true); + + for (LDObject* obj : objs) { + calcObject (obj); + delete obj; + } + } + break; + + case LDObject::Radial: + { + LDRadial* rad = static_cast<LDRadial*> (obj); + vector<LDObject*> objs = rad->decompose (true); + + for (LDObject* obj : objs) { + calcObject (obj); + delete obj; + } + } + break; + + default: + break; + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void bbox::calcVertex (vertex v) { + for (const Axis ax : g_Axes) { + if (v[ax] < m_v0[ax]) + m_v0[ax] = v[ax]; + + if (v[ax] > m_v1[ax]) + m_v1[ax] = v[ax]; + } + + m_empty = false; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void bbox::reset () { + m_v0[X] = m_v0[Y] = m_v0[Z] = +0x7FFFFFFF; + m_v1[X] = m_v1[Y] = m_v1[Z] = -0x7FFFFFFF; + + m_empty = true; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +double bbox::size () const { + double fXScale = (m_v0[X] - m_v1[X]); + double fYScale = (m_v0[Y] - m_v1[Y]); + double fZScale = (m_v0[Z] - m_v1[Z]); + double fSize = fZScale; + + if (fXScale > fYScale) { + if (fXScale > fZScale) + fSize = fXScale; + } else if (fYScale > fZScale) + fSize = fYScale; + + if (abs (fSize) >= 2.0f) + return abs (fSize / 2); + + return 1.0f; +} + +// ============================================================================= +vertex bbox::center () const { + return vertex ( + (m_v0[X] + m_v1[X]) / 2, + (m_v0[Y] + m_v1[Y]) / 2, + (m_v0[Z] + m_v1[Z]) / 2); +} + +// ============================================================================= +bool bbox::empty() const { + return m_empty; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/bbox.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,61 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef BBOX_H +#define BBOX_H + +#include "common.h" +#include "types.h" + +// ============================================================================= +// bbox +// +// The bounding box is the box that encompasses a given set of objects. The +// global instance g_BBox is the bbox for the model we have open. +// v0 is the minimum vertex, v1 is the maximum vertex. +// ============================================================================= +class bbox { +public: + bbox (); + void reset (); + void calculate (); + double size () const; + void calcObject (LDObject* obj); + void calcVertex (vertex v); + vertex center () const; + bool empty () const; + + bbox& operator<< (LDObject* obj) { + calcObject (obj); + return *this; + } + + bbox& operator<< (vertex& v) { + calcVertex (v); + return *this; + } + + const vertex& v0 () { return m_v0; } + const vertex& v1 () { return m_v1; } + +private: + vertex m_v0, m_v1; + bool m_empty; +}; + +#endif // BBOX_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colorSelectDialog.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,179 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "common.h" +#include "gui.h" +#include <qgraphicsscene.h> +#include <qgraphicsview.h> +#include <qicon.h> +#include <qboxlayout.h> +#include <qgraphicsitem.h> +#include <qevent.h> +#include <qscrollbar.h> +#include "colorSelectDialog.h" +#include "colors.h" +#include "config.h" +#include "misc.h" + +static const short g_dNumColumns = 8; +static const short g_dNumRows = 10; +static const short g_dSquareSize = 32; +static const long g_lWidth = (g_dNumColumns * g_dSquareSize); +static const long g_lHeight = (g_dNumRows * g_dSquareSize); +static const long g_lMaxHeight = ((MAX_COLORS / g_dNumColumns) * g_dSquareSize); + +extern_cfg (str, gl_maincolor); +extern_cfg (float, gl_maincolor_alpha); + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +ColorSelectDialog::ColorSelectDialog (short int defval, QWidget* parent) : QDialog (parent) { + // Remove the default color if it's invalid + if (!getColor (defval)) + defval = -1; + + gs_scene = new QGraphicsScene; + gv_view = new QGraphicsView (gs_scene); + selColor = defval; + + // not really an icon but eh + gs_scene->setBackgroundBrush (getIcon ("checkerboard")); + + gs_scene->setSceneRect (0, 0, g_lWidth, g_lMaxHeight); + gv_view->setSceneRect (0, 0, g_lWidth, g_lMaxHeight); + + drawScene (); + + IMPLEMENT_DIALOG_BUTTONS + + // Set the size of the view + const long lWidth = g_lWidth + 21; // HACK + gv_view->setMaximumWidth (lWidth); + gv_view->setMinimumWidth (lWidth); + gv_view->setMaximumHeight (g_lHeight); + gv_view->setMinimumHeight (g_lHeight); + gv_view->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff); + + // If we have a default color selected, scroll down so that it is visible. + // TODO: find a better way to do this + if (defval >= ((g_dNumColumns * g_dNumRows) - 2)) { + ulong ulNewY = ((defval / g_dNumColumns) - 3) * g_dSquareSize; + gv_view->verticalScrollBar ()->setSliderPosition (ulNewY); + } + + lb_colorInfo = new QLabel; + drawColorInfo (); + + QVBoxLayout* qLayout = new QVBoxLayout; + qLayout->addWidget (gv_view); + qLayout->addWidget (lb_colorInfo); + qLayout->addWidget (bbx_buttons); + setLayout (qLayout); + + setWindowIcon (getIcon ("palette")); + setWindowTitle (APPNAME); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ColorSelectDialog::drawScene () { + const double fPenWidth = 1.0f; + QPen qPen (Qt::black, fPenWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + + // Draw the color rectangles. + gs_scene->clear (); + for (short i = 0; i < MAX_COLORS; ++i) { + color* meta = getColor (i); + if (!meta) + continue; + + const double x = (i % g_dNumColumns) * g_dSquareSize; + const double y = (i / g_dNumColumns) * g_dSquareSize; + const double w = (g_dSquareSize) - (fPenWidth / 2); + + QColor col = meta->qColor; + + if (i == maincolor) { + // Use the user preferences for main color here + col = gl_maincolor.value.chars (); + col.setAlpha (gl_maincolor_alpha * 255.0f); + } + + bool dark = (luma (col) < 80); + + gs_scene->addRect (x, y, w, w, qPen, col); + QGraphicsTextItem* qText = gs_scene->addText (fmt ("%lu", i).chars()); + qText->setDefaultTextColor ((dark) ? Qt::white : Qt::black); + qText->setPos (x, y); + + if (i == selColor) { + auto curspic = gs_scene->addPixmap (getIcon ("colorcursor")); + curspic->setPos (x, y); + } + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ColorSelectDialog::drawColorInfo () { + color* col = getColor (selColor); + + if (selColor == -1 || !col) { + lb_colorInfo->setText ("---"); + return; + } + + lb_colorInfo->setText (fmt ("%d - %s", + selColor, col->zName.chars())); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ColorSelectDialog::mousePressEvent (QMouseEvent* event) { + QPointF qPoint = gv_view->mapToScene (event->pos ()); + + ulong x = ((ulong)qPoint.x () - (g_dSquareSize / 2)) / g_dSquareSize; + ulong y = ((ulong)qPoint.y () - (g_dSquareSize / 2)) / g_dSquareSize; + ulong idx = (y * g_dNumColumns) + x; + + color* col = getColor (idx); + if (!col) + return; + + selColor = idx; + drawScene (); + drawColorInfo (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +bool ColorSelectDialog::staticDialog (short& val, short int defval, QWidget* parent) { + ColorSelectDialog dlg (defval, parent); + + if (dlg.exec () && dlg.selColor != -1) { + val = dlg.selColor; + return true; + } + + return false; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colorSelectDialog.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,49 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef COLORSELECTOR_H +#define COLORSELECTOR_H + +#include <qdialog.h> +#include <qdialogbuttonbox.h> +#include <qgraphicsscene.h> +#include <qlabel.h> +#include "common.h" + +class ColorSelectDialog : public QDialog { + Q_OBJECT + +public: + explicit ColorSelectDialog (short defval = -1, QWidget* parent = null); + static bool staticDialog (short& val, short defval = -1, QWidget* parent = null); + + QGraphicsScene* gs_scene; + QGraphicsView* gv_view; + QLabel* lb_colorInfo; + QDialogButtonBox* bbx_buttons; + short selColor; + +private: + void drawScene (); + void drawColorInfo (); + +private slots: + void mousePressEvent (QMouseEvent* event); +}; + +#endif // COLORSELECTOR_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colors.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,173 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "common.h" +#include "colors.h" +#include "file.h" +#include "misc.h" +#include <qcolor.h> + +static color* g_LDColors[MAX_COLORS]; +static bool g_bColorsInit = false; + +void initColors () { + if (g_bColorsInit) + return; + + logf ("%s: initializing color information.\n", __func__); + + color* col; + + // Always make sure there's 16 and 24 available. They're special like that. + col = new color; + col->zColorString = "#AAAAAA"; + col->qColor = col->zColorString.chars (); + col->qEdge = Qt::black; + g_LDColors[maincolor] = col; + + col = new color; + col->zColorString = "#000000"; + col->qEdge = col->qColor = Qt::black; + g_LDColors[edgecolor] = col; + + parseLDConfig (); + + g_bColorsInit = true; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +color* getColor (short dColorNum) { + // Check bounds + if (dColorNum < 0 || dColorNum >= MAX_COLORS) + return null; + + return g_LDColors[dColorNum]; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +static bool parseLDConfigTag (StringParser& pars, char const* sTag, str& zVal) { + short dPos; + if (!pars.findToken (dPos, sTag, 1)) + return false; + + return pars.getToken (zVal, dPos + 1); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +short color::index () { + short idx = 0; + for (color* it : g_LDColors) { + if (it == this) + return idx; + idx++; + } + + return -1; +} + +// ============================================================================= +uchar luma (QColor& col) { + return (0.2126f * col.red ()) + + (0.7152f * col.green ()) + + (0.0722f * col.blue ()); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void parseLDConfig () { + FILE* fp = openLDrawFile ("LDConfig.ldr", false); + + if (!fp) + return; + + // Even though LDConfig.ldr is technically an LDraw file, parsing it as one + // would be overkill by any standard. + char line[1024]; + while (fgets (line, sizeof line, fp)) { + if (strlen (line) == 0 || line[0] != '0') + continue; // empty or illogical + + str zLine = line; + zLine.replace ("\n", ""); + zLine.replace ("\r", ""); + + StringParser pars (zLine, ' '); + short dCode = 0, dAlpha = 255; + str zName, zColor, zEdge, zValue; + + // Check 0 !COLOUR, parse the name + if (!pars.tokenCompare (0, "0") || !pars.tokenCompare (1, "!COLOUR") || !pars.getToken (zName, 2)) + continue; + + // Replace underscores in the name with spaces for readability + zName.replace ("_", " "); + + // get the CODE tag + if (!parseLDConfigTag (pars, "CODE", zValue)) + continue; + + // Ensure that the code is within range. must be within 0 - 512 + dCode = atoi (zValue); + if (dCode < 0 || dCode >= 512) + continue; + + // Don't let LDConfig.ldr override the special colors 16 and 24. However, + // do take the name it gives for the color + if (dCode == maincolor || dCode == edgecolor) { + g_LDColors[dCode]->zName = zName; + continue; + } + + // VALUE tag + if (!parseLDConfigTag (pars, "VALUE", zColor)) + continue; + + // EDGE tag + if (!parseLDConfigTag (pars, "EDGE", zEdge)) + continue; + + // Ensure that our colors are correct + QColor qColor (zColor.chars()), + qEdge (zEdge.chars()); + + if (!qColor.isValid () || !qEdge.isValid ()) + continue; + + // Parse alpha if given. + if (parseLDConfigTag (pars, "ALPHA", zValue)) + dAlpha = clamp<short> (atoi (zValue), 0, 255); + + color* col = new color; + col->zName = zName; + col->qColor = qColor; + col->qEdge = qEdge; + col->zColorString = zColor; + col->qColor.setAlpha (dAlpha); + + g_LDColors[dCode] = col; + } + + fclose (fp); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colors.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,48 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef COLORS_H +#define COLORS_H + +#include <qcolor.h> +#include "common.h" + +#define MAX_COLORS 512 + +class color { +public: + str zName, zColorString; + QColor qColor, qEdge; + + short index (); +}; + +typedef struct { + const short dIndex; + const char* sName, *sColor; + const float fAlpha; +} TemporaryColorMeta; + +void initColors (); +void parseLDConfig (); +uchar luma (QColor& col); + +// Safely gets a color with the given number or null if no such color. +color* getColor (short dColorNum); + +#endif // COLORS_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/common.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,183 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +// ============================================================================= +// This file is included one way or another in every source file of LDForge. +// Stuff defined and included here is universally included. + +#ifndef COMMON_H +#define COMMON_H + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <vector> +#include <stdint.h> +#include <stdarg.h> +#include "str.h" +#include "config.h" +#include "types.h" + +#define APPNAME "LDForge" + +#define VERSION_MAJOR 0 +#define VERSION_MAJOR_STR "0" +#define VERSION_MINOR 1 +#define VERSION_MINOR_STR "1" + +// ============--- +// #define RELEASE + +// Version string identifier +static const str versionString = fmt ("%d.%d", VERSION_MAJOR, VERSION_MINOR); + +#ifdef __GNUC__ +# define FORMAT_PRINTF(M,N) __attribute__ ((format (printf, M, N))) +# define ATTR(N) __attribute__ ((N)) +#else +# define FORMAT_PRINTF(M,N) +# define ATTR(N) +#endif // __GNUC__ + +#ifdef WIN32 +#define DIRSLASH "\\" +#else // WIN32 +#define DIRSLASH "/" +#endif // WIN32 + +#ifdef RELEASE +#define NDEBUG // remove asserts +#endif // RELEASE + +#ifdef null +#undef null +#endif // null + +// Null pointer +static const std::nullptr_t null = nullptr; + +// Main and edge color identifiers +static const short maincolor = 16; +static const short edgecolor = 24; + +static const bool yup = true; +static const bool nope = false; + +class ForgeWindow; +class LDObject; +class bbox; +class OpenFile; +class QApplication; + +// ============================================================================= +// Plural expression +#define PLURAL(n) ((n != 1) ? "s" : "") + +// ----------------------------------------------------------------------------- +// Shortcut for formatting +#define PERFORM_FORMAT(in, out) \ + va_list v; \ + va_start (v, in); \ + char* out = vdynformat (in, v, 256); \ + va_end (v); + +// ----------------------------------------------------------------------------- +// Shortcuts for stuffing vertices into printf-formatting. +#define FMT_VERTEX "(%.3f, %.3f, %.3f)" +#define FVERTEX(V) V.x, V.y, V.z + +// ----------------------------------------------------------------------------- +// Templated clamp +template<class T> static inline T clamp (T a, T min, T max) { + return (a > max) ? max : (a < min) ? min : a; +} + +// Templated minimum +template<class T> static inline T min (T a, T b) { + return (a < b) ? a : b; +} + +// Templated maximum +template<class T> static inline T max (T a, T b) { + return (a > b) ? a : b; +} + +// Templated absolute value +template<class T> static inline T abs (T a) { + return (a >= 0) ? a : -a; +} + +// Quick QString to const char* conversion +static inline const char* qchars (QString qstr) { + return qstr.toStdString ().c_str (); +} + +static const double pi = 3.14159265358979323846f; + +#ifdef IN_IDE_PARSER // KDevelop workaround +// Current function name +static const char* __func__ = ""; +#endif // IN_IDE_PARSER + +// ----------------------------------------------------------------------------- +enum LogType { + LOG_Normal, + LOG_Warning, + LOG_Error, + LOG_Dev, +}; + +// logf - universal access to the message log. Defined here so that I don't have +// to include gui.h here and recompile everything every time that file changes. +// logf is defined in main.cpp +void logf (const char* fmtstr, ...) FORMAT_PRINTF (1, 2); +void logf (LogType type, const char* fmtstr, ...) FORMAT_PRINTF (2, 3); +void warnf (const char* fmtstr, ...) FORMAT_PRINTF (1, 2); +void errf (const char* fmtstr, ...) FORMAT_PRINTF (1, 2); + +#ifndef RELEASE +void devf (const char* fmtstr, ...); +#else +# define devf(...) +#endif // RELEASE + +// ----------------------------------------------------------------------------- +// Vertex at (0, 0, 0) +extern const vertex g_origin; + +// ----------------------------------------------------------------------------- +// Pointer to the OpenFile which is currently being edited by the user. +extern OpenFile* g_curfile; + +// ----------------------------------------------------------------------------- +// Pointer to the bounding box. +extern bbox g_BBox; + +// ----------------------------------------------------------------------------- +// Vector of all currently opened files. +extern vector<OpenFile*> g_loadedFiles; + +// ----------------------------------------------------------------------------- +// Pointer to the main application. +extern const QApplication* g_app; + +// ----------------------------------------------------------------------------- +// Identity matrix +extern const matrix<3> g_identity; + +#endif // COMMON_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/config.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,276 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <time.h> +#include <QDir> +#include "common.h" +#include "str.h" +#include "config.h" + +std::vector<config*> g_configPointers; + +// ============================================================================= +const char* g_WeekdayNames[7] = { + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +}; + +// ============================================================================= +static const char* g_MonthNames[12] = { + "Januray", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November" + "December", +}; + +static const char* g_ConfigTypeNames[] = { + "None", + "Integer", + "String", + "Float", + "Boolean", + "Key sequence", +}; + +// ============================================================================= +// Load the configuration from file +bool config::load () { + printf ("config::load: loading configuration file.\n"); + + // Locale must be disabled for atof + setlocale (LC_NUMERIC, "C"); + + FILE* fp = fopen (filepath().chars(), "r"); + char linedata[MAX_INI_LINE]; + char* line; + size_t ln = 0; + + if (!fp) + return false; // can't open for reading + + // Read the values. + while (fgets (linedata, MAX_INI_LINE, fp)) { + ln++; + line = linedata; + + while (*line != 0 && (*line <= 32 || *line >= 127)) + line++; // Skip junk + + if (*line == '\0' || line[0] == '#') + continue; // Empty line or comment. + + // Find the equals sign. + char* equals = strchr (line, '='); + if (!equals) { + fprintf (stderr, "couldn't find `=` sign in entry `%s`\n", line); + continue; + } + + str entry = str (line).substr (0, equals - line); + + // Find the config entry for this. + config* cfg = null; + for (config* i : g_configPointers) + if (entry == i->name) + cfg = i; + + if (!cfg) { + fprintf (stderr, "unknown config `%s`\n", entry.chars()); + continue; + } + + str valstring = str (line).substr (equals - line + 1, -1); + + // Trim the crap off the end + while (~valstring) { + char c = valstring[~valstring - 1]; + if (c <= 32 || c >= 127) + valstring -= 1; + else + break; + } + + switch (cfg->getType()) { + case CONFIG_int: + static_cast<intconfig*> (cfg)->value = atoi (valstring.chars()); + break; + + case CONFIG_str: + static_cast<strconfig*> (cfg)->value = valstring; + break; + + case CONFIG_float: + static_cast<floatconfig*> (cfg)->value = atof (valstring.chars()); + break; + + case CONFIG_bool: + { + bool& val = static_cast<boolconfig*> (cfg)->value; + + if (+valstring == "TRUE" || valstring == "1") + val = true; + else if (+valstring == "FALSE" || valstring == "0") + val = false; + break; + } + + case CONFIG_keyseq: + static_cast<keyseqconfig*> (cfg)->value = keyseq::fromString (valstring.chars ()); + break; + + default: + break; + } + } + + fclose (fp); + return true; +} + +// ============================================================================= +// Write a given formatted string to the given file stream +static size_t writef (FILE* fp, const char* fmt, ...) { + va_list va; + + va_start (va, fmt); + char* buf = vdynformat (fmt, va, 256); + va_end (va); + + size_t len = fwrite (buf, 1, strlen (buf), fp); + delete[] buf; + + return len; +} + +// ============================================================================= +// Save the configuration to disk +bool config::save () { + // The function will write floats, disable the locale now so that they + // are written properly. + setlocale (LC_NUMERIC, "C"); + + // If the directory doesn't exist, create it now. + if (QDir (dirpath ()).exists () == nope) { + fprintf (stderr, "Creating config path %s...\n", dirpath().chars()); + if (!QDir ().mkpath (dirpath().chars())) { + logf (LOG_Warning, "Failed to create the directory. Configuration cannot be saved!\n"); + return false; // Couldn't create directory + } + } + + FILE* fp = fopen (filepath().chars(), "w"); + printf ("writing cfg to %s\n", filepath().chars()); + + if (!fp) { + printf ("Couldn't open %s for writing\n", filepath().chars()); + return false; + } + + const time_t curtime = time (NULL); + const struct tm* timeinfo = localtime (&curtime); + const char* daysuffix = + (timeinfo->tm_mday % 10 == 1) ? "st" : + (timeinfo->tm_mday % 10 == 2) ? "nd" : + (timeinfo->tm_mday % 10 == 3) ? "rd" : "th"; + + writef (fp, "# Configuration file for " APPNAME "\n"); + writef (fp, "# Written on %s, %s %d%s %d %.2d:%.2d:%.2d\n", + g_WeekdayNames[timeinfo->tm_wday], g_MonthNames[timeinfo->tm_mon], + timeinfo->tm_mday, daysuffix, timeinfo->tm_year + 1900, + timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec); + + for (config* cfg : g_configPointers) { + str valstring; + switch (cfg->getType()) { + case CONFIG_int: + valstring.format ("%d", static_cast<intconfig*> (cfg)->value); + break; + case CONFIG_str: + valstring = static_cast<strconfig*> (cfg)->value; + break; + case CONFIG_float: + valstring.format ("%f", static_cast<floatconfig*> (cfg)->value); + + // Trim any trailing zeros + if (valstring.first (".") != -1) { + while (valstring[~valstring - 1] == '0') + valstring -= 1; + + // But don't trim the only one out... + if (valstring[~valstring - 1] == '.') + valstring += '0'; + } + + break; + case CONFIG_bool: + valstring = (static_cast<boolconfig*> (cfg)->value) ? "true" : "false"; + break; + case CONFIG_keyseq: + valstring = static_cast<keyseqconfig*> (cfg)->value.toString (); + break; + default: + break; + } + + const char* sDefault = (cfg->getType() != CONFIG_keyseq) ? cfg->defaultstring : + static_cast<keyseqconfig*> (cfg)->defval.toString ().toUtf8 ().constData (); + + // Write the entry now. + writef (fp, "\n# [%s] default: %s\n", g_ConfigTypeNames[cfg->getType()], sDefault); + writef (fp, "%s=%s\n", cfg->name, valstring.chars()); + } + + fclose (fp); + return true; +} + +// ============================================================================= +void config::reset () { + for (size_t i = 0; i < NUM_CONFIG; i++) + g_configPointers[i]->resetValue (); +} + +// ============================================================================= +str config::filepath () { + str path; + path.format ("%s%s.cfg", dirpath ().chars (), + str (APPNAME).tolower ().chars ()); + return path; +} + +// ============================================================================= +str config::dirpath () { + return fmt ("%s/.%s/", qchars (QDir::homePath ()), + str (APPNAME).tolower ().chars ()); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/config.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,227 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#include "common.h" +#include "str.h" + +// ============================================================================= +#include <QString> +#include <qkeysequence.h> + +#define MAX_INI_LINE 512 +#define NUM_CONFIG (g_pConfigPointers.size ()) + +#define cfg(T, NAME, DEFAULT) \ + T##config NAME (DEFAULT, #NAME, #T, #DEFAULT) + +#define extern_cfg(T, NAME) \ + extern T##config NAME + +// ============================================================================= +enum configtype_e { + CONFIG_none, + CONFIG_int, + CONFIG_str, + CONFIG_float, + CONFIG_bool, + CONFIG_keyseq, +}; + +// ========================================================= +class config { +public: + const char* name, *typestring, *defaultstring; + + virtual configtype_e getType () { + return CONFIG_none; + } + + virtual void resetValue () {} + + // ------------------------------------------ + static bool load (); + static bool save (); + static void reset (); + static str dirpath (); + static str filepath (); +}; + +extern std::vector<config*> g_configPointers; + +// ============================================================================= +#define DEFINE_UNARY_OPERATOR(T, OP) \ + T operator OP () { \ + return (OP value); \ + } \ + +#define DEFINE_BINARY_OPERATOR(T, OP) \ + T operator OP (const T other) { \ + return (value OP other); \ + } \ + +#define DEFINE_ASSIGN_OPERATOR(T, OP) \ + T& operator OP (const T other) { \ + return (value OP other); \ + } \ + +#define DEFINE_COMPARE_OPERATOR(T, OP) \ + bool operator OP (const T other) { \ + return (value OP other); \ + } \ + +#define DEFINE_CAST_OPERATOR(T) \ + operator T () { \ + return (T) value; \ + } \ + +#define DEFINE_ALL_COMPARE_OPERATORS(T) \ + DEFINE_COMPARE_OPERATOR (T, ==) \ + DEFINE_COMPARE_OPERATOR (T, !=) \ + DEFINE_COMPARE_OPERATOR (T, >) \ + DEFINE_COMPARE_OPERATOR (T, <) \ + DEFINE_COMPARE_OPERATOR (T, >=) \ + DEFINE_COMPARE_OPERATOR (T, <=) \ + +#define DEFINE_INCREMENT_OPERATORS(T) \ + T operator++ () {return ++value;} \ + T operator++ (int) {return value++;} \ + T operator-- () {return --value;} \ + T operator-- (int) {return value--;} + +#define CONFIGTYPE(T) \ +class T##config : public config + +#define IMPLEMENT_CONFIG(T) \ + T value, defval; \ + \ + T##config (T _defval, const char* _name, const char* _typestring, \ + const char* _defaultstring) \ + { \ + value = defval = _defval; \ + name = _name; \ + typestring = _typestring; \ + defaultstring = _defaultstring; \ + g_pConfigPointers.push_back (this); \ + } \ + operator T () { \ + return value; \ + } \ + configtype_e getType () { \ + return CONFIG_##T; \ + } \ + virtual void resetValue () { \ + value = defval; \ + } + +// ============================================================================= +CONFIGTYPE (int) { +public: + IMPLEMENT_CONFIG (int) + + // Int-specific operators + DEFINE_ALL_COMPARE_OPERATORS (int) + DEFINE_INCREMENT_OPERATORS (int) + DEFINE_BINARY_OPERATOR (int, +) + DEFINE_BINARY_OPERATOR (int, -) + DEFINE_BINARY_OPERATOR (int, *) + DEFINE_BINARY_OPERATOR (int, /) + DEFINE_BINARY_OPERATOR (int, %) + DEFINE_BINARY_OPERATOR (int, ^) + DEFINE_BINARY_OPERATOR (int, |) + DEFINE_BINARY_OPERATOR (int, &) + DEFINE_BINARY_OPERATOR (int, >>) + DEFINE_BINARY_OPERATOR (int, <<) + DEFINE_UNARY_OPERATOR (int, !) + DEFINE_UNARY_OPERATOR (int, ~) + DEFINE_UNARY_OPERATOR (int, -) + DEFINE_UNARY_OPERATOR (int, +) + DEFINE_ASSIGN_OPERATOR (int, =) + DEFINE_ASSIGN_OPERATOR (int, +=) + DEFINE_ASSIGN_OPERATOR (int, -=) + DEFINE_ASSIGN_OPERATOR (int, *=) + DEFINE_ASSIGN_OPERATOR (int, /=) + DEFINE_ASSIGN_OPERATOR (int, %=) + DEFINE_ASSIGN_OPERATOR (int, >>=) + DEFINE_ASSIGN_OPERATOR (int, <<=) +}; + +// ============================================================================= +CONFIGTYPE (str) { +public: + IMPLEMENT_CONFIG (str) + + DEFINE_ALL_COMPARE_OPERATORS (str) + DEFINE_BINARY_OPERATOR (str, -) + DEFINE_BINARY_OPERATOR (str, *) + DEFINE_UNARY_OPERATOR (str, !) + DEFINE_ASSIGN_OPERATOR (str, =) + DEFINE_ASSIGN_OPERATOR (str, +=) + DEFINE_ASSIGN_OPERATOR (str, -=) + DEFINE_ASSIGN_OPERATOR (str, *=) + DEFINE_CAST_OPERATOR (char*) + + char operator[] (size_t n) { + return value[n]; + } + +#ifdef CONFIG_WITH_QT + operator QString () { + return QString (value.chars()); + } +#endif // CONFIG_WITH_QT +}; + +// ============================================================================= +CONFIGTYPE (float) { +public: + IMPLEMENT_CONFIG (float) + + DEFINE_ALL_COMPARE_OPERATORS (float) + DEFINE_INCREMENT_OPERATORS (float) + DEFINE_BINARY_OPERATOR (float, +) + DEFINE_BINARY_OPERATOR (float, -) + DEFINE_BINARY_OPERATOR (float, *) + DEFINE_UNARY_OPERATOR (float, !) + DEFINE_ASSIGN_OPERATOR (float, =) + DEFINE_ASSIGN_OPERATOR (float, +=) + DEFINE_ASSIGN_OPERATOR (float, -=) + DEFINE_ASSIGN_OPERATOR (float, *=) +}; + +// ============================================================================= +CONFIGTYPE (bool) { +public: + IMPLEMENT_CONFIG (bool) + DEFINE_ALL_COMPARE_OPERATORS (bool) + DEFINE_ASSIGN_OPERATOR (bool, =) +}; + +// ============================================================================= +typedef QKeySequence keyseq; + +CONFIGTYPE (keyseq) { +public: + IMPLEMENT_CONFIG (keyseq) + DEFINE_ALL_COMPARE_OPERATORS (keyseq) + DEFINE_ASSIGN_OPERATOR (keyseq, =) +}; + +#endif // CONFIG_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/configDialog.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,796 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "common.h" +#include "configDialog.h" +#include "file.h" +#include "config.h" +#include "misc.h" +#include "colors.h" +#include "colorSelectDialog.h" +#include <qgridlayout.h> +#include <qfiledialog.h> +#include <qcolordialog.h> +#include <qboxlayout.h> +#include <qevent.h> +#include <qgroupbox.h> + +extern_cfg (str, gl_bgcolor); +extern_cfg (str, gl_maincolor); +extern_cfg (bool, lv_colorize); +extern_cfg (bool, gl_colorbfc); +extern_cfg (float, gl_maincolor_alpha); +extern_cfg (int, gl_linethickness); +extern_cfg (int, gui_toolbar_iconsize); +extern_cfg (str, gui_colortoolbar); +extern_cfg (bool, gl_selflash); +extern_cfg (bool, edit_schemanticinline); +extern_cfg (bool, gl_blackedges); + +ConfigDialog* g_ConfigDialog = null; + +#define INIT_CHECKBOX(BOX, CFG) \ + BOX->setCheckState (CFG ? Qt::Checked : Qt::Unchecked); + +#define APPLY_CHECKBOX(BTN, CFG) \ + CFG = BTN->checkState() == Qt::Checked; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +ConfigDialog::ConfigDialog (ForgeWindow* parent) : QDialog (parent) { + g_ConfigDialog = this; + tabs = new QTabWidget; + + initMainTab (); + initShortcutsTab (); + initQuickColorTab (); + initGridTab (); + initExtProgTab (); + + IMPLEMENT_DIALOG_BUTTONS + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget (tabs); + layout->addWidget (bbx_buttons); + setLayout (layout); + + setWindowTitle ("Settings"); + setWindowIcon (getIcon ("settings")); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ConfigDialog::initMainTab () { + mainTab = new QWidget; + + // ========================================================================= + // Background and foreground colors + lb_viewBg = new QLabel ("Background color:"); + pb_viewBg = new QPushButton; + setButtonBackground (pb_viewBg, gl_bgcolor.value); + connect (pb_viewBg, SIGNAL (clicked ()), + this, SLOT (slot_setGLBackground ())); + pb_viewBg->setWhatsThis ("This is the background color for the viewport."); + + lb_viewFg = new QLabel ("Foreground color:"); + pb_viewFg = new QPushButton; + setButtonBackground (pb_viewFg, gl_maincolor.value); + connect (pb_viewFg, SIGNAL (clicked ()), + this, SLOT (slot_setGLForeground ())); + pb_viewFg->setWhatsThis ("This color is used for the main color."); + + // ========================================================================= + // Alpha and line thickness sliders + lb_viewFgAlpha = new QLabel ("Alpha:"); + makeSlider (sl_viewFgAlpha, 1, 10, (gl_maincolor_alpha * 10.0f)); + sl_viewFgAlpha->setWhatsThis ("Opacity of main color in the viewport."); + + lb_lineThickness = new QLabel ("Line thickness:"); + makeSlider (sl_lineThickness, 1, 8, gl_linethickness); + sl_lineThickness->setWhatsThis ("How thick lines should be drawn in the viewport."); + + // ========================================================================= + // Tool bar icon size slider + lb_iconSize = new QLabel ("Toolbar icon size:"); + makeSlider (sl_iconSize, 1, 5, (gui_toolbar_iconsize - 12) / 4); + + // ========================================================================= + // List view colorizer and BFC red/green view checkboxes + cb_colorize = new QCheckBox ("Colorize polygons in object list"); + cb_colorize->setChecked (lv_colorize); + cb_colorize->setWhatsThis ("Makes colored objects (non-16 and 24) appear " + "colored in the object list. A red polygon will have its description " + "written in red text."); + + cb_colorBFC = new QCheckBox ("Red/green BFC view"); + cb_colorBFC->setChecked (gl_colorbfc); + cb_colorBFC->setWhatsThis ("Polygons' front sides become green and back " + "sides red. Not implemented yet."); + + cb_selFlash = new QCheckBox ("Selection flash"); + cb_selFlash->setChecked (gl_selflash); + cb_colorBFC->setWhatsThis ("A pulse effect for clearer selection view."); + + cb_blackEdges = new QCheckBox ("Black edges"); + cb_blackEdges->setWhatsThis ("Makes all edgelines appear black. If this is " + "not set, edge lines take their color as defined in LDConfig.ldr"); + cb_blackEdges->setChecked (gl_blackedges); + + cb_schemanticInline = new QCheckBox ("Schemantic insertion only"); + cb_schemanticInline->setChecked (edit_schemanticinline); + cb_colorBFC->setWhatsThis ("When inserting objects through inlining, file " + "inserting or through external programs, all non-schemantics (those without " + "actual meaning in the part file like comments and such) are filtered out."); + + cb_schemanticInline->setEnabled (false); + cb_colorBFC->setEnabled (false); + + QGridLayout* layout = new QGridLayout; + layout->addWidget (lb_viewBg, 0, 0); + layout->addWidget (pb_viewBg, 0, 1); + layout->addWidget (lb_viewFg, 0, 2); + layout->addWidget (pb_viewFg, 0, 3); + + layout->addWidget (lb_lineThickness, 1, 0); + layout->addWidget (sl_lineThickness, 1, 1); + layout->addWidget (lb_viewFgAlpha, 1, 2); + layout->addWidget (sl_viewFgAlpha, 1, 3); + + layout->addWidget (lb_iconSize, 2, 0); + layout->addWidget (sl_iconSize, 2, 1); + + layout->addWidget (cb_colorize, 3, 0, 1, 4); + layout->addWidget (cb_colorBFC, 4, 0, 1, 4); + layout->addWidget (cb_selFlash, 5, 0, 1, 4); + layout->addWidget (cb_blackEdges, 6, 0, 1, 4); + layout->addWidget (cb_schemanticInline, 7, 0, 1, 4); + mainTab->setLayout (layout); + + // Add the tab to the manager + tabs->addTab (mainTab, "Main settings"); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ConfigDialog::initShortcutsTab () { + shortcutsTab = new QWidget; + lw_shortcutList = new QListWidget; + lw_shortcutList->setAlternatingRowColors (true); + + shortcutsTab->setWhatsThis ("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.\n" + "\tShortcut changes apply immediately after closing this dialog." ); + + // Init table items + ulong i = 0; + for (actionmeta& info : g_ActionMeta) { + QAction* const act = *info.qAct; + + ShortcutListItem* item = new ShortcutListItem; + setShortcutText (item, info); + item->setIcon (act->icon ()); + item->setActionInfo (&info); + + // 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")); + + lw_shortcutList->insertItem (i++, item); + } + + lw_shortcutList->setSortingEnabled (true); + lw_shortcutList->sortItems (); + + pb_setShortcut = new QPushButton ("Set"); + pb_resetShortcut = new QPushButton ("Reset"); + pb_clearShortcut = new QPushButton ("Clear"); + + connect (pb_setShortcut, SIGNAL (clicked ()), this, SLOT (slot_setShortcut ())); + connect (pb_resetShortcut, SIGNAL (clicked ()), this, SLOT (slot_resetShortcut ())); + connect (pb_clearShortcut, SIGNAL (clicked ()), this, SLOT (slot_clearShortcut ())); + + QVBoxLayout* buttonLayout = new QVBoxLayout; + buttonLayout->addWidget (pb_setShortcut); + buttonLayout->addWidget (pb_resetShortcut); + buttonLayout->addWidget (pb_clearShortcut); + buttonLayout->addStretch (10); + + QGridLayout* layout = new QGridLayout; + layout->addWidget (lw_shortcutList, 0, 0); + layout->addLayout (buttonLayout, 0, 1); + shortcutsTab->setLayout (layout); + tabs->addTab (shortcutsTab, "Shortcuts"); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ConfigDialog::initQuickColorTab () { + quickColorTab = new QWidget; + + pb_addColor = new QPushButton (getIcon ("palette"), "Add"); + pb_delColor = new QPushButton (getIcon ("delete"), "Remove"); + pb_changeColor = new QPushButton (getIcon ("palette"), "Set"); + pb_addColorSeparator = new QPushButton ("Add Separator"); + pb_moveColorUp = new QPushButton (getIcon ("arrow-up"), "Move Up"); + pb_moveColorDown = new QPushButton (getIcon ("arrow-down"), "Move Down"); + pb_clearColors = new QPushButton (getIcon ("delete-all"), "Clear"); + lw_quickColors = new QListWidget; + + quickColorMeta = parseQuickColorMeta (); + updateQuickColorList (); + + QVBoxLayout* buttonLayout = new QVBoxLayout; + buttonLayout->addWidget (pb_addColor); + buttonLayout->addWidget (pb_delColor); + buttonLayout->addWidget (pb_changeColor); + buttonLayout->addWidget (pb_addColorSeparator); + buttonLayout->addWidget (pb_moveColorUp); + buttonLayout->addWidget (pb_moveColorDown); + buttonLayout->addWidget (pb_clearColors); + buttonLayout->addStretch (1); + + connect (pb_addColor, SIGNAL (clicked ()), this, SLOT (slot_setColor ())); + connect (pb_delColor, SIGNAL (clicked ()), this, SLOT (slot_delColor ())); + connect (pb_changeColor, SIGNAL (clicked ()), this, SLOT (slot_setColor ())); + connect (pb_addColorSeparator, SIGNAL (clicked ()), this, SLOT (slot_addColorSeparator ())); + connect (pb_moveColorUp, SIGNAL (clicked ()), this, SLOT (slot_moveColor ())); + connect (pb_moveColorDown, SIGNAL (clicked ()), this, SLOT (slot_moveColor ())); + connect (pb_clearColors, SIGNAL (clicked ()), this, SLOT (slot_clearColors ())); + + QGridLayout* layout = new QGridLayout; + layout->addWidget (lw_quickColors, 0, 0); + layout->addLayout (buttonLayout, 0, 1); + + quickColorTab->setLayout (layout); + tabs->addTab (quickColorTab, "Quick Colors"); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ConfigDialog::initGridTab () { + QWidget* tab = new QWidget; + QGridLayout* layout = new QGridLayout; + QVBoxLayout* l2 = new QVBoxLayout; + + QLabel* xlabel = new QLabel ("X"), + *ylabel = new QLabel ("Y"), + *zlabel = new QLabel ("Z"), + *anglabel = new QLabel ("Angle"); + + short i = 1; + for (QLabel* label : std::initializer_list<QLabel*> ({xlabel, ylabel, zlabel, anglabel})) { + label->setAlignment (Qt::AlignCenter); + layout->addWidget (label, 0, i++); + } + + for (int i = 0; i < g_NumGrids; ++i) { + // Icon + lb_gridIcons[i] = new QLabel; + lb_gridIcons[i]->setPixmap (getIcon (fmt ("grid-%s", str (g_GridInfo[i].name).tolower ().chars ()))); + + // Text label + lb_gridLabels[i] = new QLabel (fmt ("%s:", g_GridInfo[i].name)); + + QHBoxLayout* labellayout = new QHBoxLayout; + labellayout->addWidget (lb_gridIcons[i]); + labellayout->addWidget (lb_gridLabels[i]); + layout->addLayout (labellayout, i + 1, 0); + + // Add the widgets + for (int j = 0; j < 4; ++j) { + dsb_gridData[i][j] = new QDoubleSpinBox; + dsb_gridData[i][j]->setValue (g_GridInfo[i].confs[j]->value); + layout->addWidget (dsb_gridData[i][j], i + 1, j + 1); + } + } + + l2->addLayout (layout); + l2->addStretch (1); + + tab->setLayout (l2); + tabs->addTab (tab, "Grids"); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +extern_cfg (str, prog_ytruder); +extern_cfg (str, prog_rectifier); +extern_cfg (str, prog_intersector); +extern_cfg (str, prog_isecalc); +static const struct extProgInfo { + const char* const name, *iconname; + strconfig* const path; + mutable QLineEdit* input; + mutable QPushButton* setPathButton; +} g_extProgInfo[] = { + { "Ytruder", "ytruder", &prog_ytruder, null, null }, + { "Rectifier", "rectifier", &prog_rectifier, null, null }, + { "Intersector", "intersector", &prog_intersector, null, null }, + { "Isecalc", "isecalc", &prog_isecalc, null, null }, +}; + +void ConfigDialog::initExtProgTab () { + QWidget* tab = new QWidget; + QGridLayout* pathsLayout = new QGridLayout; + QGroupBox* pathsBox = new QGroupBox ("Paths", this); + QVBoxLayout* layout = new QVBoxLayout (this); + + ulong row = 0; + for (const extProgInfo& info : g_extProgInfo) { + QLabel* icon = new QLabel, + *progLabel = new QLabel (info.name); + QLineEdit* input = new QLineEdit; + QPushButton* setPathButton = new QPushButton (); + + icon->setPixmap (getIcon (info.iconname)); + input->setText (info.path->value); + setPathButton->setIcon (getIcon ("folder")); + info.input = input; + info.setPathButton = setPathButton; + + connect (setPathButton, SIGNAL (clicked ()), this, SLOT (slot_setExtProgPath ())); + + pathsLayout->addWidget (icon, row, 0); + pathsLayout->addWidget (progLabel, row, 1); + pathsLayout->addWidget (input, row, 2); + pathsLayout->addWidget (setPathButton, row, 3); + ++row; + } + + pathsBox->setLayout (pathsLayout); + layout->addWidget (pathsBox); + layout->addSpacing (10); + + tab->setLayout (layout); + tabs->addTab (tab, "Ext. Programs"); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ConfigDialog::updateQuickColorList (quickColorMetaEntry* pSel) { + for (QListWidgetItem* qItem : quickColorItems) + delete qItem; + + quickColorItems.clear (); + + // Init table items + for (quickColorMetaEntry& entry : quickColorMeta) { + QListWidgetItem* qItem = new QListWidgetItem; + + if (entry.bSeparator) { + qItem->setText ("--------"); + qItem->setIcon (getIcon ("empty")); + } else { + color* col = entry.col; + + if (col == null) { + qItem->setText ("[[unknown color]]"); + qItem->setIcon (getIcon ("error")); + } else { + qItem->setText (col->zName); + qItem->setIcon (getIcon ("palette")); + } + } + + lw_quickColors->addItem (qItem); + quickColorItems.push_back (qItem); + + if (pSel && &entry == pSel) { + lw_quickColors->setCurrentItem (qItem); + lw_quickColors->scrollToItem (qItem); + } + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ConfigDialog::slot_setColor () { + quickColorMetaEntry* entry = null; + QListWidgetItem* item = null; + const bool isNew = static_cast<QPushButton*> (sender ()) == pb_addColor; + + if (isNew == false) { + item = getSelectedQuickColor (); + if (!item) + return; + + ulong ulIdx = getItemRow (item, quickColorItems); + entry = &quickColorMeta[ulIdx]; + + if (entry->bSeparator == true) + return; // don't color separators + } + + short dDefault = entry ? entry->col->index () : -1; + short dValue; + + if (ColorSelectDialog::staticDialog (dValue, dDefault, this) == false) + return; + + if (entry) + entry->col = getColor (dValue); + else { + quickColorMetaEntry entry = {getColor (dValue), null, false}; + + item = getSelectedQuickColor (); + ulong idx; + + if (item) + idx = getItemRow (item, quickColorItems) + 1; + else + idx = quickColorItems.size(); + + quickColorMeta.insert (quickColorMeta.begin() + idx, entry); + entry = quickColorMeta[idx]; + } + + updateQuickColorList (entry); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ConfigDialog::slot_delColor () { + if (lw_quickColors->selectedItems().size() == 0) + return; + + QListWidgetItem* qItem = lw_quickColors->selectedItems ()[0]; + ulong ulIdx = getItemRow (qItem, quickColorItems); + quickColorMeta.erase (quickColorMeta.begin () + ulIdx); + updateQuickColorList (); +} + +// ============================================================================= +void ConfigDialog::slot_moveColor () { + const bool bUp = (static_cast<QPushButton*> (sender()) == pb_moveColorUp); + + if (lw_quickColors->selectedItems().size() == 0) + return; + + QListWidgetItem* qItem = lw_quickColors->selectedItems ()[0]; + ulong ulIdx = getItemRow (qItem, quickColorItems); + + long lDest = bUp ? (ulIdx - 1) : (ulIdx + 1); + + if (lDest < 0 || (ulong)lDest >= quickColorItems.size ()) + return; // destination out of bounds + + quickColorMetaEntry tmp = quickColorMeta[lDest]; + quickColorMeta[lDest] = quickColorMeta[ulIdx]; + quickColorMeta[ulIdx] = tmp; + + updateQuickColorList (&quickColorMeta[lDest]); +} + +// ============================================================================= +void ConfigDialog::slot_addColorSeparator() { + quickColorMeta.push_back ({null, null, true}); + updateQuickColorList (&quickColorMeta[quickColorMeta.size () - 1]); +} + +// ============================================================================= +void ConfigDialog::slot_clearColors () { + quickColorMeta.clear (); + updateQuickColorList (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ConfigDialog::makeSlider (QSlider*& slider, short min, short max, short defval) { + slider = new QSlider (Qt::Horizontal); + slider->setRange (min, max); + slider->setSliderPosition (defval); + slider->setTickPosition (QSlider::TicksAbove); + slider->setTickInterval (1); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +ConfigDialog::~ConfigDialog () { + g_ConfigDialog = null; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ConfigDialog::pickColor (strconfig& cfg, QPushButton* qButton) { + QColorDialog dlg (QColor (cfg.value.chars())); + dlg.setWindowIcon (getIcon ("colorselect")); + + if (dlg.exec ()) { + uchar r = dlg.currentColor ().red (), + g = dlg.currentColor ().green (), + b = dlg.currentColor ().blue (); + cfg.value.format ("#%.2X%.2X%.2X", r, g, b); + setButtonBackground (qButton, cfg.value); + } +} + +void ConfigDialog::slot_setGLBackground () { + pickColor (gl_bgcolor, pb_viewBg); +} + +void ConfigDialog::slot_setGLForeground () { + pickColor (gl_maincolor, pb_viewFg); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ConfigDialog::setButtonBackground (QPushButton* qButton, str zValue) { + qButton->setIcon (getIcon ("colorselect")); + qButton->setAutoFillBackground (true); + qButton->setStyleSheet ( + fmt ("background-color: %s", zValue.chars()).chars() + ); +} + +// ============================================================================= +long ConfigDialog::getItemRow (QListWidgetItem* qItem, std::vector<QListWidgetItem*>& haystack) { + long i = 0; + + for (QListWidgetItem* it : haystack) { + if (it == qItem) + return i; + ++i; + } + + return -1; +} + +// ============================================================================= +QListWidgetItem* ConfigDialog::getSelectedQuickColor () { + if (lw_quickColors->selectedItems().size() == 0) + return null; + + return lw_quickColors->selectedItems ()[0]; +} + +// ============================================================================= +QList<ShortcutListItem*> ConfigDialog::getShortcutSelection () { + QList<ShortcutListItem*> out; + + for (QListWidgetItem* entry : lw_shortcutList->selectedItems ()) + out << static_cast<ShortcutListItem*> (entry); + + return out; +} + +// ============================================================================= +void ConfigDialog::slot_setShortcut () { + QList<ShortcutListItem*> sel = getShortcutSelection (); + + if (sel.size() < 1) + return; + + ShortcutListItem* item = sel[0]; + if (KeySequenceDialog::staticDialog (*(item->getActionInfo ()), this)) + setShortcutText (item, *(item->getActionInfo ())); +} + +// ============================================================================= +void ConfigDialog::slot_resetShortcut () { + QList<ShortcutListItem*> sel = getShortcutSelection (); + + for (ShortcutListItem* item : sel) { + actionmeta* info = item->getActionInfo (); + keyseqconfig* conf = info->conf; + + conf->reset (); + (*info->qAct)->setShortcut (*conf); + + setShortcutText (item, *info); + } +} + +// ============================================================================= +void ConfigDialog::slot_clearShortcut () { + QList<ShortcutListItem*> sel = getShortcutSelection (); + QKeySequence dummy; + + for (ShortcutListItem* item : sel) { + actionmeta* info = item->getActionInfo (); + keyseqconfig* conf = info->conf; + conf->value = dummy; + + (*info->qAct)->setShortcut (*conf); + setShortcutText (item, *info); + } +} + +// ============================================================================= +void ConfigDialog::slot_setExtProgPath () { + const extProgInfo* info = null; + for (const extProgInfo& it : g_extProgInfo) { + if (it.setPathButton == sender ()) { + info = ⁢ + break; + } + } + + assert (info != null); + + str filter; +#ifdef _WIN32 + filter = "Applications (*.exe)(*.exe);;All files (*.*)(*.*)"; +#endif // WIN32 + + str fpath = QFileDialog::getOpenFileName (this, fmt ("Path to %s", info->name), "", filter); + if (!~fpath) + return; + + info->input->setText (fpath); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ConfigDialog::setShortcutText (QListWidgetItem* qItem, actionmeta meta) { + QAction* const act = *meta.qAct; + str zLabel = act->iconText (); + str zKeybind = act->shortcut ().toString (); + + qItem->setText (fmt ("%s (%s)", zLabel.chars () ,zKeybind.chars ()).chars()); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +str ConfigDialog::makeColorToolBarString () { + str val; + + for (quickColorMetaEntry entry : quickColorMeta) { + if (~val > 0) + val += ':'; + + if (entry.bSeparator) + val += '|'; + else + val.appendformat ("%d", entry.col->index ()); + } + + return val; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ConfigDialog::staticDialog () { + ConfigDialog dlg (g_win); + + if (dlg.exec ()) { + lv_colorize = dlg.cb_colorize->isChecked (); + gl_colorbfc = dlg.cb_colorBFC->isChecked (); + gl_selflash = dlg.cb_selFlash->isChecked (); + edit_schemanticinline = dlg.cb_schemanticInline->isChecked (); + gl_blackedges = dlg.cb_blackEdges->isChecked (); + + gl_maincolor_alpha = ((double)dlg.sl_viewFgAlpha->value ()) / 10.0f; + gl_linethickness = dlg.sl_lineThickness->value (); + gui_toolbar_iconsize = (dlg.sl_iconSize->value () * 4) + 12; + + // Manage the quick color toolbar + g_win->setQuickColorMeta (dlg.quickColorMeta); + gui_colortoolbar = dlg.makeColorToolBarString (); + + // Set the grid settings + for (int i = 0; i < g_NumGrids; ++i) + for (int j = 0; j < 4; ++j) + g_GridInfo[i].confs[j]->value = dlg.dsb_gridData[i][j]->value (); + + // Ext program settings + for (const extProgInfo& info : g_extProgInfo) + *info.path = info.input->text (); + + // Save the config + config::save (); + + // Reload all subfiles as the ldraw path potentially changed. + reloadAllSubfiles (); + + g_win->R ()->setBackground (); + g_win->refresh (); + g_win->updateToolBars (); + } +} + +// ========================================================================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ========================================================================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ========================================================================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ========================================================================================================================= +KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, + Qt::WindowFlags f) : QDialog (parent, f), seq (seq) +{ + lb_output = new QLabel; + IMPLEMENT_DIALOG_BUTTONS + + setWhatsThis ("Into this dialog you can input a key sequence for use as a " + "shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to " + "dismiss."); + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget (lb_output); + layout->addWidget (bbx_buttons); + setLayout (layout); + + updateOutput (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +bool KeySequenceDialog::staticDialog (actionmeta& meta, QWidget* parent) { + KeySequenceDialog dlg (*meta.conf, parent); + + if (dlg.exec () == false) + return false; + + *meta.conf = dlg.seq; + (*meta.qAct)->setShortcut (*meta.conf); + return true; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void KeySequenceDialog::updateOutput () { + str zShortcut = seq.toString (); + + str zText = fmt ("<center><b>%s</b></center>", zShortcut.chars ()); + + lb_output->setText (zText); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) { + seq = ev->key (); + + switch (seq) { + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Alt: + case Qt::Key_Meta: + seq = 0; + break; + + default: + break; + } + + seq = (seq | ev->modifiers ()); + + updateOutput (); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/configDialog.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,143 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include "gui.h" +#include <qdialog.h> +#include <qlabel.h> +#include <qlineedit.h> +#include <qdialogbuttonbox.h> +#include <qpushbutton.h> +#include <qcheckbox.h> +#include <qlistwidget.h> +#include <qspinbox.h> + +// ============================================================================= +class ShortcutListItem : public QListWidgetItem { +public: + explicit ShortcutListItem (QListWidget* view = null, int type = Type) : + QListWidgetItem (view, type) {} + + actionmeta* getActionInfo () const { return m_info; } + void setActionInfo (actionmeta* info) { m_info = info; } + +private: + actionmeta* m_info; +}; + +// ============================================================================= +class ConfigDialog : public QDialog { + Q_OBJECT + +public: + QTabWidget* tabs; + QWidget* mainTab, *shortcutsTab, *quickColorTab, *extProgTab; + + // ========================================================================= + // Main tab widgets + QLabel* lb_viewBg, *lb_viewFg, *lb_viewFgAlpha; + QLabel* lb_lineThickness, *lb_iconSize; + QPushButton* pb_viewBg, *pb_viewFg; + QCheckBox* cb_colorize, *cb_colorBFC, *cb_selFlash, *cb_schemanticInline, + *cb_blackEdges; + QSlider* sl_viewFgAlpha, *sl_lineThickness, *sl_iconSize; + + // ========================================================================= + // Shortcuts tab + QListWidget* lw_shortcutList; + QPushButton* pb_setShortcut, *pb_resetShortcut, *pb_clearShortcut; + + // ========================================================================= + // Quick color toolbar tab + QListWidget* lw_quickColors; + QPushButton* pb_addColor, *pb_delColor, *pb_changeColor, *pb_addColorSeparator, + *pb_moveColorUp, *pb_moveColorDown, *pb_clearColors; + std::vector<QListWidgetItem*> quickColorItems; + std::vector<quickColorMetaEntry> quickColorMeta; + + // ========================================================================= + // Grid tab + QLabel* lb_gridLabels[3]; + QLabel* lb_gridIcons[3]; + QDoubleSpinBox* dsb_gridData[3][4]; + + // ========================================================================= + QDialogButtonBox* bbx_buttons; + + ConfigDialog (ForgeWindow* parent); + ~ConfigDialog (); + static void staticDialog (); + +private: + void initMainTab (); + void initShortcutsTab (); + void initQuickColorTab (); + void initGridTab (); + void initExtProgTab (); + + void makeSlider (QSlider*& slider, short min, short max, short defval); + void setButtonBackground (QPushButton* qButton, str zValue); + void pickColor (strconfig& cfg, QPushButton* qButton); + void updateQuickColorList (quickColorMetaEntry* pSel = null); + void setShortcutText (QListWidgetItem* qItem, actionmeta meta); + long getItemRow (QListWidgetItem* qItem, std::vector<QListWidgetItem*>& haystack); + str makeColorToolBarString (); + QListWidgetItem* getSelectedQuickColor (); + QList<ShortcutListItem*> getShortcutSelection (); + +private slots: + void slot_setGLBackground (); + void slot_setGLForeground (); + + void slot_setShortcut (); + void slot_resetShortcut (); + void slot_clearShortcut (); + + void slot_setColor (); + void slot_delColor (); + void slot_addColorSeparator (); + void slot_moveColor (); + void slot_clearColors (); + + void slot_setExtProgPath (); +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class KeySequenceDialog : public QDialog { + Q_OBJECT + +public: + explicit KeySequenceDialog (QKeySequence seq, QWidget* parent = null, Qt::WindowFlags f = 0); + static bool staticDialog (actionmeta& meta, QWidget* parent = null); + + QLabel* lb_output; + QDialogButtonBox* bbx_buttons; + QKeySequence seq; + +private: + void updateOutput (); + +private slots: + virtual void keyPressEvent (QKeyEvent* ev); +}; + +#endif // CONFIGDIALOG_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/extprogs.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,461 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <qprocess.h> +#include <qtemporaryfile.h> +#include <qeventloop.h> +#include <qdialog.h> +#include <qdialogbuttonbox.h> +#include <qspinbox.h> +#include <qcheckbox.h> +#include <qcombobox.h> +#include "common.h" +#include "config.h" +#include "misc.h" +#include "extprogs.h" +#include "gui.h" +#include "file.h" +#include "radiobox.h" +#include "history.h" + +// ============================================================================= +cfg (str, prog_isecalc, ""); +cfg (str, prog_intersector, ""); +cfg (str, prog_coverer, ""); +cfg (str, prog_ytruder, ""); +cfg (str, prog_datheader, ""); +cfg (str, prog_rectifier, ""); + +const char* g_extProgNames[] = { + "Isecalc", + "Intersector", + "Coverer", + "Ytruder", + "Rectifier", + "DATHeader", +}; + +// ============================================================================= +static bool checkProgPath (str path, const extprog prog) { + if (~path) + return true; + + const char* name = g_extProgNames[prog]; + + critical (fmt ("Couldn't run %s as no path has " + "been defined for it. Use the configuration dialog's External Programs " + "tab to define a path for %s.", name, name)); + return false; +} + +// ============================================================================= +static void processError (const extprog prog, QProcess& proc) { + const char* name = g_extProgNames[prog]; + str errmsg; + + switch (proc.error ()) { + case QProcess::FailedToStart: + errmsg = fmt ("Failed to launch %s. Check that you have set the proper path " + "to %s and that you have the proper permissions to launch it.", name, name); + break; + + case QProcess::Crashed: + errmsg = fmt ("%s crashed.", name); + break; + + case QProcess::WriteError: + case QProcess::ReadError: + errmsg = fmt ("I/O error while interacting with %s.", name); + break; + + case QProcess::UnknownError: + errmsg = fmt ("Unknown error occurred while executing %s.", name); + break; + + case QProcess::Timedout: + errmsg = fmt ("%s timed out.", name); + break; + } + + critical (errmsg); +} + +// ============================================================================= +static bool mkTempFile (QTemporaryFile& tmp, str& fname) { + if (!tmp.open ()) + return false; + + fname = tmp.fileName (); + tmp.close (); + return true; +} + +// ============================================================================= +void writeObjects (std::vector<LDObject*>& objects, str fname) { + // Write the input file + FILE* fp = fopen (fname, "w"); + if (!fp) { + critical (fmt ("Couldn't open temporary file %s for writing.\n", fname.chars ())); + return; + } + + for (LDObject* obj : objects) { + str line = fmt ("%s\r\n", obj->getContents ().chars ()); + fwrite (line.chars(), 1, ~line, fp); + } + +#ifndef RELEASE + ushort idx = rand (); + printf ("%s -> debug_%u\n", fname.chars (), idx); + QFile::copy (fname.chars (), fmt ("debug_%u", idx)); +#endif // RELEASE + + fclose (fp); +} + +// ============================================================================= +void writeSelection (str fname) { + writeObjects (g_win->sel (), fname); +} + +// ============================================================================= +void writeColorGroup (const short colnum, str fname) { + std::vector<LDObject*> objects; + for (LDObject*& obj : g_curfile->m_objs) { + if (obj->isColored () == false || obj->dColor != colnum) + continue; + + objects.push_back (obj); + } + + writeObjects (objects, fname); +} + +// ============================================================================= +void runUtilityProcess (extprog prog, str path, QString argvstr) { + QTemporaryFile input, output; + str inputname, outputname; + QStringList argv = argvstr.split (" ", QString::SkipEmptyParts); + + printf ("cmdline: %s %s\n", path.chars (), qchars (argvstr)); + + if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname)) + return; + + QProcess proc; + + // Init stdin + FILE* stdinfp = fopen (inputname, "w"); + + // Begin! + proc.setStandardInputFile (inputname); + proc.start (path, argv); + + // Write an enter - one is expected + char enter[2] = "\n"; + enter[1] = '\0'; + fwrite (enter, 1, sizeof enter, stdinfp); + fflush (stdinfp); + + // Wait while it runs + proc.waitForFinished (); + +#ifndef RELASE + printf ("%s", qchars (QString (proc.readAllStandardOutput ()))); +#endif // RELEASE + + if (proc.exitStatus () == QProcess::CrashExit) { + processError (prog, proc); + return; + } +} + +// ======================================================================================================================================== +static void insertOutput (str fname, bool replace, vector<short> colorsToReplace) { +#ifndef RELEASE + QFile::copy (fname, "./debug_lastOutput"); +#endif // RELEASE + + // Read the output file + FILE* fp = fopen (fname, "r"); + if (!fp) { + critical (fmt ("Couldn't open temporary file %s for reading.\n", fname.chars ())); + return; + } + + ComboHistory* cmb = new ComboHistory ({}); + std::vector<LDObject*> objs = loadFileContents (fp, null), + copies; + std::vector<ulong> indices; + + // If we replace the objects, delete the selection now. + if (replace) + *cmb << g_win->deleteSelection (); + + for (const short colnum : colorsToReplace) + *cmb << g_win->deleteByColor (colnum); + + // Insert the new objects + g_win->sel ().clear (); + for (LDObject* obj : objs) { + if (!obj->isSchemantic ()) { + delete obj; + continue; + } + + ulong idx = g_curfile->addObject (obj); + indices.push_back (idx); + copies.push_back (obj->clone ()); + g_win->sel ().push_back (obj); + } + + if (indices.size() > 0) + *cmb << new AddHistory ({indices, copies}); + + if (cmb->paEntries.size () > 0) + History::addEntry (cmb); + else + delete cmb; + + fclose (fp); + g_win->refresh (); +} + +QDialogButtonBox* makeButtonBox (QDialog& dlg) { + QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + QWidget::connect (bbx_buttons, SIGNAL (accepted ()), &dlg, SLOT (accept ())); + QWidget::connect (bbx_buttons, SIGNAL (rejected ()), &dlg, SLOT (reject ())); + return bbx_buttons; +} + +// ============================================================================= +// Interface for Ytruder +MAKE_ACTION (ytruder, "Ytruder", "ytruder", "Extrude selected lines to a given plane", KEY (F4)) { + setlocale (LC_ALL, "C"); + + if (!checkProgPath (prog_ytruder, Ytruder)) + return; + + QDialog dlg; + + RadioBox* rb_mode = new RadioBox ("Extrusion mode", {"Distance", "Symmetry", "Projection", "Radial"}, 0, Qt::Horizontal); + RadioBox* rb_axis = new RadioBox ("Axis", {"X", "Y", "Z"}, 0, Qt::Horizontal); + LabeledWidget<QDoubleSpinBox>* dsb_depth = new LabeledWidget<QDoubleSpinBox> ("Plane depth"), + *dsb_condAngle = new LabeledWidget<QDoubleSpinBox> ("Conditional line threshold"); + + rb_axis->setValue (Y); + dsb_depth->w ()->setMinimum (-10000.0); + dsb_depth->w ()->setMaximum (10000.0); + dsb_depth->w ()->setDecimals (3); + dsb_condAngle->w ()->setValue (30.0f); + + QVBoxLayout* layout = new QVBoxLayout (&dlg); + layout->addWidget (rb_mode); + layout->addWidget (rb_axis); + layout->addWidget (dsb_depth); + layout->addWidget (dsb_condAngle); + layout->addWidget (makeButtonBox (dlg)); + + dlg.setWindowIcon (getIcon ("extrude")); + + if (!dlg.exec ()) + return; + + // Read the user's choices + const enum modetype { Distance, Symmetry, Projection, Radial } mode = (modetype) rb_mode->value (); + const Axis axis = (Axis) rb_axis->value (); + const double depth = dsb_depth->w ()->value (), + condAngle = dsb_condAngle->w ()->value (); + + QTemporaryFile indat, outdat; + str inDATName, outDATName; + + // Make temp files for the input and output files + if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) + return; + + // Compose the command-line arguments + str argv = fmt ("%s %s %f -a %f %s %s", + (axis == X) ? "-x" : (axis == Y) ? "-y" : "-z", + (mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r", + depth, condAngle, inDATName.chars (), outDATName.chars ()); + + writeSelection (inDATName); + runUtilityProcess (Ytruder, prog_ytruder, argv); + insertOutput (outDATName, false, {}); +} + +// ======================================================================================================================================== +// Rectifier interface +MAKE_ACTION (rectifier, "Rectifier", "rectifier", "Optimizes quads into rect primitives.", KEY (F8)) { + setlocale (LC_ALL, "C"); + + if (!checkProgPath (prog_rectifier, Rectifier)) + return; + + QDialog dlg; + QCheckBox* cb_condense = new QCheckBox ("Condense triangles to quads"), + *cb_subst = new QCheckBox ("Substitute rect primitives"), + *cb_condlineCheck = new QCheckBox ("Don't replace quads with adj. condlines"), + *cb_colorize = new QCheckBox ("Colorize resulting objects"); + LabeledWidget<QDoubleSpinBox>* dsb_coplthres = new LabeledWidget<QDoubleSpinBox> ("Coplanarity threshold"); + + dsb_coplthres->w ()->setMinimum (0.0f); + dsb_coplthres->w ()->setMaximum (360.0f); + dsb_coplthres->w ()->setDecimals (3); + dsb_coplthres->w ()->setValue (0.95f); + cb_condense->setChecked (true); + cb_subst->setChecked (true); + + QVBoxLayout* layout = new QVBoxLayout (&dlg); + layout->addWidget (cb_condense); + layout->addWidget (cb_subst); + layout->addWidget (cb_condlineCheck); + layout->addWidget (cb_colorize); + layout->addWidget (dsb_coplthres); + layout->addWidget (makeButtonBox (dlg)); + + if (!dlg.exec ()) + return; + + const bool condense = cb_condense->isChecked (), + subst = cb_subst->isChecked (), + condlineCheck = cb_condlineCheck->isChecked (), + colorize = cb_colorize->isChecked (); + const double coplthres = dsb_coplthres->w ()->value (); + + QTemporaryFile indat, outdat; + str inDATName, outDATName; + + // Make temp files for the input and output files + if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName)) + return; + + // Compose arguments + str argv = fmt ("%s %s %s %s -t %f %s %s", + (condense == false) ? "-q" : "", + (subst == false) ? "-r" : "", + (condlineCheck) ? "-a" : "", + (colorize) ? "-c" : "", + coplthres, inDATName.chars (), outDATName.chars ()); + + writeSelection (inDATName); + runUtilityProcess (Rectifier, prog_rectifier, argv); + insertOutput (outDATName, true, {}); +} + +// ======================================================================================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ======================================================================================================================================= +// Intersector interface +MAKE_ACTION (intersector, "Intersector", "intersector", "Perform clipping between two input groups.", KEY (F5)) { + setlocale (LC_ALL, "C"); + + if (!checkProgPath (prog_intersector, Intersector)) + return; + + QDialog dlg; + + LabeledWidget<QComboBox>* cmb_incol = new LabeledWidget<QComboBox> ("Input", new QComboBox), + *cmb_cutcol = new LabeledWidget<QComboBox> ("Cutter", new QComboBox); + QCheckBox* cb_colorize = new QCheckBox ("Colorize output"), + *cb_nocondense = new QCheckBox ("No condensing"), + *cb_repeatInverse = new QCheckBox ("Repeat inverse"), + *cb_edges = new QCheckBox ("Add edges"); + LabeledWidget<QDoubleSpinBox>* dsb_prescale = new LabeledWidget<QDoubleSpinBox> ("Prescaling factor"); + + cb_repeatInverse->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."); + cb_edges->setWhatsThis ("Makes " APPNAME " try run Isecalc to create edgelines for the intersection."); + + makeColorSelector (cmb_incol->w ()); + makeColorSelector (cmb_cutcol->w ()); + dsb_prescale->w ()->setMinimum (0.0f); + dsb_prescale->w ()->setMaximum (10000.0f); + dsb_prescale->w ()->setSingleStep (0.01f); + dsb_prescale->w ()->setValue (1.0f); + + QVBoxLayout* layout = new QVBoxLayout (&dlg); + layout->addWidget (cmb_incol); + layout->addWidget (cmb_cutcol); + + QHBoxLayout* cblayout = new QHBoxLayout; + cblayout->addWidget (cb_colorize); + cblayout->addWidget (cb_nocondense); + + QHBoxLayout* cb2layout = new QHBoxLayout; + cb2layout->addWidget (cb_repeatInverse); + cb2layout->addWidget (cb_edges); + + layout->addLayout (cblayout); + layout->addLayout (cb2layout); + layout->addWidget (dsb_prescale); + layout->addWidget (makeButtonBox (dlg)); + +exec: + if (!dlg.exec ()) + return; + + const short inCol = cmb_incol->w ()->itemData (cmb_incol->w ()->currentIndex ()).toInt (), + cutCol = cmb_cutcol->w ()->itemData (cmb_cutcol->w ()->currentIndex ()).toInt (); + const bool repeatInverse = cb_repeatInverse->isChecked (); + + if (inCol == cutCol) { + critical ("Cannot use the same color group for both input and cutter!"); + goto exec; + } + + // Five temporary files! + // indat = input group file + // cutdat = cutter group file + // outdat = primary output + // outdat2 = inverse output + // edgesdat = edges output (isecalc) + QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat; + str inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName; + + if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) || + !mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) || + !mkTempFile (edgesdat, edgesDATName)) + { + return; + } + + str parms = fmt ("%s %s -s %f", + (cb_colorize->isChecked ()) ? "-c" : "", + (cb_nocondense->isChecked ()) ? "-t" : "", + dsb_prescale->w ()->value ()); + + str argv_normal = fmt ("%s %s %s %s", parms.chars (), inDATName.chars (), cutDATName.chars (), outDATName.chars ()); + str argv_inverse = fmt ("%s %s %s %s", parms.chars (), cutDATName.chars (), inDATName.chars (), outDAT2Name.chars ()); + + writeColorGroup (inCol, inDATName); + writeColorGroup (cutCol, cutDATName); + runUtilityProcess (Intersector, prog_intersector, argv_normal); + insertOutput (outDATName, false, {inCol}); + + if (repeatInverse) { + runUtilityProcess (Intersector, prog_intersector, argv_inverse); + insertOutput (outDAT2Name, false, {cutCol}); + } + + if (cb_edges->isChecked ()) { + runUtilityProcess (Isecalc, prog_isecalc, fmt ("%s %s %s", inDATName.chars (), cutDATName.chars (), edgesDATName.chars ())); + insertOutput (edgesDATName, false, {}); + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/extprogs.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,33 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef EXTPROGS_H +#define EXTPROGS_H + +#include <qobject.h> + +enum extprog { + Isecalc, + Intersector, + Coverer, + Ytruder, + Rectifier, + DATHeader +}; + +#endif // EXTPROGS_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/file.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,757 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <vector> +#include <stdio.h> +#include <qmessagebox.h> +#include <QDir> +#include "common.h" +#include "config.h" +#include "file.h" +#include "misc.h" +#include "bbox.h" +#include "gui.h" +#include "history.h" +#include "ldrawPathDialog.h" + +cfg (str, io_ldpath, ""); +cfg (str, io_recentfiles, ""); + +// ============================================================================= +namespace LDPaths { + static str pathError; + + struct { + str LDConfigPath; + str partsPath, primsPath; + } pathInfo; + + void initPaths () { + if (!tryConfigure (io_ldpath)) { + LDrawPathDialog dlg (false); + + if (!dlg.exec ()) + exit (0); + + io_ldpath = dlg.path (); + } + } + + bool tryConfigure (str path) { + QDir dir; + + if (!dir.cd (path)) { + pathError = "Directory does not exist."; + return false; + } + + QStringList mustHave = {"LDConfig.ldr", "parts", "p"}; + QStringList contents = dir.entryList (mustHave); + + if (contents.size () != mustHave.size ()) { + pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/."; + return false; + } + + pathInfo.partsPath = fmt ("%s" DIRSLASH "parts", path.chars ()); + pathInfo.LDConfigPath = fmt ("%s" DIRSLASH "LDConfig.ldr", path.chars ()); + pathInfo.primsPath = fmt ("%s" DIRSLASH "p", path.chars ()); + + return true; + } + + // Accessors + str getError () { return pathError; } + str ldconfig () { return pathInfo.LDConfigPath; } + str prims () { return pathInfo.primsPath; } + str parts () { return pathInfo.partsPath; } +} + +// ============================================================================= +OpenFile::OpenFile () { + m_implicit = true; + savePos = -1; +} + +// ============================================================================= +OpenFile::~OpenFile () { + // Clear everything from the model + for (LDObject* obj : m_objs) + delete obj; + + // Clear the cache as well + for (LDObject* obj : m_objCache) + delete obj; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +OpenFile* findLoadedFile (str zName) { + for (OpenFile* file : g_loadedFiles) + if (file->m_filename == zName) + return file; + + return null; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +FILE* openLDrawFile (str path, bool bSubDirectories) { + str zTruePath = path; + +#ifndef WIN32 + zTruePath.replace ("\\", "/"); +#endif // WIN32 + + FILE* fp = fopen (path.chars (), "r"); + str zFilePath; + + if (fp != null) + return fp; + + if (~io_ldpath.value) { + // Try with just the LDraw path first + zFilePath = fmt ("%s" DIRSLASH "%s", + io_ldpath.value.chars(), zTruePath.chars()); + logf ("Trying %s\n", zFilePath.chars()); + + fp = fopen (zFilePath, "r"); + if (fp != null) + return fp; + + if (bSubDirectories) { + char const* saSubdirectories[] = { + "parts", + "p", + }; + + for (char const* sSubdir : saSubdirectories) { + zFilePath = fmt ("%s" DIRSLASH "%s" DIRSLASH "%s", + io_ldpath.value.chars(), sSubdir, zTruePath.chars()); + printf ("try %s\n", zFilePath.chars()); + + fp = fopen (zFilePath.chars (), "r"); + + if (fp) + return fp; + } + } + } + + return null; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +std::vector<LDObject*> loadFileContents (FILE* fp, ulong* numWarnings) { + char line[1024]; + vector<str> lines; + vector<LDObject*> objs; + ulong lnum = 0; + + if (numWarnings) + *numWarnings = 0; + + while (fgets (line, sizeof line, fp)) { + // Trim the trailing newline + str data = line; + while (data[~data - 1] == '\n' || data[~data - 1] == '\r') + data -= 1; + + LDObject* obj = parseLine (data); + assert (obj != null); + + // Check for parse errors and warn about tthem + if (obj->getType() == LDObject::Gibberish) { + logf (LOG_Warning, "Couldn't parse line #%lu: %s\n", + lnum, static_cast<LDGibberish*> (obj)->zReason.chars()); + + logf (LOG_Warning, "- Line was: %s\n", data.chars()); + + if (numWarnings) + (*numWarnings)++; + } + + objs.push_back (obj); + lnum++; + } + + return objs; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +OpenFile* openDATFile (str path, bool search) { + logf ("Opening %s...\n", path.chars()); + + // Convert the file name to lowercase since some parts contain uppercase + // file names. I'll assume here that the library will always use lowercase + // file names for the actual parts.. + FILE* fp; + if (search) + fp = openLDrawFile (-path, true); + else + fp = fopen (path, "r"); + + if (!fp) { + logf (LOG_Error, "Couldn't open %s: %s\n", path.chars (), strerror (errno)); + return null; + } + + OpenFile* load = new OpenFile; + load->m_filename = path; + ulong numWarnings; + std::vector<LDObject*> objs = loadFileContents (fp, &numWarnings); + + for (LDObject* obj : objs) + load->m_objs.push_back (obj); + + fclose (fp); + g_loadedFiles.push_back (load); + + logf ("File %s parsed successfully (%lu warning%s).\n", + path.chars(), numWarnings, PLURAL (numWarnings)); + + return load; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +bool OpenFile::safeToClose () { + setlocale (LC_ALL, "C"); + + // If we have unsaved changes, warn and give the option of saving. + if (!m_implicit && History::pos () != savePos) { + switch (QMessageBox::question (g_win, "Unsaved Changes", + fmt ("There are unsaved changes to %s. Should it be saved?", m_filename.chars ()), + (QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel), QMessageBox::Cancel)) + { + case QMessageBox::Yes: + if (!save ()) { + str errormsg = fmt ("Failed to save %s: %s\nDo you still want to close?", + m_filename.chars (), strerror (lastError)); + + if (QMessageBox::critical (g_win, "Save Failure", errormsg, + (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::No) + { + return false; + } + } + + break; + + case QMessageBox::Cancel: + return false; + + default: + break; + } + } + + return true; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void closeAll () { + if (!g_loadedFiles.size()) + return; + + // Remove all loaded files and the objects they contain + for (OpenFile* file : g_loadedFiles) + delete file; + + // Clear the array + g_loadedFiles.clear(); + g_curfile = NULL; + + g_win->refresh (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void newFile () { + // Create a new anonymous file and set it to our current + closeAll (); + + OpenFile* f = new OpenFile; + f->m_filename = ""; + g_loadedFiles.push_back (f); + g_curfile = f; + + g_BBox.calculate(); + g_win->refresh (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void addRecentFile (str zPath) { + long lPos = io_recentfiles.value.first (zPath); + + // If this file already is in the list, pop it out. + if (lPos != -1) { + if (~io_recentfiles.value == ~zPath) + return; // only recent file - do nothing + + // Pop it out. + str zFront = io_recentfiles.value.substr (0, lPos); + str zBack = io_recentfiles.value.substr (lPos + ~zPath + 1, -1); + io_recentfiles.value = zFront + zBack; + } + + // If there's too many recent files, drop one out. + while (io_recentfiles.value.count ('@') > 3) + io_recentfiles.value = io_recentfiles.value.substr (io_recentfiles.value.first ("@") + 1, -1); + + // Add the file + if (~io_recentfiles.value > 0) + io_recentfiles.value += "@"; + + io_recentfiles += zPath; + + config::save (); + g_win->updateRecentFilesMenu (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void openMainFile (str path) { + closeAll (); + + OpenFile* file = openDATFile (path, false); + + if (!file) { + // Tell the user loading failed. + setlocale (LC_ALL, "C"); + critical (fmt ("Failed to open %s: %s", path.chars(), strerror (errno))); + return; + } + + file->m_implicit = false; + g_curfile = file; + + // Recalculate the bounding box + g_BBox.calculate(); + + // Rebuild the object tree view now. + g_win->refresh (); + g_win->setTitle (); + g_win->R ()->resetAngles (); + + History::clear (); + + // Add it to the recent files list. + addRecentFile (path); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +bool OpenFile::save (str path) { + if (!~path) + path = m_filename; + + FILE* fp = fopen (path, "w"); + + if (!fp) { + lastError = errno; + return false; + } + + // Write all entries now + for (LDObject* obj : m_objs) { + // LDraw requires lines to have DOS line endings + str zLine = fmt ("%s\r\n", obj->getContents ().chars ()); + + fwrite (zLine.chars(), 1, ~zLine, fp); + } + + fclose (fp); + + // We have successfully saved, update the save position now. + savePos = History::pos (); + + g_win->setTitle (); + + return true; +} + +#define CHECK_TOKEN_COUNT(N) \ + if (tokens.size() != N) \ + return new LDGibberish (zLine, "Bad amount of tokens"); + +#define CHECK_TOKEN_NUMBERS(MIN,MAX) \ + for (ushort i = MIN; i <= MAX; ++i) \ + if (!isNumber (tokens[i])) \ + return new LDGibberish (zLine, fmt ("Token #%u was `%s`, expected a number", \ + (i + 1), tokens[i].chars())); + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +static vertex parseVertex (vector<str>& s, const ushort n) { + // Disable the locale while parsing the line or atof's behavior changes + // between locales (i.e. fails to read decimals properly). That is + // quite undesired... + setlocale (LC_NUMERIC, "C"); + + vertex v; + for (const Axis ax : g_Axes) + v[ax] = atof (s[n + ax]); + + return v; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +LDObject* parseLine (str zLine) { + vector<str> tokens = zLine.split (" ", true); + + if (!tokens.size ()) { + // Line was empty, or only consisted of whitespace + return new LDEmpty; + } + + if (~tokens[0] != 1) + return new LDGibberish (zLine, "Illogical line code"); + + const char c = tokens[0][0]; + switch (c - '0') { + case 0: + { + // Comment + str comm; + for (uint i = 1; i < tokens.size(); ++i) { + comm += tokens[i]; + + if (i != tokens.size() - 1) + comm += ' '; + } + + // Handle BFC statements + if (tokens.size() > 2 && tokens[1] == "BFC") { + for (short i = 0; i < LDBFC::NumStatements; ++i) + if (comm == fmt ("BFC %s", LDBFC::statements [i])) + return new LDBFC ((LDBFC::Type) i); + + // MLCAD is notorious for stuffing these statements in parts it + // creates. The above block only handles valid statements, so we + // need to handle MLCAD-style invertnext separately. + if (comm == "BFC CERTIFY INVERTNEXT") + return new LDBFC (LDBFC::InvertNext); + } + + if (tokens.size() > 2 && tokens[1] == "!LDFORGE") { + // Handle LDForge-specific types, they're embedded into comments + + if (tokens[2] == "VERTEX") { + // Vertex (0 !LDFORGE VERTEX) + CHECK_TOKEN_COUNT (7) + CHECK_TOKEN_NUMBERS (3, 6) + + LDVertex* obj = new LDVertex; + obj->dColor = atol (tokens[3]); + + for (const Axis ax : g_Axes) + obj->vPosition[ax] = atof (tokens[4 + ax]); // 4 - 6 + + return obj; + } + + if (tokens[2] == "RADIAL") { + CHECK_TOKEN_COUNT (20) + CHECK_TOKEN_NUMBERS (4, 19) + + LDRadial::Type eType = LDRadial::NumTypes; + + for (int i = 0; i < LDRadial::NumTypes; ++i) { + if (str (LDRadial::radialTypeName ((LDRadial::Type) i)).toupper ().strip (' ') == tokens[3]) { + eType = (LDRadial::Type) i; + break; + } + } + + if (eType == LDRadial::NumTypes) + return new LDGibberish (zLine, fmt ("Unknown radial type %s", tokens[3].chars ())); + + LDRadial* obj = new LDRadial; + + obj->eRadialType = eType; // 3 + obj->dColor = atol (tokens[4]); // 4 + obj->dSegments = atol (tokens[5]); // 5 + obj->dDivisions = atol (tokens[6]); // 6 + obj->dRingNum = atol (tokens[7]); // 7 + + obj->vPosition = parseVertex (tokens, 8); // 8 - 10 + + for (short i = 0; i < 9; ++i) + obj->mMatrix[i] = atof (tokens[i + 11]); // 11 - 19 + + return obj; + } + } + + LDComment* obj = new LDComment; + obj->text = comm; + return obj; + } + + case 1: + { + // Subfile + CHECK_TOKEN_COUNT (15) + CHECK_TOKEN_NUMBERS (1, 13) + + // Try open the file + OpenFile* pFile = loadSubfile (tokens[14]); + + // If we cannot open the file, mark it an error + if (!pFile) + return new LDGibberish (zLine, "Could not open referred file"); + + LDSubfile* obj = new LDSubfile; + obj->dColor = atol (tokens[1]); + obj->vPosition = parseVertex (tokens, 2); // 2 - 4 + + for (short i = 0; i < 9; ++i) + obj->mMatrix[i] = atof (tokens[i + 5]); // 5 - 13 + + obj->zFileName = tokens[14]; + obj->pFile = pFile; + return obj; + } + + case 2: + { + CHECK_TOKEN_COUNT (8) + CHECK_TOKEN_NUMBERS (1, 7) + + // Line + LDLine* obj = new LDLine; + obj->dColor = atol (tokens[1]); + for (short i = 0; i < 2; ++i) + obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 7 + return obj; + } + + case 3: + { + CHECK_TOKEN_COUNT (11) + CHECK_TOKEN_NUMBERS (1, 10) + + // Triangle + LDTriangle* obj = new LDTriangle; + obj->dColor = atol (tokens[1]); + + for (short i = 0; i < 3; ++i) + obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 10 + + return obj; + } + + case 4: + { + CHECK_TOKEN_COUNT (14) + CHECK_TOKEN_NUMBERS (1, 13) + + // Quadrilateral + LDQuad* obj = new LDQuad; + obj->dColor = atol (tokens[1]); + + for (short i = 0; i < 4; ++i) + obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 13 + + return obj; + } + + case 5: + { + CHECK_TOKEN_COUNT (14) + CHECK_TOKEN_NUMBERS (1, 13) + + // Conditional line + LDCondLine* obj = new LDCondLine; + obj->dColor = atol (tokens[1]); + + for (short i = 0; i < 4; ++i) + obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 13 + + return obj; + } + + default: // Strange line we couldn't parse + return new LDGibberish (zLine, "Unknown line code number"); + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +OpenFile* loadSubfile (str zFile) { + // Try open the file + OpenFile* pFile = findLoadedFile (zFile); + if (!pFile) + pFile = openDATFile (zFile, true); + + return pFile; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void reloadAllSubfiles () { + if (!g_curfile) + return; + + // First, close all but the current open file. + for (OpenFile* file : g_loadedFiles) + if (file != g_curfile) + delete file; + + g_loadedFiles.clear (); + g_loadedFiles.push_back (g_curfile); + + // Go through all objects in the current file and reload the subfiles + for (LDObject* obj : g_curfile->m_objs) { + if (obj->getType() == LDObject::Subfile) { + // Note: ref->pFile is invalid right now since all subfiles were closed. + LDSubfile* ref = static_cast<LDSubfile*> (obj); + OpenFile* pFile = loadSubfile (ref->zFileName); + + if (pFile) + ref->pFile = pFile; + else { + // Couldn't load the file, mark it an error + ref->replace (new LDGibberish (ref->getContents (), "Could not open referred file")); + } + } + + // Reparse gibberish files. It could be that they are invalid because + // the file could not be opened. Circumstances may be different now. + if (obj->getType() == LDObject::Gibberish) + obj->replace (parseLine (static_cast<LDGibberish*> (obj)->zContents)); + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +ulong OpenFile::addObject (LDObject* obj) { + m_objs.push_back (obj); + + if (this == g_curfile) + g_BBox.calcObject (obj); + + return m_objs.size() - 1; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void OpenFile::insertObj (const ulong pos, LDObject* obj) { + m_objs.insert (m_objs.begin () + pos, obj); + + if (this == g_curfile) + g_BBox.calcObject (obj); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void OpenFile::forgetObject (LDObject* obj) { + // Find the index for the given object + ulong ulIndex; + for (ulIndex = 0; ulIndex < (ulong)m_objs.size(); ++ulIndex) + if (m_objs[ulIndex] == obj) + break; // found it + + if (ulIndex >= m_objs.size ()) + return; // was not found + + // Erase it from memory + m_objs.erase (m_objs.begin() + ulIndex); + + // Update the bounding box + if (this == g_curfile) + g_BBox.calculate (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +std::vector<partListEntry> g_PartList; + +void initPartList () { + logf ("%s: initializing parts.lst\n", __func__); + + FILE* fp = openLDrawFile ("parts.lst", false); + + if (!fp) + return; + + char sLine[1024]; + while (fgets (sLine, sizeof sLine, fp)) { + // Locate the first whitespace + char* cpWhite = strstr (sLine, " "); + + char sName[65]; + size_t uLength = (cpWhite - sLine); + + if (uLength >= 64) + continue; // too long + + strncpy (sName, sLine, uLength); + sName[uLength] = '\0'; + + // Surf through the whitespace sea! + while (*cpWhite == ' ') + cpWhite++; + + // Get the end point + char* cpEnd = strstr (sLine, "\r"); + + if (cpEnd == null) { + // must not be DOS-formatted + cpEnd = strstr (sLine, "\n"); + } + + assert (cpEnd != null); + + // Make the file title now + char sTitle[81]; + uLength = (cpEnd - cpWhite); + strncpy (sTitle, cpWhite, uLength); + sTitle[uLength] = '\0'; + + // Add it to the array. + partListEntry entry; + strcpy (entry.sName, sName); + strcpy (entry.sTitle, sTitle); + g_PartList.push_back (entry); + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/file.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,119 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef FILE_H +#define FILE_H + +#include "common.h" +#include "ldtypes.h" +#include "str.h" + +namespace LDPaths { + void initPaths (); + bool tryConfigure (str path); + + str ldconfig (); + str prims (); + str parts (); + str getError (); +} + +// ============================================================================= +// OpenFile +// +// The OpenFile class stores a file opened in LDForge either as a editable file +// for the user or for subfile caching. Its methods handle file input and output. +// ============================================================================= +class OpenFile { +public: + str m_filename, m_title; + vector<LDObject*> m_objs; + vector<LDObject*> m_objCache; // Cache of this file's contents, if desired + + int lastError; + + // Is this file implicit? Implicit files are opened automatically for + // caching purposes and are hidden from the user. + bool m_implicit; + + OpenFile (); + ~OpenFile (); + + // Saves this file to disk. + bool save (str zPath = ""); + + // Perform safety checks. Do this before closing any files! + bool safeToClose (); + + // Adds an object to this file at the end of the file. + ulong addObject (LDObject* obj); + + // Deletes the given object from the object chain. + void forgetObject (LDObject* obj); + + // At what point was this file last saved? + long savePos; + + LDObject* object (ulong pos) const { + return m_objs[pos]; + } + + void insertObj (const ulong pos, LDObject* obj); +}; + +// Close all current loaded files and start off blank. +void newFile (); + +// Opens the given file as the main file. Everything is closed first. +void openMainFile (str zPath); + +// Finds an OpenFile by name or null if not open +OpenFile* findLoadedFile (str name); + +// Opens the given file and parses the LDraw code within. Returns a pointer +// to the opened file or null on error. +OpenFile* openDATFile (str path, bool search); + +// Opens the given file and returns a pointer to it, potentially looking in /parts and /p +FILE* openLDrawFile (str path, bool bSubDirectories); + +// Close all open files, whether user-opened or subfile caches. +void closeAll (); + +// Parses a string line containing an LDraw object and returns the object parsed. +LDObject* parseLine (str zLine); + +// Retrieves the pointer to - or loads - the given subfile. +OpenFile* loadSubfile (str zFile); + +// Re-caches all subfiles. +void reloadAllSubfiles (); + +typedef struct { + char sName[65], sTitle[81]; +} partListEntry; + +// Init and parse parts.lst +void initPartList (); + +std::vector< LDObject* > loadFileContents (FILE* fp, ulong* numWarnings); + +extern vector<OpenFile*> g_loadedFiles; +extern vector<partListEntry> g_PartList; + +#endif // FILE_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gldraw.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,1214 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QtGui> +#include <QGLWidget> +#include <GL/glu.h> +#include "common.h" +#include "config.h" +#include "file.h" +#include "gldraw.h" +#include "bbox.h" +#include "colors.h" +#include "gui.h" +#include "misc.h" +#include "history.h" + +static double g_objOffset[3]; + +static short g_pulseTick = 0; +static const short g_numPulseTicks = 8; +static const short g_pulseInterval = 65; + +static const struct staticCameraMeta { + const char glrotate[3]; + const Axis axisX, axisY; + const bool negX, negY; +} g_staticCameras[6] = { + { { 1, 0, 0 }, X, Z, false, false }, + { { 0, 0, 0 }, X, Y, false, true }, + { { 0, 1, 0 }, Z, Y, true, true }, + { { -1, 0, 0 }, X, Z, false, true }, + { { 0, 0, 0 }, X, Y, true, true }, + { { 0, -1, 0 }, Z, Y, false, true }, +}; + +cfg (str, gl_bgcolor, "#CCCCD9"); +cfg (str, gl_maincolor, "#707078"); +cfg (float, gl_maincolor_alpha, 1.0); +cfg (int, gl_linethickness, 2); +cfg (bool, gl_colorbfc, true); +cfg (bool, gl_selflash, false); +cfg (int, gl_camera, GLRenderer::Free); +cfg (bool, gl_blackedges, true); +cfg (bool, gl_axes, false); + +// CameraIcon::img is a heap-allocated QPixmap because otherwise it gets +// initialized before program gets to main() and constructs a QApplication +// and Qt doesn't like that. +struct CameraIcon { + QPixmap* img; + QRect srcRect, destRect, selRect; + GLRenderer::Camera cam; +} g_CameraIcons[7]; + +const char* g_CameraNames[7] = { "Top", "Front", "Left", "Bottom", "Back", "Right", "Free" }; + +const GLRenderer::Camera g_Cameras[7] = { + GLRenderer::Top, + GLRenderer::Front, + GLRenderer::Left, + GLRenderer::Bottom, + GLRenderer::Back, + GLRenderer::Right, + GLRenderer::Free +}; + +const struct GLAxis { + const QColor col; + const vertex vert; +} g_GLAxes[3] = { + { QColor (255, 0, 0), vertex (10000, 0, 0) }, + { QColor (128, 192, 0), vertex (0, 10000, 0) }, + { QColor (0, 160, 192), vertex (0, 0, 10000) }, +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) { + resetAngles (); + m_picking = m_rangepick = false; + m_camera = (GLRenderer::Camera) gl_camera.value; + m_drawToolTip = false; + m_planeDraw = false; + + m_pulseTimer = new QTimer (this); + connect (m_pulseTimer, SIGNAL (timeout ()), this, SLOT (slot_timerUpdate ())); + + m_toolTipTimer = new QTimer (this); + m_toolTipTimer->setSingleShot (true); + connect (m_toolTipTimer, SIGNAL (timeout ()), this, SLOT (slot_toolTipTimer ())); + + m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); + m_thinBorderPen = m_thickBorderPen; + m_thinBorderPen.setWidth (1); + + // Init camera icons + for (const GLRenderer::Camera cam : g_Cameras) { + str iconname; + iconname.format ("camera-%s", str (g_CameraNames[cam]).tolower ().chars ()); + + CameraIcon* info = &g_CameraIcons[cam]; + info->img = new QPixmap (getIcon (iconname)); + info->cam = cam; + } + + calcCameraIconRects (); +} + +// ============================================================================= +GLRenderer::~GLRenderer() { + for (CameraIcon& info : g_CameraIcons) + delete info.img; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::calcCameraIconRects () { + ushort i = 0; + + for (CameraIcon& info : g_CameraIcons) { + const long x1 = (m_width - (info.cam != Free ? 48 : 16)) + ((i % 3) * 16) - 1, + y1 = ((i / 3) * 16) + 1; + + info.srcRect = QRect (0, 0, 16, 16); + info.destRect = QRect (x1, y1, 16, 16); + info.selRect = QRect (info.destRect.x (), info.destRect.y (), + info.destRect.width () + 1, info.destRect.height () + 1); + ++i; + } +} + +// ============================================================================= +void GLRenderer::resetAngles () { + m_rotX = 30.0f; + m_rotY = 325.f; + m_panX = m_panY = m_rotZ = 0.0f; + + // Set the default zoom based on the bounding box + m_zoom = g_BBox.size () * 6; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::initializeGL () { + setBackground (); + + glEnable (GL_POLYGON_OFFSET_FILL); + glPolygonOffset (1.0f, 1.0f); + + glEnable (GL_DEPTH_TEST); + glShadeModel (GL_SMOOTH); + glEnable (GL_MULTISAMPLE); + + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnable (GL_LINE_SMOOTH); + glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); + + glLineWidth (gl_linethickness); + + setAutoFillBackground (false); + setMouseTracking (true); + setFocusPolicy (Qt::WheelFocus); + compileObjects (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +QColor GLRenderer::getMainColor () { + QColor col (gl_maincolor.value.chars()); + + if (!col.isValid ()) + return QColor (0, 0, 0); + + col.setAlpha (gl_maincolor_alpha * 255.f); + return col; +} + +// ----------------------------------------------------------------------------- +void GLRenderer::setBackground () { + QColor col (gl_bgcolor.value.chars()); + + if (!col.isValid ()) + return; + + m_darkbg = luma (col) < 80; + + col.setAlpha (255); + qglClearColor (col); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +static vector<short> g_daWarnedColors; +void GLRenderer::setObjectColor (LDObject* obj) { + QColor qcol; + + if (!obj->isColored()) + return; + + if (m_picking) { + // Make the color by the object's index color if we're picking, so we can + // make the index from the color we get from the picking results. + long i = obj->getIndex (g_curfile); + + // If we couldn't find the index, this object must not be from this file, + // therefore it must be an object inlined from a subfile reference or + // decomposed from a radial. Find the top level parent object and use + // its index. + if (i == -1) + i = obj->topLevelParent ()->getIndex (g_curfile); + + // We should have the index now. + assert (i != -1); + + // Calculate a color based from this index. This method caters for + // 16777216 objects. I don't think that'll be exceeded anytime soon. :) + // ATM biggest is 53588.dat with 12600 lines. + double r = (i / (256 * 256)) % 256, + g = (i / 256) % 256, + b = i % 256; + + qglColor (QColor (r, g, b, 255)); + return; + } + +#if 0 + if (gl_colorbfc && + obj->getType () != LDObject::Line && + obj->getType () != LDObject::CondLine) + { + if (bBackSide) + glColor4f (0.9f, 0.0f, 0.0f, 1.0f); + else + glColor4f (0.0f, 0.8f, 0.0f, 1.0f); + return; + } +#endif + + if (obj->dColor == maincolor) + qcol = getMainColor (); + else { + color* col = getColor (obj->dColor); + qcol = col->qColor; + } + + if (obj->dColor == edgecolor) { + qcol = Qt::black; + color* col; + + if (!gl_blackedges && obj->parent != null && (col = getColor (obj->parent->dColor)) != null) + qcol = col->qEdge; + } + + if (qcol.isValid () == false) { + // The color was unknown. Use main color to make the object at least + // not appear pitch-black. + if (obj->dColor != edgecolor) + qcol = getMainColor (); + + // Warn about the unknown colors, but only once. + for (short i : g_daWarnedColors) + if (obj->dColor == i) + return; + + printf ("%s: Unknown color %d!\n", __func__, obj->dColor); + g_daWarnedColors.push_back (obj->dColor); + return; + } + + long r = qcol.red (), + g = qcol.green (), + b = qcol.blue (), + a = qcol.alpha (); + + // If it's selected, brighten it up, also pulse flash it if desired. + if (g_win->isSelected (obj)) { + short tick, numTicks; + + if (gl_selflash) { + tick = (g_pulseTick < (g_numPulseTicks / 2)) ? g_pulseTick : (g_numPulseTicks - g_pulseTick); + numTicks = g_numPulseTicks; + } else { + tick = 2; + numTicks = 5; + } + + const long add = ((tick * 128) / numTicks); + r = min (r + add, 255l); + g = min (g + add, 255l); + b = min (b + add, 255l); + + // a = 255; + } + + glColor4f ( + ((double) r) / 255.0f, + ((double) g) / 255.0f, + ((double) b) / 255.0f, + ((double) a) / 255.0f); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::refresh () { + update (); + swapBuffers (); +} + +// ============================================================================= +void GLRenderer::hardRefresh () { + compileObjects (); + refresh (); + + glLineWidth (gl_linethickness); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::resizeGL (int w, int h) { + m_width = w; + m_height = h; + + calcCameraIconRects (); + + 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 () const { + if (g_curfile == null) + return; + + glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable (GL_DEPTH_TEST); + + if (m_camera != GLRenderer::Free) { + glMatrixMode (GL_PROJECTION); + glPushMatrix (); + + glLoadIdentity (); + glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f); + glTranslatef (m_panX, m_panY, 0.0f); + glRotatef (90.0f, g_staticCameras[m_camera].glrotate[0], + g_staticCameras[m_camera].glrotate[1], + g_staticCameras[m_camera].glrotate[2]); + + // Back camera needs to be handled differently + if (m_camera == GLRenderer::Back) { + glRotatef (180.0f, 1.0f, 0.0f, 0.0f); + glRotatef (180.0f, 0.0f, 0.0f, 1.0f); + } + } else { + glMatrixMode (GL_MODELVIEW); + glPushMatrix (); + glLoadIdentity (); + + glTranslatef (0.0f, 0.0f, -2.0f); + glTranslatef (m_panX, m_panY, -m_zoom); + glRotatef (m_rotX, 1.0f, 0.0f, 0.0f); + glRotatef (m_rotY, 0.0f, 1.0f, 0.0f); + glRotatef (m_rotZ, 0.0f, 0.0f, 1.0f); + } + + for (LDObject* obj : g_curfile->m_objs) { + if (obj->hidden ()) + continue; // Don't draw hidden objects + + glCallList (m_picking == false ? obj->uGLList : obj->uGLPickList); + } + + if (gl_axes && !m_picking) + glCallList (m_axeslist); + + glPopMatrix (); + glMatrixMode (GL_MODELVIEW); +} + +// ============================================================================= +vertex GLRenderer::coord_2to3 (const QPoint& pos2d, const bool snap) const { + vertex pos3d; + const staticCameraMeta* cam = &g_staticCameras[m_camera]; + const Axis axisX = cam->axisX; + const Axis axisY = cam->axisY; + const short negXFac = cam->negX ? -1 : 1, + negYFac = cam->negY ? -1 : 1; + + // Calculate cx and cy - these are the LDraw unit coords the cursor is at. + double cx = (-m_virtWidth + ((2 * pos2d.x () * m_virtWidth) / m_width) - m_panX) - (negXFac * g_objOffset[axisX]); + double cy = (m_virtHeight - ((2 * pos2d.y () * m_virtHeight) / m_height) - m_panY) - (negYFac * g_objOffset[axisY]); + + if (snap) { + cx = Grid::snap (cx, (Grid::Config) axisX); + cy = Grid::snap (cy, (Grid::Config) axisY); + } + + cx *= negXFac; + cy *= negYFac; + + pos3d = g_origin; + pos3d[axisX] = cx; + pos3d[axisY] = cy; + return pos3d; +} + +// ============================================================================= +QPoint GLRenderer::coord_3to2 (const vertex& pos3d) const { + /* + cx = (-m_virtWidth + ((2 * pos2d.x () * m_virtWidth) / m_width) - m_panX) - (negXFac * g_objOffset[axisX]); + + cx = (-vw + ((2 * x * vw) / w) - panx) - (neg * ofs) + cx + (neg * ofs) = (-vw + ((2 * x * vw) / w) - panx) + cx + (neg * ofs) = ((2 * x * vw) / w) - vw - panx + (cx + (neg * ofs)) + vw + panx = (2 * x * vw) / w + ((cx + (neg * ofs)) + vw + panx) * w = 2 * vw * x + + x = (((cx + (neg * ofs)) + vw + panx) * w) / (2 * vw) + */ + + QPoint pos2d; + const staticCameraMeta* cam = &g_staticCameras[m_camera]; + const Axis axisX = cam->axisX; + const Axis axisY = cam->axisY; + const short negXFac = cam->negX ? -1 : 1, + negYFac = cam->negY ? -1 : 1; + + short x1 = (((pos3d[axisX] + (negXFac * g_objOffset[axisX])) + + m_virtWidth + m_panX) * m_width) / (2 * m_virtWidth); + short y1 = -(((pos3d[axisY] + (negYFac * g_objOffset[axisY])) - + m_virtHeight + m_panY) * m_height) / (2 * m_virtHeight); + + x1 *= negXFac; + y1 *= negYFac; + + pos2d = QPoint (x1, y1); + return pos2d; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::paintEvent (QPaintEvent* ev) { + Q_UNUSED (ev) + m_virtWidth = m_zoom; + m_virtHeight = (m_height * m_virtWidth) / m_width; + drawGLScene (); + + QPainter paint (this); + QFontMetrics metrics = QFontMetrics (QFont ()); + paint.setRenderHint (QPainter::Antialiasing); + + m_hoverpos = g_origin; + + if (m_camera != Free) { + // Calculate 3d position of the cursor + m_hoverpos = coord_2to3 (m_pos, true); + + // Paint the coordinates onto the screen. + str text; + text.format ("X: %s, Y: %s, Z: %s", ftoa (m_hoverpos[X]).chars (), + ftoa (m_hoverpos[Y]).chars (), ftoa (m_hoverpos[Z]).chars ()); + + QFontMetrics metrics = QFontMetrics (font ()); + QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); + + paint.drawText (m_width - textSize.width (), m_height - 16, textSize.width (), + textSize.height (), Qt::AlignCenter, text); + + // If we're plane drawing, draw the vertices onto the screen. + if (m_planeDraw) { + ushort numverts = m_planeDrawVerts.size () + 1; + const short blipsize = 8; + + if (numverts > 0) { + QPoint* poly = new QPoint[numverts]; + + uchar i = 0; + for (vertex& vert : m_planeDrawVerts) { + poly[i] = coord_3to2 (vert); + ++i; + } + + // Draw the cursor vertex as the last one in the list. + if (numverts < 5) + poly[i] = coord_3to2 (m_hoverpos); + else + numverts = 4; + + paint.setPen (m_thinBorderPen); + paint.setBrush (QColor (128, 192, 0)); + + // Draw vertex blips + for (ushort i = 0; i < numverts; ++i) { + QPoint& blip = poly[i]; + paint.drawEllipse (blip.x () - blipsize / 2, blip.y () - blipsize / 2, + blipsize, blipsize); + } + + paint.setPen (m_thickBorderPen); + paint.setBrush (QColor (128, 192, 0, 128)); + paint.drawPolygon (poly, numverts); + + delete[] poly; + } + } + } + + // Camera icons + if (!m_picking && m_planeDraw == false) { + // Draw a background for the selected camera + paint.setPen (m_thinBorderPen); + paint.setBrush (QBrush (QColor (0, 128, 160, 128))); + paint.drawRect (g_CameraIcons[camera ()].selRect); + + // Draw the actual icons + for (CameraIcon& info : g_CameraIcons) + paint.drawPixmap (info.destRect, *info.img, info.srcRect); + + // Draw a label for the current camera in the top left corner + { + const ushort margin = 4; + + str label; + label.format ("%s Camera", g_CameraNames[camera ()]); + paint.setBrush (Qt::black); + paint.drawText (QPoint (margin, margin + metrics.ascent ()), label); + } + + // Tool tips + if (m_drawToolTip) { + if (g_CameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false) + m_drawToolTip = false; + else { + QPen bord = m_thinBorderPen; + bord.setBrush (Qt::black); + + const ushort margin = 2; + ushort x0 = m_pos.x (), + y0 = m_pos.y (); + + str label; + label.format ("%s Camera", g_CameraNames[m_toolTipCamera]); + + const ushort textWidth = metrics.width (label), + textHeight = metrics.height (), + fullWidth = textWidth + (2 * margin), + fullHeight = textHeight + (2 * margin); + + QRect area (m_pos.x (), m_pos.y (), fullWidth, fullHeight); + + if (x0 + fullWidth > m_width) + x0 -= fullWidth; + + if (y0 + fullHeight > m_height) + y0 -= fullHeight; + + paint.setBrush (QColor (0, 128, 255, 208)); + paint.setPen (bord); + paint.drawRect (x0, y0, fullWidth, fullHeight); + + paint.setBrush (Qt::black); + paint.drawText (QPoint (x0 + margin, y0 + margin + metrics.ascent ()), label); + } + } + } + + // If we're range-picking, draw a rectangle encompassing the selection area. + if (m_rangepick && !m_picking) { + const short x0 = m_rangeStart.x (), + y0 = m_rangeStart.y (), + x1 = m_pos.x (), + y1 = m_pos.y (); + + QRect rect (x0, y0, x1 - x0, y1 - y0); + QColor fillColor = (m_addpick ? "#80FF00" : "#00CCFF"); + fillColor.setAlphaF (0.2f); + + paint.setPen (m_thickBorderPen); + paint.setBrush (QBrush (fillColor)); + paint.drawRect (rect); + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::compileObjects () { + if (g_BBox.empty () == false) { + g_objOffset[X] = -(g_BBox.v0 ()[X] + g_BBox.v1 ()[X]) / 2; + g_objOffset[Y] = -(g_BBox.v0 ()[Y] + g_BBox.v1 ()[Y]) / 2; + g_objOffset[Z] = -(g_BBox.v0 ()[Z] + g_BBox.v1 ()[Z]) / 2; + } else { + // use a default bbox if we need + g_objOffset[X] = g_objOffset[Y] = g_objOffset[Z] = 0; + } + + if (!g_curfile) { + printf ("renderer: no files loaded, cannot compile anything\n"); + return; + } + + for (LDObject* obj : g_curfile->m_objs) { + GLuint* upaLists[2] = { + &obj->uGLList, + &obj->uGLPickList + }; + + for (GLuint* upMemberList : upaLists) { + GLuint uList = glGenLists (1); + glNewList (uList, GL_COMPILE); + + m_picking = (upMemberList == &obj->uGLPickList); + compileOneObject (obj); + m_picking = false; + + glEndList (); + *upMemberList = uList; + } + } + + // Compile axes + m_axeslist = glGenLists (1); + glNewList (m_axeslist, GL_COMPILE); + glBegin (GL_LINES); + for (const GLAxis& ax : g_GLAxes) { + qglColor (ax.col); + compileVertex (ax.vert); + compileVertex (-ax.vert); + } + glEnd (); + glEndList (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype) { + glBegin (gltype); + + const short numverts = (obj->getType () != LDObject::CondLine) ? obj->vertices () : 2; + + for (short i = 0; i < numverts; ++i) + compileVertex (obj->vaCoords[i]); + + glEnd (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::compileOneObject (LDObject* obj) { + setObjectColor (obj); + + switch (obj->getType ()) { + case LDObject::Line: + compileSubObject (obj, GL_LINES); + break; + + case LDObject::CondLine: + glLineStipple (1, 0x6666); + glEnable (GL_LINE_STIPPLE); + + compileSubObject (obj, GL_LINES); + + glDisable (GL_LINE_STIPPLE); + break; + + case LDObject::Triangle: + compileSubObject (obj, GL_TRIANGLES); + break; + + case LDObject::Quad: + compileSubObject (obj, GL_QUADS); + break; + + case LDObject::Subfile: + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + vector<LDObject*> objs = ref->inlineContents (true, true); + + for (LDObject* obj : objs) { + compileOneObject (obj); + delete obj; + } + } + break; + + case LDObject::Radial: + { + LDRadial* pRadial = static_cast<LDRadial*> (obj); + std::vector<LDObject*> objs = pRadial->decompose (true); + + for (LDObject* obj : objs) { + compileOneObject (obj); + delete obj; + } + } + break; + +#if 0 + TODO: find a proper way to draw vertices without having them be affected by zoom. + case LDObject::Vertex: + { + LDVertex* pVert = static_cast<LDVertex*> (obj); + LDTriangle* pPoly; + vertex* vPos = &(pVert->vPosition); + const double fPolyScale = max (fZoom, 1.0); + +#define BIPYRAMID_COORD(N) ((((i + N) % 4) >= 2 ? 1 : -1) * 0.3f * fPolyScale) + + for (int i = 0; i < 8; ++i) { + pPoly = new LDTriangle; + pPoly->vaCoords[0] = {vPos->x, vPos->y + ((i >= 4 ? 1 : -1) * 0.4f * fPolyScale), vPos->z}; + pPoly->vaCoords[1] = { + vPos->x + BIPYRAMID_COORD (0), + vPos->y, + vPos->z + BIPYRAMID_COORD (1) + }; + + pPoly->vaCoords[2] = { + vPos->x + BIPYRAMID_COORD (1), + vPos->y, + vPos->z + BIPYRAMID_COORD (2) + }; + + pPoly->dColor = pVert->dColor; + compileOneObject (pPoly); + delete pPoly; + } + } + break; +#endif // 0 + + default: + break; + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::compileVertex (const vertex& vrt) { + glVertex3d ( + (vrt[X] + g_objOffset[0]), + -(vrt[Y] + g_objOffset[1]), + -(vrt[Z] + g_objOffset[2])); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::clampAngle (double& angle) { + 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) && !(ev->buttons() & Qt::LeftButton); + const bool wasRight = (m_lastButtons & Qt::RightButton) && !(ev->buttons() & Qt::RightButton); + + if (wasLeft) { + if (m_planeDraw) { + // If we picked an already-existing vertex, stop drawing + for (vertex& vert : m_planeDrawVerts) { + if (vert == m_hoverpos) { + endPlaneDraw (true); + return; + } + } + + // Also, if have 4 verts, also stop drawing. + if (m_planeDrawVerts.size () >= 4) + endPlaneDraw (true); + + m_planeDrawVerts.push_back (m_hoverpos); + + update (); + return; + } else { + if (!m_rangepick) + m_addpick = (m_keymods & Qt::ControlModifier); + + if (m_totalmove < 10 || m_rangepick) + pick (ev->x (), ev->y ()); + } + + m_rangepick = false; + m_totalmove = 0; + return; + } + + if (wasRight && m_planeDraw) { + if (m_planeDrawVerts.size () > 0) { + // Remove the last vertex + m_planeDrawVerts.erase (m_planeDrawVerts.end () - 1); + } else { + endPlaneDraw (false); + return; + } + + update (); + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::mousePressEvent (QMouseEvent* ev) { + if (ev->buttons () & Qt::LeftButton) + m_totalmove = 0; + + if (ev->modifiers () & Qt::ShiftModifier) { + m_rangepick = true; + m_rangeStart.setX (ev->x ()); + m_rangeStart.setY (ev->y ()); + + m_addpick = (m_keymods & Qt::ControlModifier); + } + + m_lastButtons = ev->buttons (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::mouseMoveEvent (QMouseEvent* ev) { + int dx = ev->x () - m_pos.x (); + int dy = ev->y () - m_pos.y (); + m_totalmove += abs (dx) + abs (dy); + + if (ev->buttons () & Qt::LeftButton && !m_rangepick) { + m_rotX = m_rotX + (dy); + m_rotY = m_rotY + (dx); + + clampAngle (m_rotX); + clampAngle (m_rotY); + } + + if (ev->buttons () & Qt::MidButton) { + m_panX += 0.03f * dx * (m_zoom / 7.5f); + m_panY -= 0.03f * dy * (m_zoom / 7.5f); + } + + // Start the tool tip timer + if (!m_drawToolTip) + m_toolTipTimer->start (1000); + + m_pos = ev->pos (); + update (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::keyPressEvent (QKeyEvent* ev) { + m_keymods = ev->modifiers (); +} + +void GLRenderer::keyReleaseEvent (QKeyEvent* ev) { + m_keymods = ev->modifiers (); +} + +// ============================================================================= +void GLRenderer::wheelEvent (QWheelEvent* ev) { + m_zoom *= (ev->delta () < 0) ? 1.2f : (1.0f / 1.2f); + m_zoom = clamp (m_zoom, 0.01, 10000.0); + + update (); + ev->accept (); +} + +// ============================================================================= +void GLRenderer::leaveEvent (QEvent* ev) { + Q_UNUSED (ev); + m_drawToolTip = false; + m_toolTipTimer->stop (); + update (); +} + +// ============================================================================= +void GLRenderer::contextMenuEvent (QContextMenuEvent* ev) { + g_win->spawnContextMenu (ev->globalPos ()); +} + +// ============================================================================= +void GLRenderer::setCamera (const GLRenderer::Camera cam) { + m_camera = cam; + gl_camera = (int) cam; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::updateSelFlash () { + if (gl_selflash && g_win->sel ().size() > 0) { + m_pulseTimer->start (g_pulseInterval); + g_pulseTick = 0; + } else + m_pulseTimer->stop (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::pick (uint mouseX, uint mouseY) { + // Check if we selected a camera icon + if (!m_rangepick) { + QPoint pos (mouseX, mouseY); + + for (CameraIcon& info : g_CameraIcons) { + if (info.destRect.contains (pos)) { + setCamera (info.cam); + update (); + return; + } + } + } + + GLint viewport[4]; + LDObject* removedObject = null; + + // Clear the selection if we do not wish to add to it. + if (!m_addpick) { + std::vector<LDObject*> oldsel = g_win->sel (); + g_win->sel ().clear (); + + // Recompile the prior selection to remove the highlight color + for (LDObject* obj : oldsel) + recompileObject (obj); + } + + m_picking = true; + + // Paint the picking scene + glDisable (GL_DITHER); + glClearColor (1.0f, 1.0f, 1.0f, 1.0f); + drawGLScene (); + + glGetIntegerv (GL_VIEWPORT, viewport); + + short x0 = mouseX, + y0 = mouseY; + short x1, y1; + + // Determine how big an area to read - with range picking, we pick by + // the area given, with single pixel picking, we use an 1 x 1 area. + if (m_rangepick) { + x1 = m_rangeStart.x (); + y1 = m_rangeStart.y (); + } else { + x1 = x0 + 1; + y1 = y0 + 1; + } + + // x0 and y0 must be less than x1 and y1, respectively. + if (x0 > x1) + dataswap (x0, x1); + + if (y0 > y1) + dataswap (y0, y1); + + // Clamp the values to ensure they're within bounds + x0 = max<short> (0, x0); + y0 = max<short> (0, y0); + x1 = min<short> (x1, m_width); + y1 = min<short> (y1, m_height); + + const short areawidth = (x1 - x0); + const short areaheight = (y1 - y0); + const long numpixels = areawidth * areaheight; + + // Allocate space for the pixel data. + uchar* const pixeldata = new uchar[4 * numpixels]; + uchar* pixelptr = &pixeldata[0]; + + assert (viewport[3] == m_height); + + // Read pixels from the color buffer. + glReadPixels (x0, viewport[3] - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata); + + // Go through each pixel read and add them to the selection. + for (long i = 0; i < numpixels; ++i) { + uint32 idx = + (*(pixelptr) * 0x10000) + + (*(pixelptr + 1) * 0x00100) + + (*(pixelptr + 2) * 0x00001); + pixelptr += 4; + + if (idx == 0xFFFFFF) + continue; // White is background; skip + + LDObject* obj = g_curfile->object (idx); + + // If this is an additive single pick and the object is currently selected, + // we remove it from selection instead. + if (!m_rangepick && m_addpick) { + bool removed = false; + + for (ulong i = 0; i < g_win->sel ().size(); ++i) { + if (g_win->sel ()[i] == obj) { + g_win->sel ().erase (g_win->sel ().begin () + i); + removedObject = obj; + removed = true; + } + } + + if (removed) + break; + } + + g_win->sel ().push_back (obj); + } + + delete[] pixeldata; + + // Remove duplicate entries. For this to be effective, the vector must be + // sorted first. + std::vector<LDObject*>& sel = g_win->sel (); + std::sort (sel.begin(), sel.end ()); + std::vector<LDObject*>::iterator pos = std::unique (sel.begin (), sel.end ()); + sel.resize (std::distance (sel.begin (), pos)); + + // Update everything now. + g_win->buildObjList (); + + m_picking = false; + m_rangepick = false; + glEnable (GL_DITHER); + + setBackground (); + updateSelFlash (); + + for (LDObject* obj : g_win->sel ()) + recompileObject (obj); + + if (removedObject != null) + recompileObject (removedObject); + + drawGLScene (); + swapBuffers (); + update (); +} + +// ============================================================================= +void GLRenderer::beginPlaneDraw () { + if (m_camera == Free) + return; // Cannot draw with the free camera + + m_planeDraw = true; + + // Disable the context menu - we need the right mouse button + // for removing vertices. + setContextMenuPolicy (Qt::NoContextMenu); + + // Use the crosshair cursor when plane drawing. + setCursor (Qt::CrossCursor); + + // Clear the selection when beginning to draw onto a plane. + // FIXME: make the selection clearing stuff in ::pick a method and use it + // here! This code doesn't update the GL lists. + g_win->sel ().clear (); + g_win->updateSelection (); + update (); +} + +// ============================================================================= +void GLRenderer::endPlaneDraw (bool accept) { + // If we accepted, clean the selection and create the object + if (accept) { + vector<vertex>& verts = m_planeDrawVerts; + LDObject* obj = null; + + switch (verts.size ()) { + case 1: + { + // 1 vertex - add a vertex object + obj = new LDVertex; + static_cast<LDVertex*> (obj)->vPosition = verts[0]; + obj->dColor = maincolor; + } + break; + + case 2: + { + // 2 verts - make a line + obj = new LDLine; + obj->dColor = edgecolor; + for (ushort i = 0; i < 2; ++i) + obj->vaCoords[i] = verts[i]; + } + break; + + case 3: + case 4: + { + obj = (verts.size () == 3) ? + static_cast<LDObject*> (new LDTriangle) : + static_cast<LDObject*> (new LDQuad); + + obj->dColor = maincolor; + for (ushort i = 0; i < obj->vertices (); ++i) + obj->vaCoords[i] = verts[i]; + } + break; + } + + if (obj) { + g_curfile->addObject (obj); + recompileObject (obj); + g_win->refresh (); + + History::addEntry (new AddHistory ({(ulong) obj->getIndex (g_curfile)}, {obj->clone ()})); + } + } + + m_planeDraw = false; + m_planeDrawVerts.clear (); + + unsetCursor (); + + // Restore the context menu + setContextMenuPolicy (Qt::DefaultContextMenu); + + update (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void GLRenderer::recompileObject (LDObject* obj) { + // Replace the old list with the new one. + GLuint uList = glGenLists (1); + glNewList (uList, GL_COMPILE); + + compileOneObject (obj); + + glEndList (); + obj->uGLList = uList; +} + +// ============================================================================= +uchar* GLRenderer::screencap (ushort& w, ushort& 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); + + // Restore the background + setBackground (); + + return cap; +} + +// ============================================================================= +void GLRenderer::slot_timerUpdate () { + ++g_pulseTick %= g_numPulseTicks; + + for (LDObject* obj : g_win->sel ()) + recompileObject (obj); + + update (); +} + +// ============================================================================= +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 : g_CameraIcons) { + if (icon.destRect.contains (m_pos)) { + m_toolTipCamera = icon.cam; + m_drawToolTip = true; + update (); + break; + } + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gldraw.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,115 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GLDRAW_H +#define GLDRAW_H + +#include <QGLWidget> +#include <qtimer.h> +#include "common.h" +#include "ldtypes.h" + +// ============================================================================= +// GLRenderer +// +// The main renderer object, draws the brick on the screen, manages the camera +// and selection picking. The instance of GLRenderer is accessible as +// g_win->R () +// ============================================================================= +class GLRenderer : public QGLWidget { + Q_OBJECT + +public: + enum Camera { + Top, + Front, + Left, + Bottom, + Back, + Right, + Free + }; + + GLRenderer (QWidget* parent = null); + ~GLRenderer (); + + void hardRefresh (); + void compileObjects (); + void setBackground (); + void pick (uint mouseX, uint mouseY); + QColor getMainColor (); + void recompileObject (LDObject* obj); + void refresh (); + void updateSelFlash (); + void resetAngles (); + uchar* screencap (ushort& w, ushort& h); + void setCamera (const GLRenderer::Camera cam); + void beginPlaneDraw (); + void endPlaneDraw (bool accept); + + GLRenderer::Camera camera () { return m_camera; } + bool picking () { return m_picking; } + void setZoom (const double zoom) { m_zoom = zoom; } + double zoom () const { return m_zoom; } + +protected: + void initializeGL (); + void resizeGL (int w, int h); + + void mousePressEvent (QMouseEvent* ev); + void mouseMoveEvent (QMouseEvent* ev); + void mouseReleaseEvent (QMouseEvent* ev); + void keyPressEvent (QKeyEvent* ev); + void keyReleaseEvent (QKeyEvent* ev); + void wheelEvent (QWheelEvent* ev); + void paintEvent (QPaintEvent* ev); + void leaveEvent (QEvent* ev); + void contextMenuEvent (QContextMenuEvent* ev); + +private: + QTimer* m_pulseTimer, *m_toolTipTimer; + Qt::MouseButtons m_lastButtons; + Qt::KeyboardModifiers m_keymods; + ulong m_totalmove; + vertex m_hoverpos; + double m_virtWidth, m_virtHeight, m_rotX, m_rotY, m_rotZ, m_panX, m_panY, m_zoom; + bool m_darkbg, m_picking, m_rangepick, m_addpick, m_drawToolTip, m_screencap, m_planeDraw; + QPoint m_pos, m_rangeStart; + QPen m_thinBorderPen, m_thickBorderPen; + Camera m_camera, m_toolTipCamera; + uint m_axeslist; + ushort m_width, m_height; + std::vector<vertex> m_planeDrawVerts; + + void compileOneObject (LDObject* obj); + void compileSubObject (LDObject* obj, const GLenum gltype); + void compileVertex (const vertex& vrt); + void clampAngle (double& angle); + void setObjectColor (LDObject* obj); + void drawGLScene () const; + void calcCameraIconRects (); + + vertex coord_2to3 (const QPoint& pos2d, const bool snap) const; + QPoint coord_3to2 (const vertex& pos3d) const; + +private slots: + void slot_timerUpdate (); + void slot_toolTipTimer (); +}; + +#endif // GLDRAW_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,1025 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <qgridlayout.h> +#include <qmessagebox.h> +#include <qevent.h> +#include <qmenubar.h> +#include <qstatusbar.h> +#include <qsplitter.h> +#include <qlistwidget.h> +#include <qtoolbutton.h> +#include <qcombobox.h> +#include <qcoreapplication.h> +#include "common.h" +#include "gldraw.h" +#include "gui.h" +#include "file.h" +#include "config.h" +#include "misc.h" +#include "colors.h" +#include "history.h" +#include "config.h" + +vector<actionmeta> g_ActionMeta; + +static bool g_bSelectionLocked = false; + +cfg (bool, lv_colorize, true); +cfg (int, gui_toolbar_iconsize, 24); +cfg (str, gui_colortoolbar, "16:24:|:0:1:2:3:4:5:6:7"); +extern_cfg (str, io_recentfiles); +extern_cfg (bool, gl_axes); + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +ForgeWindow::ForgeWindow () { + g_win = this; + m_renderer = new GLRenderer; + + m_objList = new ObjectList; + m_objList->setSelectionMode (QListWidget::ExtendedSelection); + m_objList->setAlternatingRowColors (true); + connect (m_objList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_selectionChanged ())); + + m_splitter = new QSplitter; + m_splitter->addWidget (m_renderer); + m_splitter->addWidget (m_objList); + setCentralWidget (m_splitter); + + m_colorMeta = parseQuickColorMeta (); + + createMenuActions (); + createMenus (); + createToolbars (); + + slot_selectionChanged (); + + setStatusBar (new QStatusBar); + setWindowIcon (getIcon ("ldforge")); + setTitle (); + setMinimumSize (320, 200); + resize (800, 600); + + connect (QCoreApplication::instance (), SIGNAL (aboutToQuit ()), this, SLOT (slot_lastSecondCleanup ())); +} + +// ============================================================================= +void ForgeWindow::slot_lastSecondCleanup () { + m_renderer->setParent (null); + delete m_renderer; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::createMenuActions () { + // Create the actions based on stored meta. + for (actionmeta meta : g_ActionMeta) { + QAction*& qAct = *meta.qAct; + qAct = new QAction (getIcon (meta.sIconName), meta.sDisplayName, this); + qAct->setStatusTip (meta.sDescription); + qAct->setShortcut (*meta.conf); + + connect (qAct, SIGNAL (triggered ()), this, SLOT (slot_action ())); + } + + // Grid actions and axes are checkable + findAction ("gridCoarse")->setCheckable (true); + findAction ("gridMedium")->setCheckable (true); + findAction ("gridFine")->setCheckable (true); + + findAction ("axes")->setCheckable (true); + findAction ("axes")->setChecked (gl_axes); + + // things not implemented yet + findAction ("help")->setEnabled (false); + + History::updateActions (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +QMenu* g_CurrentMenu; + +QMenu* ForgeWindow::initMenu (const char* name) { + return g_CurrentMenu = menuBar ()->addMenu (tr (name)); +} + +void ForgeWindow::addMenuAction (const char* name) { + g_CurrentMenu->addAction (findAction (name)); +} + + +// ============================================================================= +void ForgeWindow::createMenus () { + m_recentFilesMenu = new QMenu (tr ("Open &Recent")); + m_recentFilesMenu->setIcon (getIcon ("open-recent")); + updateRecentFilesMenu (); + + QMenu*& menu = g_CurrentMenu; + + // File menu + initMenu ("&File"); + addMenuAction ("newFile"); // New + addMenuAction ("open"); // Open + menu->addMenu (m_recentFilesMenu); // Open Recent + addMenuAction ("save"); // Save + addMenuAction ("saveAs"); // Save As + menu->addSeparator (); // ------- + addMenuAction ("settings"); // Settings + addMenuAction ("setLDrawPath"); // Set LDraw Path + menu->addSeparator (); // ------- + addMenuAction ("exit"); // Exit + + // View menu + initMenu ("&View"); + addMenuAction ("resetView"); // Reset View + addMenuAction ("axes"); // Draw Axes + menu->addSeparator (); // ----- + addMenuAction ("screencap"); // Screencap Part + addMenuAction ("showHistory"); // Edit History + + // Insert menu + initMenu ("&Insert"); + addMenuAction ("insertFrom"); // Insert from File + addMenuAction ("insertRaw"); // Insert Raw + menu->addSeparator (); // ------- + addMenuAction ("newSubfile"); // New Subfile + addMenuAction ("newLine"); // New Line + addMenuAction ("newTriangle"); // New Triangle + addMenuAction ("newQuad"); // New Quad + addMenuAction ("newCondLine"); // New Conditional Line + addMenuAction ("newComment"); // New Comment + addMenuAction ("newBFC"); // New BFC Statment + addMenuAction ("newVertex"); // New Vertex + addMenuAction ("newRadial"); // New Radial + menu->addSeparator (); // ----- + addMenuAction ("beginDraw"); // Begin Drawing + addMenuAction ("doneDraw"); // Cancel Drawing + addMenuAction ("cancelDraw"); // Done Drawing + + // Edit menu + initMenu ("&Edit"); + addMenuAction ("undo"); // Undo + addMenuAction ("redo"); // Redo + menu->addSeparator (); // ----- + addMenuAction ("cut"); // Cut + addMenuAction ("copy"); // Copy + addMenuAction ("paste"); // Paste + addMenuAction ("del"); // Delete + menu->addSeparator (); // ----- + + addMenuAction ("selectAll"); // Select All + addMenuAction ("selectByColor"); // Select by Color + addMenuAction ("selectByType"); // Select by Type + + initMenu ("&Tools"); + addMenuAction ("setColor"); // Set Color + addMenuAction ("invert"); // Invert + addMenuAction ("inlineContents"); // Inline + addMenuAction ("deepInline"); // Deep Inline + addMenuAction ("splitQuads"); // Split Quads + addMenuAction ("setContents"); // Set Contents + addMenuAction ("makeBorders"); // Make Borders + addMenuAction ("makeCornerVerts"); // Make Corner Vertices + addMenuAction ("roundCoords"); // Round Coordinates + addMenuAction ("uncolorize"); // Uncolorize + addMenuAction ("visibility"); // Toggle Visibility + + // Move menu + initMenu ("&Move"); + addMenuAction ("moveUp"); // Move Up + addMenuAction ("moveDown"); // Move Down + menu->addSeparator (); // ----- + addMenuAction ("gridCoarse"); // Coarse Grid + addMenuAction ("gridMedium"); // Medium Grid + addMenuAction ("gridFine"); // Fine Grid + menu->addSeparator (); // ----- + addMenuAction ("moveXPos"); // Move +X + addMenuAction ("moveXNeg"); // Move -X + addMenuAction ("moveYPos"); // Move +Y + addMenuAction ("moveYNeg"); // Move -Y + addMenuAction ("moveZPos"); // Move +Z + addMenuAction ("moveZNeg"); // Move -Z + menu->addSeparator (); // ----- + addMenuAction ("rotateXPos"); // Rotate +X + addMenuAction ("rotateXNeg"); // Rotate -X + addMenuAction ("rotateYPos"); // Rotate +Y + addMenuAction ("rotateYNeg"); // Rotate -Y + addMenuAction ("rotateZPos"); // Rotate +Z + addMenuAction ("rotateZNeg"); // Rotate -Z + + initMenu ("E&xternal Programs"); + addMenuAction ("ytruder"); + addMenuAction ("rectifier"); + addMenuAction ("intersector"); + + // Help menu + initMenu ("&Help"); + addMenuAction ("help"); // Help + menu->addSeparator (); // ----- + addMenuAction ("about"); // About + addMenuAction ("aboutQt"); // About Qt +} + + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::updateRecentFilesMenu () { + // First, clear any items in the recent files menu + for (QAction* recent : m_recentFiles) + delete recent; + m_recentFiles.clear (); + + std::vector<str> files = io_recentfiles.value / "@"; + for (long i = files.size() - 1; i >= 0; --i) { + str file = files[i]; + + QAction* recent = new QAction (getIcon ("open-recent"), file, this); + + connect (recent, SIGNAL (triggered ()), this, SLOT (slot_recentFile ())); + m_recentFilesMenu->addAction (recent); + m_recentFiles.push_back (recent); + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +static QToolBar* g_CurrentToolBar; +static Qt::ToolBarArea g_ToolBarArea = Qt::TopToolBarArea; + +void ForgeWindow::initSingleToolBar (const char* name) { + QToolBar* toolbar = new QToolBar (name); + addToolBar (g_ToolBarArea, toolbar); + m_toolBars.push_back (toolbar); + + g_CurrentToolBar = toolbar; +} + +// ============================================================================= +void ForgeWindow::addToolBarAction (const char* name) { + g_CurrentToolBar->addAction (findAction (name)); +} + +// ============================================================================= +void ForgeWindow::createToolbars () { + initSingleToolBar ("File"); + addToolBarAction ("newFile"); + addToolBarAction ("open"); + addToolBarAction ("save"); + addToolBarAction ("saveAs"); + + // ========================================== + initSingleToolBar ("Insert"); + addToolBarAction ("newSubfile"); + addToolBarAction ("newLine"); + addToolBarAction ("newTriangle"); + addToolBarAction ("newQuad"); + addToolBarAction ("newCondLine"); + addToolBarAction ("newComment"); + addToolBarAction ("newBFC"); + addToolBarAction ("newVertex"); + addToolBarAction ("newRadial"); + + // ========================================== + initSingleToolBar ("Edit"); + addToolBarAction ("undo"); + addToolBarAction ("redo"); + addToolBarAction ("cut"); + addToolBarAction ("copy"); + addToolBarAction ("paste"); + addToolBarAction ("del"); + + // ========================================== + initSingleToolBar ("Select"); + addToolBarAction ("selectAll"); + addToolBarAction ("selectByColor"); + addToolBarAction ("selectByType"); + + addToolBarBreak (Qt::TopToolBarArea); + + // ========================================== + initSingleToolBar ("Move"); + addToolBarAction ("moveUp"); + addToolBarAction ("moveDown"); + addToolBarAction ("moveXPos"); + addToolBarAction ("moveXNeg"); + addToolBarAction ("moveYPos"); + addToolBarAction ("moveYNeg"); + addToolBarAction ("moveZPos"); + addToolBarAction ("moveZNeg"); + + // ========================================== + initSingleToolBar ("Rotate"); + addToolBarAction ("rotateXPos"); + addToolBarAction ("rotateXNeg"); + addToolBarAction ("rotateYPos"); + addToolBarAction ("rotateYNeg"); + addToolBarAction ("rotateZPos"); + addToolBarAction ("rotateZNeg"); + + // ========================================== + // Grid toolbar + initSingleToolBar ("Grids"); + addToolBarAction ("gridCoarse"); + addToolBarAction ("gridMedium"); + addToolBarAction ("gridFine"); + addToolBarBreak (Qt::TopToolBarArea); + + // ========================================== + initSingleToolBar ("View"); + addToolBarAction ("axes"); + + // ========================================== + // Color toolbar + m_colorToolBar = new QToolBar ("Quick Colors"); + addToolBar (Qt::RightToolBarArea, m_colorToolBar); + + // ========================================== + // Left area toolbars + //g_ToolBarArea = Qt::LeftToolBarArea; + initSingleToolBar ("Tools"); + addToolBarAction ("setColor"); + addToolBarAction ("invert"); + addToolBarAction ("inlineContents"); + addToolBarAction ("deepInline"); + addToolBarAction ("splitQuads"); + addToolBarAction ("setContents"); + addToolBarAction ("makeBorders"); + addToolBarAction ("makeCornerVerts"); + addToolBarAction ("roundCoords"); + addToolBarAction ("screencap"); + addToolBarAction ("uncolorize"); + addToolBarAction ("visibility"); + + initSingleToolBar ("External Programs"); + addToolBarAction ("ytruder"); + addToolBarAction ("rectifier"); + addToolBarAction ("intersector"); + updateToolBars (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +std::vector<quickColorMetaEntry> parseQuickColorMeta () { + std::vector<quickColorMetaEntry> meta; + + for (str colorname : gui_colortoolbar.value / ":") { + if (colorname == "|") { + meta.push_back ({null, null, true}); + } else { + color* col = getColor (atoi (colorname)); + assert (col != null); + meta.push_back ({col, null, false}); + } + } + + return meta; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::updateToolBars () { + for (QToolBar* bar : m_toolBars) + bar->setIconSize (QSize (gui_toolbar_iconsize, gui_toolbar_iconsize)); + + // Update the quick color toolbar. + for (QPushButton* btn : m_colorButtons) + delete btn; + + m_colorButtons.clear (); + + // Clear the toolbar to remove separators + m_colorToolBar->clear (); + + for (quickColorMetaEntry& entry : m_colorMeta) { + if (entry.bSeparator) + m_colorToolBar->addSeparator (); + else { + QPushButton* colorButton = new QPushButton; + colorButton->setAutoFillBackground (true); + colorButton->setStyleSheet (fmt ("background-color: %s", entry.col->zColorString.chars())); + colorButton->setToolTip (entry.col->zName); + + connect (colorButton, SIGNAL (clicked ()), this, SLOT (slot_quickColor ())); + m_colorToolBar->addWidget (colorButton); + m_colorButtons.push_back (colorButton); + + entry.btn = colorButton; + } + } + + updateGridToolBar (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::updateGridToolBar () { + // Ensure that the current grid - and only the current grid - is selected. + findAction ("gridCoarse")->setChecked (grid == Grid::Coarse); + findAction ("gridMedium")->setChecked (grid == Grid::Medium); + findAction ("gridFine")->setChecked (grid == Grid::Fine); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::setTitle () { + str title = APPNAME " v"; + title += versionString; + + // Append our current file if we have one + if (g_curfile) { + title += fmt (": %s", basename (g_curfile->m_filename.chars())); + + if (g_curfile->m_objs.size() > 0 && + g_curfile->m_objs[0]->getType() == LDObject::Comment) + { + // Append title + LDComment* comm = static_cast<LDComment*> (g_curfile->m_objs[0]); + title += fmt (": %s", comm->text.chars()); + } + + if (History::pos () != g_curfile->savePos) + title += '*'; + } + + setWindowTitle (title); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::slot_action () { + // Get the action that triggered this slot. + QAction* qAct = static_cast<QAction*> (sender ()); + + // Find the meta for the action. + actionmeta* pMeta = null; + + for (actionmeta meta : g_ActionMeta) { + if (*meta.qAct == qAct) { + pMeta = &meta; + break; + } + } + + if (!pMeta) { + logf (LOG_Warning, "unknown signal sender %p!\n", qAct); + return; + } + + // We have the meta, now call the handler. + (*pMeta->handler) (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::deleteSelection (vector<ulong>* ulapIndices, std::vector<LDObject*>* papObjects) { + if (m_sel.size () == 0) + return; + + std::vector<LDObject*> selCopy = m_sel; + + // Delete the objects that were being selected + for (LDObject* obj : selCopy) { + if (papObjects && ulapIndices) { + papObjects->push_back (obj->clone ()); + ulapIndices->push_back (obj->getIndex (g_curfile)); + } + + g_curfile->forgetObject (obj); + delete obj; + } + + refresh (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::buildObjList () { + if (!g_curfile) + return; + + // Lock the selection while we do this so that refreshing the object list + // doesn't trigger selection updating so that the selection doesn't get lost + // while this is done. + g_bSelectionLocked = true; + + m_objList->clear (); + + for (LDObject* obj : g_curfile->m_objs) { + str descr; + switch (obj->getType ()) { + case LDObject::Comment: + descr = static_cast<LDComment*> (obj)->text.chars(); + + // Remove leading whitespace + while (~descr && descr[0] == ' ') + descr -= -1; + break; + + case LDObject::Empty: + break; // leave it empty + + case LDObject::Line: + { + LDLine* line = static_cast<LDLine*> (obj); + descr.format ("%s, %s", + line->vaCoords[0].stringRep (true).chars(), + line->vaCoords[1].stringRep (true).chars()); + } + break; + + case LDObject::Triangle: + { + LDTriangle* triangle = static_cast<LDTriangle*> (obj); + descr.format ("%s, %s, %s", + triangle->vaCoords[0].stringRep (true).chars(), + triangle->vaCoords[1].stringRep (true).chars(), + triangle->vaCoords[2].stringRep (true).chars()); + } + break; + + case LDObject::Quad: + { + LDQuad* quad = static_cast<LDQuad*> (obj); + descr.format ("%s, %s, %s, %s", + quad->vaCoords[0].stringRep (true).chars(), + quad->vaCoords[1].stringRep (true).chars(), + quad->vaCoords[2].stringRep (true).chars(), + quad->vaCoords[3].stringRep (true).chars()); + } + break; + + case LDObject::CondLine: + { + LDCondLine* line = static_cast<LDCondLine*> (obj); + descr.format ("%s, %s, %s, %s", + line->vaCoords[0].stringRep (true).chars(), + line->vaCoords[1].stringRep (true).chars(), + line->vaCoords[2].stringRep (true).chars(), + line->vaCoords[3].stringRep (true).chars()); + } + break; + + case LDObject::Gibberish: + descr.format ("ERROR: %s", + static_cast<LDGibberish*> (obj)->zContents.chars()); + break; + + case LDObject::Vertex: + descr.format ("%s", static_cast<LDVertex*> (obj)->vPosition.stringRep (true).chars()); + break; + + case LDObject::Subfile: + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + + descr.format ("%s %s, (", + ref->zFileName.chars(), ref->vPosition.stringRep (true).chars()); + + for (short i = 0; i < 9; ++i) + descr.appendformat ("%s%s", + ftoa (ref->mMatrix[i]).chars(), + (i != 8) ? " " : ""); + + descr += ')'; + } + break; + + case LDObject::BFC: + { + LDBFC* bfc = static_cast<LDBFC*> (obj); + descr = LDBFC::statements[bfc->type]; + } + break; + + case LDObject::Radial: + { + LDRadial* pRad = static_cast<LDRadial*> (obj); + descr.format ("%d / %d %s", pRad->dSegments, pRad->dDivisions, pRad->radialTypeName()); + + if (pRad->eRadialType == LDRadial::Ring || pRad->eRadialType == LDRadial::Cone) + descr.appendformat (" %d", pRad->dRingNum); + + descr.appendformat (" %s", pRad->vPosition.stringRep (true).chars ()); + } + break; + + default: + descr = g_saObjTypeNames[obj->getType ()]; + break; + } + + // Put it into brackets if it's hidden + if (obj->hidden ()) { + str copy = descr.chars (); + descr.format ("[[ %s ]]", copy.chars ()); + } + + QListWidgetItem* item = new QListWidgetItem (descr.chars()); + item->setIcon (getIcon (g_saObjTypeIcons[obj->getType ()])); + + // Color gibberish orange on red so it stands out. + if (obj->getType() == LDObject::Gibberish) { + item->setBackground (QColor ("#AA0000")); + item->setForeground (QColor ("#FFAA00")); + } else if (lv_colorize && obj->isColored () && + obj->dColor != maincolor && obj->dColor != edgecolor) + { + // If the object isn't in the main or edge color, draw this + // list entry in said color. + color* col = getColor (obj->dColor); + if (col) + item->setForeground (col->qColor); + } + + obj->qObjListEntry = item; + m_objList->insertItem (m_objList->count (), item); + } + + g_bSelectionLocked = false; + updateSelection (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::scrollToSelection () { + if (m_sel.size() == 0) + return; + + LDObject* obj = m_sel[m_sel.size () - 1]; + m_objList->scrollToItem (obj->qObjListEntry); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::slot_selectionChanged () { + if (g_bSelectionLocked == true || g_curfile == null) + return; + + /* + // If the selection isn't 1 exact, disable setting contents + findAction ("setContents")->setEnabled (qObjList->selectedItems().size() == 1); + + // If we have no selection, disable splitting quads + findAction ("splitQuads")->setEnabled (qObjList->selectedItems().size() > 0); + */ + + // Update the shared selection array, though don't do this if this was + // called during GL picking, in which case the GL renderer takes care + // of the selection. + if (m_renderer->picking ()) + return; + + std::vector<LDObject*> priorSelection = m_sel; + + // Get the objects from the object list selection + m_sel.clear (); + const QList<QListWidgetItem*> items = m_objList->selectedItems (); + + for (LDObject* obj : g_curfile->m_objs) + for (QListWidgetItem* qItem : items) { + if (qItem == obj->qObjListEntry) { + m_sel.push_back (obj); + break; + } + } + + // Update the GL renderer + for (LDObject* obj : m_sel) + m_renderer->recompileObject (obj); + + for (LDObject* obj : priorSelection) + m_renderer->recompileObject (obj); + + m_renderer->updateSelFlash (); + m_renderer->refresh (); +} + +// ============================================================================= +void ForgeWindow::slot_recentFile () { + QAction* qAct = static_cast<QAction*> (sender ()); + openMainFile (qAct->text ()); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::slot_quickColor () { + QPushButton* button = static_cast<QPushButton*> (sender ()); + color* col = null; + + for (quickColorMetaEntry entry : m_colorMeta) { + if (entry.btn == button) { + col = entry.col; + break; + } + } + + if (col == null) + return; + + std::vector<ulong> indices; + std::vector<short> colors; + short newColor = col->index (); + + for (LDObject* obj : m_sel) { + if (obj->dColor == -1) + continue; // uncolored object + + indices.push_back (obj->getIndex (g_curfile)); + colors.push_back (obj->dColor); + + obj->dColor = newColor; + } + + History::addEntry (new SetColorHistory (indices, colors, newColor)); + refresh (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +ulong ForgeWindow::getInsertionPoint () { + if (m_sel.size () > 0) { + // If we have a selection, put the item after it. + return (m_sel[m_sel.size() - 1]->getIndex (g_curfile)) + 1; + } + + // Otherwise place the object at the end. + return g_curfile->m_objs.size(); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::refresh () { + buildObjList (); + m_renderer->hardRefresh (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::updateSelection () { + g_bSelectionLocked = true; + + m_objList->clearSelection (); + for (LDObject* obj : m_sel) + obj->qObjListEntry->setSelected (true); + + g_bSelectionLocked = false; + slot_selectionChanged (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +bool ForgeWindow::isSelected (LDObject* obj) { + LDObject* needle = obj->topLevelParent (); + + if (needle == null) + needle = obj; + + for (LDObject* hay : m_sel) + if (hay == needle) + return true; + + return false; +} + +short ForgeWindow::getSelectedColor() { + short result = -1; + + for (LDObject* obj : m_sel) { + if (obj->dColor == -1) + continue; // doesn't use color + + if (result != -1 && obj->dColor != result) + return -1; // No consensus in object color + + if (result == -1) + result = obj->dColor; + } + + return result; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +LDObject::Type ForgeWindow::uniformSelectedType () { + LDObject::Type eResult = LDObject::Unidentified; + + for (LDObject* obj : m_sel) { + if (eResult != LDObject::Unidentified && obj->dColor != eResult) + return LDObject::Unidentified; + + if (eResult == LDObject::Unidentified) + eResult = obj->getType (); + } + + return eResult; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::closeEvent (QCloseEvent* ev) { + // Check whether it's safe to close all files. + for (OpenFile* f : g_loadedFiles) { + if (!f->safeToClose ()) { + ev->ignore (); + return; + } + } + + // Save the configuration before leaving so that, for instance, grid choice + // is preserved across instances. + config::save (); + + ev->accept (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void ForgeWindow::spawnContextMenu (const QPoint pos) { + const bool single = (g_win->sel ().size () == 1); + LDObject* singleObj = (single) ? g_win->sel ()[0] : null; + + QMenu* contextMenu = new QMenu; + + if (single && singleObj->getType () != LDObject::Empty) { + contextMenu->addAction (findAction ("editObject")); + contextMenu->addSeparator (); + } + + contextMenu->addAction (findAction ("cut")); + contextMenu->addAction (findAction ("copy")); + contextMenu->addAction (findAction ("paste")); + contextMenu->addAction (findAction ("del")); + contextMenu->addSeparator (); + contextMenu->addAction (findAction ("setColor")); + if (single) + contextMenu->addAction (findAction ("setContents")); + contextMenu->addAction (findAction ("makeBorders")); + + contextMenu->exec (pos); +} + +// ======================================================================================================================================== +DelHistory* ForgeWindow::deleteObjVector (const std::vector<LDObject*> objs) { + vector<ulong> indices; + vector<LDObject*> cache; + + for (LDObject* obj : objs) { + indices.push_back (obj->getIndex (g_curfile)); + cache.push_back (obj->clone ()); + + g_curfile->forgetObject (obj); + delete obj; + } + + if (indices.size () > 0) + return new DelHistory (indices, cache); + + return null; +} + +// ======================================================================================================================================== +DelHistory* ForgeWindow::deleteSelection () { + return deleteObjVector (sel ()); +} + +// ======================================================================================================================================== +DelHistory* ForgeWindow::deleteByColor (const short colnum) { + vector<LDObject*> objs; + for (LDObject* obj : g_curfile->m_objs) { + if (!obj->isColored () || obj->dColor != colnum) + continue; + + objs.push_back (obj); + } + + return deleteObjVector (objs); +} + +// ======================================================================================================================================== +void ObjectList::contextMenuEvent (QContextMenuEvent* ev) { + g_win->spawnContextMenu (ev->globalPos ()); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +QPixmap getIcon (const char* iconName) { + return (QPixmap (fmt (":/icons/%s.png", iconName))); +} + +// ============================================================================= +bool confirm (str msg) { + return confirm ("Confirm", msg); +} + +bool confirm (str title, str msg) { + return QMessageBox::question (g_win, title, msg, + (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes; +} + +// ============================================================================= +void critical (str msg) { + QMessageBox::critical (g_win, "Error", msg, + (QMessageBox::Close), QMessageBox::Close); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +// Print to message log +// TODO: I don't think that the message log being a widget in the window +// is a very good idea... maybe this should log into the renderer? Or into +// another dialog that can be popped up? +void ForgeWindow::logVA (LogType type, const char* fmtstr, va_list va) { + (void) type; + (void) fmtstr; + (void) va; +} + +// ============================================================================= +QAction* findAction (str name) { + for (actionmeta& meta : g_ActionMeta) + if (name == meta.name) + return *meta.qAct; + + fprintf (stderr, "%s: couldn't find action named `%s'!\n", __func__, name.chars ()); + assert (false); + return null; +} + +// ============================================================================= +void makeColorSelector (QComboBox* box) { + std::map<short, ulong> counts; + + for (LDObject* obj : g_curfile->m_objs) { + if (!obj->isColored ()) + continue; + + if (counts.find (obj->dColor) == counts.end ()) + counts[obj->dColor] = 1; + else + counts[obj->dColor]++; + } + + box->clear (); + ulong row = 0; + for (const auto& pair : counts) { + const ushort size = 16; + color* col = getColor (pair.first); + assert (col != null); + + // Paint an icon for this color + QIcon ico; + QImage img (size, size, QImage::Format_ARGB32); + QPainter paint (&img); + paint.fillRect (QRect (0, 0, size, size), Qt::black); + paint.drawPixmap (QRect (1, 1, size - 2, size - 2), getIcon ("checkerboard"), QRect (0, 0, 8, 8)); + paint.fillRect (QRect (1, 1, size - 2, size - 2), QColor (col->qColor)); + ico = QIcon (QPixmap::fromImage (img)); + + box->addItem (ico, fmt ("[%d] %s (%lu object%s)", + pair.first, col->zName.chars (), pair.second, PLURAL (pair.second))); + box->setItemData (row, pair.first); + + ++row; + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,253 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GUI_H +#define GUI_H + +#include <QMainWindow> +#include <QMenu> +#include <QToolBar> +#include <QAction> +#include <QToolBar> +#include <QTextEdit> +#include <qpushbutton.h> +#include <qlistwidget.h> +#include <qlabel.h> +#include <qboxlayout.h> +#include "gldraw.h" +#include "config.h" + +class QComboBox; +class ForgeWindow; +class color; +class QSplitter; +class DelHistory; + +// Stuff for dialogs +#define IMPLEMENT_DIALOG_BUTTONS \ + bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); \ + connect (bbx_buttons, SIGNAL (accepted ()), this, SLOT (accept ())); \ + connect (bbx_buttons, SIGNAL (rejected ()), this, SLOT (reject ())); \ + +// ============================================================================= +// Metadata for actions +typedef struct { + QAction** const qAct; + keyseqconfig* const conf; + const char* const name, *sDisplayName, *sIconName, *sDescription; + void (*const handler) (); +} actionmeta; + +extern vector<actionmeta> g_ActionMeta; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +#define MAKE_ACTION(NAME, DISPLAYNAME, ICONNAME, DESCR, DEFSHORTCUT) \ + QAction* ACTION (NAME); \ + cfg (keyseq, key_##NAME, DEFSHORTCUT); \ + static void actionHandler_##NAME (); \ + static ActionAdder ActionAdderInstance_##NAME (&ACTION(NAME), DISPLAYNAME, \ + ICONNAME, DESCR, &key_##NAME, actionHandler_##NAME, #NAME); \ + static void actionHandler_##NAME () + +#define EXTERN_ACTION(NAME) extern QAction* ACTION (NAME); +#define ACTION(N) LDForgeAction_##N + +// Convenience macros for key sequences. +#define KEY(N) (Qt::Key_##N) +#define CTRL(N) (Qt::CTRL | Qt::Key_##N) +#define SHIFT(N) (Qt::SHIFT | Qt::Key_##N) +#define CTRL_SHIFT(N) (Qt::CTRL | Qt::SHIFT | Qt::Key_##N) + +// ============================================================================= +typedef struct { + color* col; + QPushButton* btn; + bool bSeparator; +} quickColorMetaEntry; + +// ============================================================================= +// ActionAdder +// +// The ACTION macro expands into - among other stuff - into an instance of this. +// This class' constructor creates meta for the newly defined action and stores +// it in g_ActionMeta. It is not supposed to be used directly! +// ============================================================================= +class ActionAdder { +public: + ActionAdder (QAction** qAct, const char* sDisplayName, const char* sIconName, + const char* sDescription, keyseqconfig* conf, void (*const handler) (), + const char* name) + { + actionmeta meta = {qAct, conf, name, sDisplayName, sIconName, sDescription, handler}; + g_ActionMeta.push_back (meta); + } +}; + +// ============================================================================= +// ObjectList +// +// Object list class for ForgeWindow +// ============================================================================= +class ObjectList : public QListWidget { + Q_OBJECT + +protected: + void contextMenuEvent (QContextMenuEvent* ev); +}; + +// ============================================================================= +// ForgeWindow +// +// The one main GUI class. Hosts the renderer, object list, message log. Contains +// slot_action, which is what all actions connect to. Manages menus and toolbars. +// Large and in charge. +// ============================================================================= +class ForgeWindow : public QMainWindow { + Q_OBJECT + +public: + ForgeWindow (); + void buildObjList (); + void setTitle (); + void refresh (); + ulong getInsertionPoint (); + void deleteSelection (vector<ulong>* ulapIndices, std::vector<LDObject*>* papObjects); + void updateToolBars (); + void updateRecentFilesMenu (); + void updateSelection (); + void updateGridToolBar (); + bool isSelected (LDObject* obj); + short getSelectedColor(); + LDObject::Type uniformSelectedType (); + void scrollToSelection (); + void spawnContextMenu (const QPoint pos); + DelHistory* deleteObjVector (const std::vector<LDObject*> objs); + DelHistory* deleteSelection (); + DelHistory* deleteByColor (const short colnum); + GLRenderer* R () { return m_renderer; } + std::vector<LDObject*>& sel () { return m_sel; } + void setQuickColorMeta (std::vector<quickColorMetaEntry>& quickColorMeta) { + m_colorMeta = quickColorMeta; + } + +protected: + void closeEvent (QCloseEvent* ev); + void logVA (LogType eType, const char* fmtstr, va_list va); + + friend void logf (const char* fmt, ...); + friend void logf (LogType eType, const char* fmt, ...); + friend void warnf (const char* fmt, ...); + friend void infof (const char* fmt, ...); + friend void succf (const char* fmt, ...); + friend void errf (const char* fmt, ...); + friend void devf (const char* fmt, ...); + +private: + GLRenderer* m_renderer; + ObjectList* m_objList; + QMenu* m_recentFilesMenu; + QSplitter* m_splitter; + str m_msglogHTML; + QToolBar* m_colorToolBar; + std::vector<QToolBar*> m_toolBars; + std::vector<LDObject*> m_sel; + std::vector<quickColorMetaEntry> m_colorMeta; + std::vector<QPushButton*> m_colorButtons; + std::vector<QAction*> m_recentFiles; + + void createMenuActions (); + void createMenus (); + void createToolbars (); + void initSingleToolBar (const char* name); + void addToolBarAction (const char* name); + + QMenu* initMenu (const char* name); + void addMenuAction (const char* name); + +private slots: + void slot_selectionChanged (); + void slot_action (); + void slot_recentFile (); + void slot_quickColor (); + void slot_lastSecondCleanup (); +}; + +// ============================================================================= +// LabeledWidget +// +// Convenience class for a widget with a label beside it. +// ============================================================================= +template<class R> class LabeledWidget : public QWidget { +public: + explicit LabeledWidget (const char* labelstr, QWidget* parent = null) : QWidget (parent) { + m_widget = new R (this); + commonInit (labelstr); + } + + explicit LabeledWidget (const char* labelstr, R* widget, QWidget* parent = null) : + QWidget (parent), m_widget (widget) + { + commonInit (labelstr); + } + + explicit LabeledWidget (QWidget* parent = 0, Qt::WindowFlags f = 0) { + m_widget = new R (this); + commonInit (""); + } + + R* widget () const { return m_widget; } + R* w () const { return m_widget; } + QLabel* label () const { return m_label; } + QLabel* l () const { return m_label; } + void setWidget (R* widget) { m_widget = widget; } + void setLabel (QLabel* label) { m_label = label; } + operator R* () { return m_widget; } + +private: + Q_DISABLE_COPY (LabeledWidget<R>) + + void commonInit (const char* labelstr) { + m_label = new QLabel (labelstr, this); + m_layout = new QHBoxLayout; + m_layout->addWidget (m_label); + m_layout->addWidget (m_widget); + setLayout (m_layout); + } + + R* m_widget; + QLabel* m_label; + QHBoxLayout* m_layout; +}; + +// ----------------------------------------------------------------------------- +// Other GUI-related stuff not directly part of ForgeWindow: +QPixmap getIcon (const char* sIconName); +std::vector<quickColorMetaEntry> parseQuickColorMeta (); +bool confirm (str title, str msg); +bool confirm (str msg); +void critical (str msg); +QAction* findAction (str name); +void makeColorSelector (QComboBox* box); + +// ----------------------------------------------------------------------------- +// Pointer to the instance of ForgeWindow. +extern ForgeWindow* g_win; + +#endif // GUI_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui_actions.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,419 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <qfiledialog.h> +#include <qmessagebox.h> +#include "gui.h" +#include "file.h" +#include "history.h" +#include "newPartDialog.h" +#include "configDialog.h" +#include "addObjectDialog.h" +#include "aboutDialog.h" +#include "misc.h" +#include "ldrawPathDialog.h" + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (newFile, "&New", "brick", "Create a new part model.", CTRL (N)) { + NewPartDialog::StaticDialog (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (open, "&Open", "file-open", "Load a part model from a file.", CTRL (O)) { + str zName; + zName += QFileDialog::getOpenFileName (g_win, "Open File", + "", "LDraw files (*.dat *.ldr)"); + + if (~zName) + openMainFile (zName); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void doSave (bool saveAs) { + str path = g_curfile->m_filename; + + if (~path == 0 || saveAs) { + path = QFileDialog::getSaveFileName (g_win, "Save As", + "", "LDraw files (*.dat *.ldr)"); + + if (~path == 0) { + // User didn't give a file name. This happens if the user cancelled + // saving in the save file dialog. Abort. + return; + } + } + + if (g_curfile->save (path)) { + g_curfile->m_filename = path; + g_win->setTitle (); + + logf ("Saved successfully to %s\n", path.chars ()); + } else { + setlocale (LC_ALL, "C"); + + // Tell the user the save failed, and give the option for saving as with it. + QMessageBox dlg (QMessageBox::Critical, "Save Failure", + fmt ("Failed to save to %s\nReason: %s", path.chars(), strerror (g_curfile->lastError)), + QMessageBox::Close, g_win); + + QPushButton* saveAsBtn = new QPushButton ("Save As"); + saveAsBtn->setIcon (getIcon ("file-save-as")); + dlg.addButton (saveAsBtn, QMessageBox::ActionRole); + dlg.setDefaultButton (QMessageBox::Close); + dlg.exec (); + + if (dlg.clickedButton () == saveAsBtn) + doSave (true); // yay recursion! + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (save, "&Save", "file-save", "Save the part model.", CTRL (S)) { + doSave (false); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (saveAs, "Save &As", "file-save-as", "Save the part model to a specific file.", CTRL_SHIFT (S)) { + doSave (true); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (settings, "Settin&gs", "settings", "Edit the settings of " APPNAME ".", (0)) { + ConfigDialog::staticDialog (); +} + +MAKE_ACTION (setLDrawPath, "Set LDraw Path", "settings", "Change the LDraw directory path.", (0)) { + LDrawPathDialog dlg (true); + dlg.exec (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (exit, "&Exit", "exit", "Close " APPNAME ".", CTRL (Q)) { + exit (0); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (newSubfile, "New Subfile", "add-subfile", "Creates a new subfile reference.", 0) { + AddObjectDialog::staticDialog (LDObject::Subfile, null); +} + +MAKE_ACTION (newLine, "New Line", "add-line", "Creates a new line.", 0) { + AddObjectDialog::staticDialog (LDObject::Line, null); +} + +MAKE_ACTION (newTriangle, "New Triangle", "add-triangle", "Creates a new triangle.", 0) { + AddObjectDialog::staticDialog (LDObject::Triangle, null); +} + +MAKE_ACTION (newQuad, "New Quadrilateral", "add-quad", "Creates a new quadrilateral.", 0) { + AddObjectDialog::staticDialog (LDObject::Quad, null); +} + +MAKE_ACTION (newCondLine, "New Conditional Line", "add-condline", "Creates a new conditional line.", 0) { + AddObjectDialog::staticDialog (LDObject::CondLine, null); +} + +MAKE_ACTION (newComment, "New Comment", "add-comment", "Creates a new comment.", 0) { + AddObjectDialog::staticDialog (LDObject::Comment, null); +} + +MAKE_ACTION (newBFC, "New BFC Statement", "add-bfc", "Creates a new BFC statement.", 0) { + AddObjectDialog::staticDialog (LDObject::BFC, null); +} + +MAKE_ACTION (newVertex, "New Vertex", "add-vertex", "Creates a new vertex.", 0) { + AddObjectDialog::staticDialog (LDObject::Vertex, null); +} + +MAKE_ACTION (newRadial, "New Radial", "add-radial", "Creates a new radial.", 0) { + AddObjectDialog::staticDialog (LDObject::Radial, null); +} + +MAKE_ACTION (editObject, "Edit Object", "edit-object", "Edits this object.", 0) { + if (g_win->sel ().size () != 1) + return; + + LDObject* obj = g_win->sel ()[0]; + AddObjectDialog::staticDialog (obj->getType (), obj); +} + +MAKE_ACTION (help, "Help", "help", "Shows the " APPNAME " help manual.", KEY (F1)) { + +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (about, "About " APPNAME, "ldforge", + "Shows information about " APPNAME ".", (0)) +{ + AboutDialog dlg; + dlg.exec (); +} + +MAKE_ACTION (aboutQt, "About Qt", "qt", "Shows information about Qt.", (0)) { + QMessageBox::aboutQt (g_win); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (selectAll, "Select All", "select-all", "Selects all objects.", CTRL (A)) { + g_win->sel ().clear (); + + for (LDObject* obj : g_curfile->m_objs) + g_win->sel ().push_back (obj); + + g_win->updateSelection (); +} + +// ============================================================================= +MAKE_ACTION (selectByColor, "Select by Color", "select-color", + "Select all objects by the given color.", CTRL_SHIFT (A)) +{ + short dColor = g_win->getSelectedColor (); + + if (dColor == -1) + return; // no consensus on color + + g_win->sel ().clear (); + for (LDObject* obj : g_curfile->m_objs) + if (obj->dColor == dColor) + g_win->sel ().push_back (obj); + + g_win->updateSelection (); +} + +// ============================================================================= +MAKE_ACTION (selectByType, "Select by Type", "select-type", + "Select all objects by the given type.", (0)) +{ + if (g_win->sel ().size () == 0) + return; + + LDObject::Type eType = g_win->uniformSelectedType (); + + if (eType == LDObject::Unidentified) + return; + + // If we're selecting subfile references, the reference filename must also + // be uniform. + str zRefName; + + if (eType == LDObject::Subfile) { + zRefName = static_cast<LDSubfile*> (g_win->sel ()[0])->zFileName; + + for (LDObject* pObj : g_win->sel ()) + if (static_cast<LDSubfile*> (pObj)->zFileName != zRefName) + return; + } + + g_win->sel ().clear (); + for (LDObject* obj : g_curfile->m_objs) { + if (obj->getType() != eType) + continue; + + if (eType == LDObject::Subfile && static_cast<LDSubfile*> (obj)->zFileName != zRefName) + continue; + + g_win->sel ().push_back (obj); + } + + g_win->updateSelection (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (gridCoarse, "Coarse Grid", "grid-coarse", "Set the grid to Coarse", CTRL (1)) { + grid = Grid::Coarse; + g_win->updateGridToolBar (); +} + +MAKE_ACTION (gridMedium, "Medium Grid", "grid-medium", "Set the grid to Medium", CTRL (2)) { + grid = Grid::Medium; + g_win->updateGridToolBar (); +} + +MAKE_ACTION (gridFine, "Fine Grid", "grid-fine", "Set the grid to Fine", CTRL (3)) { + grid = Grid::Fine; + g_win->updateGridToolBar (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (resetView, "Reset View", "reset-view", "Reset view angles, pan and zoom", CTRL (0)) { + g_win->R ()->resetAngles (); + g_win->R ()->update (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (insertFrom, "Insert from File", "insert-from", "Insert LDraw data from a file.", (0)) { + str fname = QFileDialog::getOpenFileName (); + ulong idx = g_win->getInsertionPoint (); + + if (!~fname) + return; + + FILE* fp = fopen (fname, "r"); + if (!fp) { + critical (fmt ("Couldn't open %s\n%s", fname.chars(), strerror (errno))); + return; + } + + std::vector<LDObject*> historyCopies; + std::vector<ulong> historyIndices; + std::vector<LDObject*> objs = loadFileContents (fp, null); + + g_win->sel ().clear (); + + for (LDObject* obj : objs) { + historyCopies.push_back (obj->clone ()); + historyIndices.push_back (idx); + g_curfile->insertObj (idx, obj); + g_win->sel ().push_back (obj); + + idx++; + } + + if (historyCopies.size() > 0) { + History::addEntry (new AddHistory (historyIndices, historyCopies)); + g_win->refresh (); + g_win->scrollToSelection (); + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (insertRaw, "Insert Raw", "insert-raw", "Type in LDraw code to insert.", (0)) { + ulong idx = g_win->getInsertionPoint (); + + QDialog* const dlg = new QDialog; + QVBoxLayout* const layout = new QVBoxLayout; + QTextEdit* const te_edit = new QTextEdit; + QDialogButtonBox* const bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + std::vector<LDObject*> historyCopies; + std::vector<ulong> historyIndices; + + layout->addWidget (te_edit); + layout->addWidget (bbx_buttons); + dlg->setLayout (layout); + dlg->setWindowTitle (APPNAME ": Insert Raw"); + dlg->connect (bbx_buttons, SIGNAL (accepted ()), dlg, SLOT (accept ())); + dlg->connect (bbx_buttons, SIGNAL (rejected ()), dlg, SLOT (reject ())); + + if (dlg->exec () == false) + return; + + g_win->sel ().clear (); + + for (str line : str (te_edit->toPlainText ()).split ("\n")) { + LDObject* obj = parseLine (line); + + g_curfile->insertObj (idx, obj); + historyIndices.push_back (idx); + historyCopies.push_back (obj->clone ()); + g_win->sel ().push_back (obj); + idx++; + } + + if (historyCopies.size () > 0) { + History::addEntry (new AddHistory (historyIndices, historyCopies)); + g_win->refresh (); + g_win->scrollToSelection (); + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (screencap, "Screencap Part", "screencap", "Save a picture of the model", (0)) { + setlocale (LC_ALL, "C"); + + ushort w, h; + uchar* imagedata = g_win->R ()->screencap (w, h); + + // GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well. + QImage img = QImage (imagedata, w, h, QImage::Format_ARGB32).rgbSwapped ().mirrored (); + + str root = basename (g_curfile->m_filename.chars ()); + if (root.substr (~root - 4, -1) == ".dat") + root -= 4; + + str defaultname = (~root > 0) ? fmt ("%s.png", root.chars ()) : ""; + str fname = QFileDialog::getSaveFileName (g_win, "Save Screencap", defaultname, + "PNG images (*.png);;JPG images (*.jpg);;BMP images (*.bmp);;All Files (*.*)"); + + if (~fname > 0 && !img.save (fname)) + critical (fmt ("Couldn't open %s for writing to save screencap: %s", fname.chars(), strerror (errno))); + + delete[] imagedata; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +extern_cfg (bool, gl_axes); +MAKE_ACTION (axes, "Draw Axes", "axes", "Toggles drawing of axes", (0)) { + gl_axes = !gl_axes; + ACTION (axes)->setChecked (gl_axes); + g_win->R ()->update (); +} + +// ============================================================================= +MAKE_ACTION (beginDraw, "Begin Drawing", "draw", "Begin drawing geometry", KEY (Insert)) { + g_win->R ()->beginPlaneDraw (); +} + +MAKE_ACTION (cancelDraw, "Cancel Drawing", "draw-cancel", "Cancel drawing geometry", KEY (Escape)) { + g_win->R ()->endPlaneDraw (false); +} + +MAKE_ACTION (doneDraw, "Done Drawing", "draw-done", "Done drawing geometry", KEY (Enter)) { + g_win->R ()->endPlaneDraw (true); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (visibility, "Toggle Visibility", "visibility", "Toggles visibility/hiding on objects.", (0)) { + for (LDObject* obj : g_win->sel ()) + obj->setHidden (!obj->hidden ()); + + g_win->refresh (); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui_editactions.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,687 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui.h" +#include "common.h" +#include "file.h" +#include "history.h" +#include "colorSelectDialog.h" +#include "historyDialog.h" +#include "setContentsDialog.h" +#include "misc.h" +#include "bbox.h" +#include "radiobox.h" +#include "extprogs.h" +#include <qspinbox.h> +#include <qcheckbox.h> + +vector<LDObject*> g_Clipboard; + +cfg (bool, edit_schemanticinline, false); + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +static bool copyToClipboard () { + vector<LDObject*> objs = g_win->sel (); + + if (objs.size() == 0) + return false; + + // Clear the clipboard first. + for (LDObject* obj : g_Clipboard) + delete obj; + + g_Clipboard.clear (); + + // Now, copy the contents into the clipboard. The objects should be + // separate objects so that modifying the existing ones does not affect + // the clipboard. Thus, we add clones of the objects to the clipboard, not + // the objects themselves. + for (ulong i = 0; i < objs.size(); ++i) + g_Clipboard.push_back (objs[i]->clone ()); + + return true; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (cut, "Cut", "cut", "Cut the current selection to clipboard.", CTRL (X)) { + vector<ulong> ulaIndices; + vector<LDObject*> copies; + + if (!copyToClipboard ()) + return; + + g_win->deleteSelection (&ulaIndices, &copies); + History::addEntry (new DelHistory (ulaIndices, copies, DelHistory::Cut)); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (copy, "Copy", "copy", "Copy the current selection to clipboard.", CTRL (C)) { + copyToClipboard (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (paste, "Paste", "paste", "Paste clipboard contents.", CTRL (V)) { + vector<ulong> historyIndices; + vector<LDObject*> historyCopies; + + ulong idx = g_win->getInsertionPoint (); + g_win->sel ().clear (); + + for (LDObject* obj : g_Clipboard) { + historyIndices.push_back (idx); + historyCopies.push_back (obj->clone ()); + + LDObject* copy = obj->clone (); + g_curfile->insertObj (idx, copy); + g_win->sel ().push_back (copy); + } + + History::addEntry (new AddHistory (historyIndices, historyCopies, AddHistory::Paste)); + g_win->refresh (); + g_win->scrollToSelection (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (del, "Delete", "delete", "Delete the selection", KEY (Delete)) { + vector<ulong> ulaIndices; + vector<LDObject*> copies; + + g_win->deleteSelection (&ulaIndices, &copies); + + if (copies.size ()) + History::addEntry (new DelHistory (ulaIndices, copies)); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +static void doInline (bool bDeep) { + vector<LDObject*> sel = g_win->sel (); + + // History stuff + vector<LDSubfile*> paRefs; + vector<ulong> ulaRefIndices, ulaBitIndices; + + for (LDObject* obj : sel) { + if (obj->getType() != LDObject::Subfile) + continue; + + ulaRefIndices.push_back (obj->getIndex (g_curfile)); + paRefs.push_back (static_cast<LDSubfile*> (obj)->clone ()); + } + + for (LDObject* obj : sel) { + // Get the index of the subfile so we know where to insert the + // inlined contents. + long idx = obj->getIndex (g_curfile); + if (idx == -1) + continue; + + vector<LDObject*> objs; + + if (obj->getType() == LDObject::Subfile) + objs = static_cast<LDSubfile*> (obj)->inlineContents (bDeep, true); + else if (obj->getType() == LDObject::Radial) + objs = static_cast<LDRadial*> (obj)->decompose (true); + else + continue; + + // Merge in the inlined objects + for (LDObject* inlineobj : objs) { + ulaBitIndices.push_back (idx); + + // This object is now inlined so it has no parent anymore. + inlineobj->parent = null; + + g_curfile->insertObj (idx++, inlineobj); + } + + // Delete the subfile now as it's been inlined. + g_curfile->forgetObject (obj); + delete obj; + } + + History::addEntry (new InlineHistory (ulaBitIndices, ulaRefIndices, paRefs, bDeep)); + g_win->refresh (); +} + +MAKE_ACTION (inlineContents, "Inline", "inline", "Inline selected subfiles.", CTRL (I)) { + doInline (false); +} + +MAKE_ACTION (deepInline, "Deep Inline", "inline-deep", "Recursively inline selected subfiles " + "down to polygons only.", CTRL_SHIFT (I)) +{ + doInline (true); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (splitQuads, "Split Quads", "quad-split", "Split quads into triangles.", (0)) { + vector<LDObject*> objs = g_win->sel (); + + vector<ulong> ulaIndices; + vector<LDQuad*> paCopies; + + // Store stuff first for history archival + for (LDObject* obj : objs) { + if (obj->getType() != LDObject::Quad) + continue; + + ulaIndices.push_back (obj->getIndex (g_curfile)); + paCopies.push_back (static_cast<LDQuad*> (obj)->clone ()); + } + + for (LDObject* obj : objs) { + if (obj->getType() != LDObject::Quad) + continue; + + // Find the index of this quad + long lIndex = obj->getIndex (g_curfile); + + if (lIndex == -1) + return; + + std::vector<LDTriangle*> triangles = static_cast<LDQuad*> (obj)->splitToTriangles (); + + // Replace the quad with the first triangle and add the second triangle + // after the first one. + g_curfile->m_objs[lIndex] = triangles[0]; + g_curfile->insertObj (lIndex + 1, triangles[1]); + + // Delete this quad now, it has been split. + delete obj; + } + + History::addEntry (new QuadSplitHistory (ulaIndices, paCopies)); + g_win->refresh (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (setContents, "Set Contents", "set-contents", "Set the raw code of this object.", KEY (F9)) { + if (g_win->sel ().size() != 1) + return; + + LDObject* obj = g_win->sel ()[0]; + SetContentsDialog::staticDialog (obj); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (setColor, "Set Color", "palette", "Set the color on given objects.", KEY (F10)) { + if (g_win->sel ().size() <= 0) + return; + + short dColor; + short dDefault = -1; + + std::vector<LDObject*> objs = g_win->sel (); + + // If all selected objects have the same color, said color is our default + // value to the color selection dialog. + dDefault = g_win->getSelectedColor (); + + // Show the dialog to the user now and ask for a color. + if (ColorSelectDialog::staticDialog (dColor, dDefault, g_win)) { + std::vector<ulong> ulaIndices; + std::vector<short> daColors; + + for (LDObject* obj : objs) { + if (obj->dColor != -1) { + ulaIndices.push_back (obj->getIndex (g_curfile)); + daColors.push_back (obj->dColor); + + obj->dColor = dColor; + } + } + + History::addEntry (new SetColorHistory (ulaIndices, daColors, dColor)); + g_win->refresh (); + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (makeBorders, "Make Borders", "make-borders", "Add borders around given polygons.", + CTRL_SHIFT (B)) +{ + vector<LDObject*> objs = g_win->sel (); + + vector<ulong> ulaIndices; + vector<LDObject*> paObjs; + + for (LDObject* obj : objs) { + if (obj->getType() != LDObject::Quad && obj->getType() != LDObject::Triangle) + continue; + + short dNumLines; + LDLine* lines[4]; + + if (obj->getType() == LDObject::Quad) { + dNumLines = 4; + + LDQuad* quad = static_cast<LDQuad*> (obj); + lines[0] = new LDLine (quad->vaCoords[0], quad->vaCoords[1]); + lines[1] = new LDLine (quad->vaCoords[1], quad->vaCoords[2]); + lines[2] = new LDLine (quad->vaCoords[2], quad->vaCoords[3]); + lines[3] = new LDLine (quad->vaCoords[3], quad->vaCoords[0]); + } else { + dNumLines = 3; + + LDTriangle* tri = static_cast<LDTriangle*> (obj); + lines[0] = new LDLine (tri->vaCoords[0], tri->vaCoords[1]); + lines[1] = new LDLine (tri->vaCoords[1], tri->vaCoords[2]); + lines[2] = new LDLine (tri->vaCoords[2], tri->vaCoords[0]); + } + + for (short i = 0; i < dNumLines; ++i) { + ulong idx = obj->getIndex (g_curfile) + i + 1; + + lines[i]->dColor = edgecolor; + g_curfile->insertObj (idx, lines[i]); + + ulaIndices.push_back (idx); + paObjs.push_back (lines[i]->clone ()); + } + } + + History::addEntry (new AddHistory (ulaIndices, paObjs)); + g_win->refresh (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (makeCornerVerts, "Make Corner Vertices", "corner-verts", + "Adds vertex objects to the corners of the given polygons", (0)) +{ + vector<ulong> ulaIndices; + vector<LDObject*> paObjs; + + for (LDObject* obj : g_win->sel ()) { + if (obj->vertices () < 2) + continue; + + ulong idx = obj->getIndex (g_curfile); + for (short i = 0; i < obj->vertices(); ++i) { + LDVertex* vert = new LDVertex; + vert->vPosition = obj->vaCoords[i]; + vert->dColor = obj->dColor; + + g_curfile->insertObj (++idx, vert); + ulaIndices.push_back (idx); + paObjs.push_back (vert->clone ()); + } + } + + if (ulaIndices.size() > 0) { + History::addEntry (new AddHistory (ulaIndices, paObjs)); + g_win->refresh (); + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +static void doMoveSelection (const bool bUp) { + vector<LDObject*> objs = g_win->sel (); + + // Get the indices of the objects for history archival + vector<ulong> ulaIndices; + for (LDObject* obj : objs) + ulaIndices.push_back (obj->getIndex (g_curfile)); + + LDObject::moveObjects (objs, bUp); + History::addEntry (new ListMoveHistory (ulaIndices, bUp)); + g_win->buildObjList (); +} + +MAKE_ACTION (moveUp, "Move Up", "arrow-up", "Move the current selection up.", SHIFT (Up)) { + doMoveSelection (true); +} + +MAKE_ACTION (moveDown, "Move Down", "arrow-down", "Move the current selection down.", SHIFT (Down)) { + doMoveSelection (false); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (undo, "Undo", "undo", "Undo a step.", CTRL (Z)) { + History::undo (); +} + +MAKE_ACTION (redo, "Redo", "redo", "Redo a step.", CTRL_SHIFT (Z)) { + History::redo (); +} + +MAKE_ACTION (showHistory, "Edit History", "history", "Show the history dialog.", (0)) { + HistoryDialog dlg; + dlg.exec (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void doMoveObjects (vertex vVector) { + vector<ulong> ulaIndices; + + // Apply the grid values + vVector[X] *= currentGrid ().confs[Grid::X]->value; + vVector[Y] *= currentGrid ().confs[Grid::Y]->value; + vVector[Z] *= currentGrid ().confs[Grid::Z]->value; + + for (LDObject* obj : g_win->sel ()) { + ulaIndices.push_back (obj->getIndex (g_curfile)); + obj->move (vVector); + } + + History::addEntry (new MoveHistory (ulaIndices, vVector)); + g_win->refresh (); +} + +MAKE_ACTION (moveXNeg, "Move -X", "move-x-neg", "Move selected objects negative on the X axis.", KEY (Left)) { + doMoveObjects ({-1, 0, 0}); +} + +MAKE_ACTION (moveYNeg, "Move -Y", "move-y-neg", "Move selected objects negative on the Y axis.", KEY (PageUp)) { + doMoveObjects ({0, -1, 0}); +} + +MAKE_ACTION (moveZNeg, "Move -Z", "move-z-neg", "Move selected objects negative on the Z axis.", KEY (Down)) { + doMoveObjects ({0, 0, -1}); +} + +MAKE_ACTION (moveXPos, "Move +X", "move-x-pos", "Move selected objects positive on the X axis.", KEY (Right)) { + doMoveObjects ({1, 0, 0}); +} + +MAKE_ACTION (moveYPos, "Move +Y", "move-y-pos", "Move selected objects positive on the Y axis.", KEY (PageDown)) { + doMoveObjects ({0, 1, 0}); +} + +MAKE_ACTION (moveZPos, "Move +Z", "move-z-pos", "Move selected objects positive on the Z axis.", KEY (Up)) { + doMoveObjects ({0, 0, 1}); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// invert - reverse winding +// +// NOTE: History management gets a little tricky here. For lines, cond-lines, +// triangles and quads, we edit the object, thus we record an EditHistory. For +// subfiles and radials we create or delete invertnext objects. Since we have +// multiple actions of different types, we store a history entry for each of +// them and wrap them into a ComboHistory, which allows storage of multiple +// simultaneous edits with different type. This is what we ultimately store into +// History. +// ============================================================================= +MAKE_ACTION (invert, "Invert", "invert", "Reverse the winding of given objects.", CTRL_SHIFT (W)) { + std::vector<LDObject*> paSelection = g_win->sel (); + std::vector<HistoryEntry*> paHistory; + + for (LDObject* obj : paSelection) { + // For the objects we end up editing, we store information into these + // variables and we store them into an EditHistory after the switch + // block. Subfile and radial management is stored into the history + // list immediately. + ulong ulHistoryIndex = obj->getIndex (g_curfile); + LDObject* pOldCopy, *pNewCopy; + bool bEdited = false; + + switch (obj->getType ()) { + case LDObject::Line: + case LDObject::CondLine: + { + // For lines, we swap the vertices. I don't think that a + // cond-line's control points need to be swapped, do they? + LDLine* pLine = static_cast<LDLine*> (obj); + vertex vTemp = pLine->vaCoords[0]; + + pOldCopy = pLine->clone (); + pLine->vaCoords[0] = pLine->vaCoords[1]; + pLine->vaCoords[1] = vTemp; + pNewCopy = pLine->clone (); + bEdited = true; + } + break; + + case LDObject::Triangle: + { + // Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1. + // Thus, we swap 1 and 2. + LDTriangle* pTri = static_cast<LDTriangle*> (obj); + vertex vTemp = pTri->vaCoords[1]; + + pOldCopy = pTri->clone (); + pTri->vaCoords[1] = pTri->vaCoords[2]; + pTri->vaCoords[2] = vTemp; + pNewCopy = pTri->clone (); + bEdited = true; + } + break; + + case LDObject::Quad: + { + // Quad: 0 -> 1 -> 2 -> 3 + // rev: 0 -> 3 -> 2 -> 1 + // Thus, we swap 1 and 3. + LDQuad* pQuad = static_cast<LDQuad*> (obj); + vertex vTemp = pQuad->vaCoords[1]; + + pOldCopy = pQuad->clone (); + pQuad->vaCoords[1] = pQuad->vaCoords[3]; + pQuad->vaCoords[3] = vTemp; + pNewCopy = pQuad->clone (); + bEdited = true; + } + break; + + case LDObject::Subfile: + case LDObject::Radial: + { + // Subfiles and radials are inverted when they're prefixed with + // a BFC INVERTNEXT statement. Thus we need to toggle this status. + // For flat primitives it's sufficient that the determinant is + // flipped but I don't have a method for checking flatness yet. + // Food for thought... + + bool inverted = false; + ulong idx = obj->getIndex (g_curfile); + + if (idx > 0) { + LDObject* prev = g_curfile->object (idx - 1); + LDBFC* bfc = dynamic_cast<LDBFC*> (prev); + + if (bfc && bfc->type == LDBFC::InvertNext) { + // Object is prefixed with an invertnext, thus remove it. + paHistory.push_back (new DelHistory ({idx - 1}, {bfc->clone ()})); + + inverted = true; + g_curfile->forgetObject (bfc); + delete bfc; + } + } + + if (!inverted) { + // Not inverted, thus prefix it with a new invertnext. + LDBFC* bfc = new LDBFC (LDBFC::InvertNext); + g_curfile->insertObj (idx, bfc); + + paHistory.push_back (new AddHistory ({idx}, {bfc->clone ()})); + } + } + break; + + default: + break; + } + + // If we edited this object, store the EditHistory based on collected + // information now. + if (bEdited == true) + paHistory.push_back (new EditHistory ({ulHistoryIndex}, {pOldCopy}, {pNewCopy})); + } + + if (paHistory.size () > 0) { + History::addEntry (new ComboHistory (paHistory)); + g_win->refresh (); + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +static void doRotate (const short l, const short m, const short n) { + std::vector<LDObject*> sel = g_win->sel (); + bbox box; + vertex origin; + std::vector<vertex*> queue; + const double angle = (pi * currentGrid ().confs[Grid::Angle]->value) / 360; + + // ref: http://en.wikipedia.org/wiki/Transformation_matrix#Rotation_2 + matrix<3> transform ({ + (l * l * (1 - cos (angle))) + cos (angle), + (m * l * (1 - cos (angle))) - (n * sin (angle)), + (n * l * (1 - cos (angle))) + (m * sin (angle)), + + (l * m * (1 - cos (angle))) + (n * sin (angle)), + (m * m * (1 - cos (angle))) + cos (angle), + (n * m * (1 - cos (angle))) - (l * sin (angle)), + + (l * n * (1 - cos (angle))) - (m * sin (angle)), + (m * n * (1 - cos (angle))) + (l * sin (angle)), + (n * n * (1 - cos (angle))) + cos (angle) + }); + + // Calculate center vertex + for (LDObject* obj : sel) { + if (obj->getType () == LDObject::Subfile) + box << static_cast<LDSubfile*> (obj)->vPosition; + else if (obj->getType () == LDObject::Radial) + box << static_cast<LDRadial*> (obj)->vPosition; + else + box << obj; + } + + origin = box.center (); + + // Apply the above matrix to everything + for (LDObject* obj : sel) { + if (obj->vertices ()) + for (short i = 0; i < obj->vertices (); ++i) + queue.push_back (&obj->vaCoords[i]); + else if (obj->getType () == LDObject::Subfile) { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + + queue.push_back (&ref->vPosition); + ref->mMatrix = ref->mMatrix * transform; + } else if (obj->getType () == LDObject::Radial) { + LDRadial* rad = static_cast<LDRadial*> (obj); + + queue.push_back (&rad->vPosition); + rad->mMatrix = rad->mMatrix * transform; + } else if (obj->getType () == LDObject::Vertex) + queue.push_back (&static_cast<LDVertex*> (obj)->vPosition); + } + + for (vertex* v : queue) { + v->move (-origin); + v->transform (transform, g_origin); + v->move (origin); + } + + g_win->refresh (); +} + +MAKE_ACTION (rotateXPos, "Rotate +X", "rotate-x-pos", "Rotate objects around X axis", CTRL (Right)) { + doRotate (1, 0, 0); +} + +MAKE_ACTION (rotateYPos, "Rotate +Y", "rotate-y-pos", "Rotate objects around Y axis", CTRL (PageDown)) { + doRotate (0, 1, 0); +} + +MAKE_ACTION (rotateZPos, "Rotate +Z", "rotate-z-pos", "Rotate objects around Z axis", CTRL (Up)) { + doRotate (0, 0, 1); +} + +MAKE_ACTION (rotateXNeg, "Rotate -X", "rotate-x-neg", "Rotate objects around X axis", CTRL (Left)) { + doRotate (-1, 0, 0); +} + +MAKE_ACTION (rotateYNeg, "Rotate -Y", "rotate-y-neg", "Rotate objects around Y axis", CTRL (PageUp)) { + doRotate (0, -1, 0); +} + +MAKE_ACTION (rotateZNeg, "Rotate -Z", "rotate-z-neg", "Rotate objects around Z axis", CTRL (Down)) { + doRotate (0, 0, -1); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (roundCoords, "Round Coordinates", "round-coords", "Round coordinates down to 3/4 decimals", (0)) { + setlocale (LC_ALL, "C"); + + for (LDObject* obj : g_win->sel ()) + for (short i = 0; i < obj->vertices (); ++i) + for (const Axis ax : g_Axes) + obj->vaCoords[i][ax] = atof (fmt ("%.3f", obj->vaCoords[i][ax])); + + g_win->refresh (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MAKE_ACTION (uncolorize, "Uncolorize", "uncolorize", "Reduce colors of everything selected to main and edge colors", (0)) { + vector<LDObject*> oldCopies, newCopies; + vector<ulong> indices; + + for (LDObject* obj : g_win->sel ()) { + if (obj->isColored () == false) + continue; + + indices.push_back (obj->getIndex (g_curfile)); + oldCopies.push_back (obj->clone ()); + + obj->dColor = (obj->getType () == LDObject::Line || obj->getType () == LDObject::CondLine) ? edgecolor : maincolor; + newCopies.push_back (obj->clone ()); + } + + if (indices.size () > 0) { + History::addEntry (new EditHistory (indices, oldCopies, newCopies)); + g_win->refresh (); + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/history.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,351 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "history.h" +#include "ldtypes.h" +#include "file.h" +#include "misc.h" +#include "gui.h" + +EXTERN_ACTION (undo) +EXTERN_ACTION (redo) + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +namespace History { + std::vector<HistoryEntry*> s_entries; + static long s_pos = -1; + + // ========================================================================= + void addEntry (HistoryEntry* entry) { + // If there's any entries ahead the current position, we need to + // remove them now + for (ulong i = s_pos + 1; i < s_entries.size(); ++i) { + delete s_entries[i]; + s_entries.erase (s_entries.begin() + i); + } + + s_entries.push_back (entry); + s_pos++; + + updateActions (); + } + + // ========================================================================= + void undo () { + if (s_pos == -1) + return; // nothing to undo + + s_entries[s_pos--]->undo (); + updateActions (); + } + + // ========================================================================= + void redo () { + if (s_pos == (long) s_entries.size () - 1) + return; // nothing to redo; + + s_entries[++s_pos]->redo (); + updateActions (); + } + + // ========================================================================= + void clear () { + for (HistoryEntry* entry : s_entries) + delete entry; + + s_entries.clear (); + s_pos = -1; + updateActions (); + } + + // ========================================================================= + void updateActions () { + ACTION (undo)->setEnabled (s_pos > -1); + ACTION (redo)->setEnabled (s_pos < (long) s_entries.size () - 1); + + // Update the window title as well + g_win->setTitle (); + } + + // ========================================================================= + long pos () { + return s_pos; + } + + // ========================================================================= + std::vector<HistoryEntry*>& entries () { + return s_entries; + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void DelHistory::undo () { + for (ulong i = 0; i < cache.size(); ++i) { + ulong idx = cache.size() - i - 1; + LDObject* obj = cache[idx]->clone (); + g_curfile->insertObj (indices[idx], obj); + } + + g_win->refresh (); +} + +// ============================================================================= +void DelHistory::redo () { + for (ulong i = 0; i < cache.size(); ++i) { + LDObject* obj = g_curfile->m_objs[indices[i]]; + + g_curfile->forgetObject (obj); + delete obj; + } + + g_win->refresh (); +} + +// ============================================================================= +DelHistory::~DelHistory () { + for (LDObject* obj : cache) + delete obj; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void SetColorHistory::undo () { + // Restore colors + for (ulong i = 0; i < ulaIndices.size (); ++i) + g_curfile->m_objs[ulaIndices[i]]->dColor = daColors[i]; + + g_win->refresh (); +} + +void SetColorHistory::redo () { + // Re-set post color + for (ulong i = 0; i < ulaIndices.size (); ++i) + g_curfile->m_objs[ulaIndices[i]]->dColor = dNewColor; + + g_win->refresh (); +} + +SetColorHistory::~SetColorHistory () {} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void EditHistory::undo () { + for (ulong idx : ulaIndices) + g_curfile->object (idx)->replace (paOldObjs[idx]->clone ()); + + g_win->refresh (); +} + +void EditHistory::redo () { + for (ulong idx : ulaIndices) + g_curfile->object (idx)->replace (paNewObjs[idx]->clone ()); + + g_win->refresh (); +} + +EditHistory::~EditHistory () { + for (ulong idx : ulaIndices) { + delete paOldObjs[idx]; + delete paNewObjs[idx]; + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +std::vector<LDObject*> ListMoveHistory::getObjects (short ofs) { + std::vector<LDObject*> objs; + + for (ulong idx : ulaIndices) + objs.push_back (g_curfile->m_objs[idx + ofs]); + + return objs; +} + +void ListMoveHistory::undo () { + std::vector<LDObject*> objs = getObjects (bUp ? -1 : 1); + LDObject::moveObjects (objs, !bUp); + g_win->buildObjList (); +} + +void ListMoveHistory::redo () { + std::vector<LDObject*> objs = getObjects (0); + LDObject::moveObjects (objs, bUp); + g_win->buildObjList (); +} + +ListMoveHistory::~ListMoveHistory() {} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +AddHistory::~AddHistory () { + for (LDObject* pObj : paObjs) + delete pObj; +} + +void AddHistory::undo () { + for (ulong i = 0; i < paObjs.size(); ++i) { + ulong idx = ulaIndices[ulaIndices.size() - i - 1]; + LDObject* obj = g_curfile->m_objs[idx]; + + g_curfile->forgetObject (obj); + delete obj; + } + + g_win->refresh (); +} + +void AddHistory::redo () { + for (ulong i = 0; i < paObjs.size(); ++i) { + ulong idx = ulaIndices[i]; + LDObject* obj = paObjs[i]->clone (); + + g_curfile->insertObj (idx, obj); + } + + g_win->refresh (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +QuadSplitHistory::~QuadSplitHistory () { + for (LDQuad* pQuad : paQuads) + delete pQuad; +} + +void QuadSplitHistory::undo () { + for (ulong i = 0; i < paQuads.size(); ++i) { + // The quad was replaced by the first triangle and a second one was + // added after it. Thus, we remove the second one here and replace + // the first with a copy of the quad. + ulong idx = ulaIndices[i]; + + LDTriangle* tri1 = static_cast<LDTriangle*> (g_curfile->m_objs[idx]), + *tri2 = static_cast<LDTriangle*> (g_curfile->m_objs[idx + 1]); + LDQuad* pCopy = paQuads[i]->clone (); + + tri1->replace (pCopy); + g_curfile->forgetObject (tri2); + delete tri2; + } + + g_win->refresh (); +} + +void QuadSplitHistory::redo () { + for (long i = paQuads.size() - 1; i >= 0; --i) { + ulong idx = ulaIndices[i]; + + LDQuad* pQuad = static_cast<LDQuad*> (g_curfile->m_objs[idx]); + std::vector<LDTriangle*> paTriangles = pQuad->splitToTriangles (); + + g_curfile->m_objs[idx] = paTriangles[0]; + g_curfile->insertObj (idx + 1, paTriangles[1]); + delete pQuad; + } + + g_win->refresh (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void InlineHistory::undo () { + for (long i = ulaBitIndices.size() - 1; i >= 0; --i) { + LDObject* obj = g_curfile->m_objs [ulaBitIndices[i]]; + g_curfile->forgetObject (obj); + delete obj; + } + + for (ulong i = 0; i < ulaRefIndices.size(); ++i) { + LDSubfile* obj = paRefs[i]->clone (); + g_curfile->insertObj (ulaRefIndices[i], obj); + } + + g_win->refresh (); +} + +void InlineHistory::redo () { + for (long i = ulaRefIndices.size() - 1; i >= 0; --i) { + ulong idx = ulaRefIndices[i]; + + assert (g_curfile->object (idx)->getType () == LDObject::Subfile); + LDSubfile* ref = static_cast<LDSubfile*> (g_curfile->object (idx)); + vector<LDObject*> objs = ref->inlineContents (bDeep, false); + + for (LDObject* obj : objs) + g_curfile->insertObj (idx++, obj); + + g_curfile->forgetObject (ref); + delete ref; + } + + g_win->refresh (); +} + +InlineHistory::~InlineHistory () { + for (LDSubfile* ref : paRefs) + delete ref; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +MoveHistory::~MoveHistory () {} + +void MoveHistory::undo () { + const vertex vInverse = -vVector; + + for (ulong i : ulaIndices) + g_curfile->object (i)->move (vInverse); + g_win->refresh (); +} + +void MoveHistory::redo () { + for (ulong i : ulaIndices) + g_curfile->object (i)->move (vVector); + g_win->refresh (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +ComboHistory::~ComboHistory () { + for (HistoryEntry* pEntry : paEntries) + delete pEntry; +} + +void ComboHistory::undo () { + for (long i = paEntries.size() - 1; i >= 0; --i) { + HistoryEntry* pEntry = paEntries[i]; + pEntry->undo (); + } +} + +void ComboHistory::redo () { + for (HistoryEntry* pEntry : paEntries) + pEntry->redo (); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/history.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,215 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef HISTORY_H +#define HISTORY_H + +#include "common.h" +#include "ldtypes.h" + +#define IMPLEMENT_HISTORY_TYPE(N) \ + virtual ~N##History (); \ + virtual void undo (); \ + virtual void redo (); \ + virtual HistoryType type () { return HISTORY_##N; } + +// ============================================================================= +enum HistoryType { + HISTORY_Del, + HISTORY_SetColor, + HISTORY_Edit, + HISTORY_ListMove, + HISTORY_Add, + HISTORY_QuadSplit, + HISTORY_Inline, + HISTORY_Move, + HISTORY_Combo, +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class HistoryEntry { +public: + virtual void undo () {} + virtual void redo () {} + virtual ~HistoryEntry () {} + virtual HistoryType type () { return (HistoryType)(0); } +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class DelHistory : public HistoryEntry { +public: + enum Type { + Cut, // were deleted with a cut operation + Other, // were deleted witout specific reason + }; + + IMPLEMENT_HISTORY_TYPE (Del) + + vector<ulong> indices; + vector<LDObject*> cache; + const Type eType; + + DelHistory (vector<ulong> indices, vector<LDObject*> cache, const Type eType = Other) : + indices (indices), cache (cache), eType (eType) {} +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class SetColorHistory : public HistoryEntry { +public: + IMPLEMENT_HISTORY_TYPE (SetColor) + + vector<ulong> ulaIndices; + vector<short> daColors; + short dNewColor; + + SetColorHistory (vector<ulong> ulaIndices, vector<short> daColors, short dNewColor) : + ulaIndices (ulaIndices), daColors (daColors), dNewColor (dNewColor) {} +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class EditHistory : public HistoryEntry { +public: + IMPLEMENT_HISTORY_TYPE (Edit) + + const std::vector<ulong> ulaIndices; + const std::vector<LDObject*> paOldObjs, paNewObjs; + + EditHistory (std::vector<ulong> ulaIndices, std::vector<LDObject*> paOldObjs, + std::vector<LDObject*> paNewObjs) : + ulaIndices (ulaIndices), paOldObjs (paOldObjs), paNewObjs (paNewObjs) {} +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class ListMoveHistory : public HistoryEntry { +public: + IMPLEMENT_HISTORY_TYPE (ListMove) + + std::vector<ulong> ulaIndices; + bool bUp; + + std::vector<LDObject*> getObjects (short ofs); + ListMoveHistory (vector<ulong> ulaIndices, const bool bUp) : + ulaIndices (ulaIndices), bUp (bUp) {} +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class AddHistory : public HistoryEntry { +public: + enum Type { + Other, // was "just added" + Paste, // was added through a paste operation + }; + + IMPLEMENT_HISTORY_TYPE (Add) + + std::vector<ulong> ulaIndices; + std::vector<LDObject*> paObjs; + const Type eType; + + AddHistory (std::vector<ulong> ulaIndices, std::vector<LDObject*> paObjs, + const Type eType = Other) : + ulaIndices (ulaIndices), paObjs (paObjs), eType (eType) {} +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class QuadSplitHistory : public HistoryEntry { +public: + IMPLEMENT_HISTORY_TYPE (QuadSplit) + + std::vector<ulong> ulaIndices; + std::vector<LDQuad*> paQuads; + + QuadSplitHistory (std::vector<ulong> ulaIndices, std::vector<LDQuad*> paQuads) : + ulaIndices (ulaIndices), paQuads (paQuads) {} +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class InlineHistory : public HistoryEntry { +public: + IMPLEMENT_HISTORY_TYPE (Inline) + + const std::vector<ulong> ulaBitIndices, ulaRefIndices; + const std::vector<LDSubfile*> paRefs; + const bool bDeep; + + InlineHistory (const std::vector<ulong> ulaBitIndices, const std::vector<ulong> ulaRefIndices, + const std::vector<LDSubfile*> paRefs, const bool bDeep) : + ulaBitIndices (ulaBitIndices), ulaRefIndices (ulaRefIndices), paRefs (paRefs), bDeep (bDeep) {} +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class MoveHistory : public HistoryEntry { +public: + IMPLEMENT_HISTORY_TYPE (Move) + + const std::vector<ulong> ulaIndices; + const vertex vVector; + + MoveHistory (const std::vector<ulong> ulaIndices, const vertex vVector) : + ulaIndices (ulaIndices), vVector (vVector) {} +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +class ComboHistory : public HistoryEntry { +public: + IMPLEMENT_HISTORY_TYPE (Combo) + + std::vector<HistoryEntry*> paEntries; + + ComboHistory (std::vector<HistoryEntry*> paEntries) : paEntries (paEntries) {} + + ComboHistory& operator<< (HistoryEntry* entry) { + paEntries.push_back (entry); + return *this; + } +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +namespace History { + void addEntry (HistoryEntry* entry); + void undo (); + void redo (); + void clear (); + void updateActions (); + long pos (); + std::vector<HistoryEntry*>& entries (); +}; + +#endif // HISTORY_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/historyDialog.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,283 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "historyDialog.h" +#include "history.h" +#include "colors.h" +#include <qboxlayout.h> +#include <qmessagebox.h> + +EXTERN_ACTION (undo); +EXTERN_ACTION (redo); + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +HistoryDialog::HistoryDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) { + historyList = new QListWidget; + undoButton = new QPushButton ("Undo"); + redoButton = new QPushButton ("Redo"); + clearButton = new QPushButton ("Clear"); + buttons = new QDialogButtonBox (QDialogButtonBox::Close); + + historyList->setAlternatingRowColors (true); + + undoButton->setIcon (getIcon ("undo")); + redoButton->setIcon (getIcon ("redo")); + + connect (undoButton, SIGNAL (clicked ()), this, SLOT (slot_undo ())); + connect (redoButton, SIGNAL (clicked ()), this, SLOT (slot_redo ())); + connect (clearButton, SIGNAL (clicked ()), this, SLOT (slot_clear ())); + connect (buttons, SIGNAL (rejected ()), this, SLOT (reject ())); + connect (historyList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_selChanged ())); + + QVBoxLayout* qButtonLayout = new QVBoxLayout; + qButtonLayout->setDirection (QBoxLayout::TopToBottom); + qButtonLayout->addWidget (undoButton); + qButtonLayout->addWidget (redoButton); + qButtonLayout->addWidget (clearButton); + qButtonLayout->addStretch (); + + QGridLayout* qLayout = new QGridLayout; + qLayout->setColumnStretch (0, 1); + qLayout->addWidget (historyList, 0, 0, 2, 1); + qLayout->addLayout (qButtonLayout, 0, 1); + qLayout->addWidget (buttons, 1, 1); + + setLayout (qLayout); + setWindowIcon (getIcon ("history")); + setWindowTitle (APPNAME " - Edit history"); + + populateList (); + updateButtons (); + updateSelection (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void HistoryDialog::populateList () { + historyList->clear (); + + QListWidgetItem* qItem = new QListWidgetItem; + qItem->setText ("[[ initial state ]]"); + qItem->setIcon (getIcon ("empty")); + historyList->addItem (qItem); + + for (HistoryEntry* entry : History::entries ()) { + str text; + QIcon entryIcon; + + switch (entry->type ()) { + case HISTORY_Add: + { + AddHistory* subentry = static_cast<AddHistory*> (entry); + ulong count = subentry->paObjs.size (); + str verb = "Added"; + + switch (subentry->eType) { + case AddHistory::Paste: + verb = "Pasted"; + entryIcon = getIcon ("paste"); + break; + + default: + { + // Determine a common type for these objects. If all objects are of the same + // type, we display its addition icon. Otherwise, we draw a subfile addition + // one as a default. + LDObject::Type eCommonType = LDObject::Unidentified; + for (LDObject* obj : subentry->paObjs) { + if (eCommonType == LDObject::Unidentified or obj->getType() == eCommonType) + eCommonType = obj->getType (); + else { + eCommonType = LDObject::Unidentified; + break; + } + } + + // Set the icon based on the common type decided above. + if (eCommonType == LDObject::Unidentified) + entryIcon = getIcon ("add-subfile"); + else + entryIcon = getIcon (fmt ("add-%s", g_saObjTypeIcons[eCommonType])); + } + break; + } + + text.format ("%s %lu objects\n%s", verb.chars(), count, + LDObject::objectListContents (subentry->paObjs).chars()); + } + break; + + case HISTORY_QuadSplit: + { + QuadSplitHistory* subentry = static_cast<QuadSplitHistory*> (entry); + ulong ulCount = subentry->paQuads.size (); + text.format ("Split %lu quad%s to triangles", ulCount, PLURAL (ulCount)); + + entryIcon = getIcon ("quad-split"); + } + break; + + case HISTORY_Del: + { + DelHistory* subentry = static_cast<DelHistory*> (entry); + ulong count = subentry->cache.size (); + str verb = "Deleted"; + entryIcon = getIcon ("delete"); + + switch (subentry->eType) { + case DelHistory::Cut: + entryIcon = getIcon ("cut"); + verb = "Cut"; + break; + + default: + break; + } + + text.format ("%s %lu objects:\n%s", verb.chars(), count, + LDObject::objectListContents (subentry->cache).chars ()); + } + break; + + case HISTORY_SetColor: + { + SetColorHistory* subentry = static_cast<SetColorHistory*> (entry); + ulong count = subentry->ulaIndices.size (); + text.format ("Set color of %lu objects to %d (%s)", count, + subentry->dNewColor, getColor (subentry->dNewColor)->zName.chars()); + + entryIcon = getIcon ("palette"); + } + break; + + case HISTORY_ListMove: + { + ListMoveHistory* subentry = static_cast<ListMoveHistory*> (entry); + ulong ulCount = subentry->ulaIndices.size (); + + text.format ("Moved %lu objects %s", ulCount, + subentry->bUp ? "up" : "down"); + entryIcon = getIcon (subentry->bUp ? "arrow-up" : "arrow-down"); + } + break; + + case HISTORY_Edit: + { + EditHistory* subentry = static_cast<EditHistory*> (entry); + + text.format ("Edited %u objects\n%s", + subentry->paNewObjs.size(), + LDObject::objectListContents (subentry->paOldObjs).chars ()); + entryIcon = getIcon ("set-contents"); + } + break; + + case HISTORY_Inline: + { + InlineHistory* subentry = static_cast<InlineHistory*> (entry); + + text.format ("%s %lu subfiles:\n%lu resultants", + (subentry->bDeep) ? "Deep-inlined" : "Inlined", + (ulong) subentry->paRefs.size(), + (ulong) subentry->ulaBitIndices.size()); + + entryIcon = getIcon (subentry->bDeep ? "inline-deep" : "inline"); + } + break; + + default: + text = "???"; + break; + } + + QListWidgetItem* item = new QListWidgetItem; + item->setText (text); + item->setIcon (entryIcon); + historyList->addItem (item); + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void HistoryDialog::slot_undo () { + History::undo (); + updateButtons (); + updateSelection (); +} + +// ============================================================================= +void HistoryDialog::slot_redo () { + History::redo (); + updateButtons (); + updateSelection (); +} + +void HistoryDialog::updateSelection () { + historyList->setCurrentItem (historyList->item (History::pos () + 1)); +} + +// ============================================================================= +void HistoryDialog::slot_clear () { + if (!confirm ("Are you sure you want to clear the edit history?\nThis cannot be undone.")) + return; // Canceled + + History::clear (); + populateList (); + updateButtons (); +} + +// ============================================================================= +void HistoryDialog::updateButtons () { + undoButton->setEnabled (ACTION (undo)->isEnabled ()); + redoButton->setEnabled (ACTION (redo)->isEnabled ()); +} + +// ============================================================================= +void HistoryDialog::slot_selChanged () { + if (historyList->selectedItems ().size () != 1) + return; + + QListWidgetItem* qItem = historyList->selectedItems ()[0]; + + // Find the index of the edit + long lIdx; + for (lIdx = 0; lIdx < historyList->count (); ++lIdx) + if (historyList->item (lIdx) == qItem) + break; + + // qHistoryList is 0-based, History is -1-based, thus decrement + // the index now + lIdx--; + + if (lIdx == History::pos ()) + return; + + // Seek to the selected edit by repeadetly undoing or redoing. + while (History::pos () != lIdx) { + if (History::pos () > lIdx) + History::undo (); + else + History::redo (); + } + + updateButtons (); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/historyDialog.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,50 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef HISTORYDIALOG_H +#define HISTORYDIALOG_H + +#include <qdialog.h> +#include <qdialogbuttonbox.h> +#include <qlistwidget.h> +#include <qpushbutton.h> +#include "gui.h" + +class HistoryDialog : public QDialog { + Q_OBJECT + +public: + explicit HistoryDialog (QWidget* parent = null, Qt::WindowFlags f = 0); + void populateList (); + + QListWidget* historyList; + QPushButton* undoButton, *redoButton, *clearButton; + QDialogButtonBox* buttons; + +private: + void updateButtons (); + void updateSelection (); + +private slots: + void slot_undo (); + void slot_redo (); + void slot_clear (); + void slot_selChanged (); +}; + +#endif // HISTORYDIALOG_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldrawPathDialog.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,129 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <qlineedit.h> +#include <qpushbutton.h> +#include <qdialogbuttonbox.h> +#include <QFileDialog> +#include "ldrawPathDialog.h" +#include "gui.h" +#include "file.h" + +extern_cfg (str, io_ldpath); + +// ======================================================================================================================================== +LDrawPathDialog::LDrawPathDialog (const bool validDefault, QWidget* parent, Qt::WindowFlags f) + : QDialog (parent, f), m_validDefault (validDefault) +{ + QLabel* lb_description; + lb_resolution = new QLabel ("---"); + + if (validDefault == false) + lb_description = new QLabel ("Please input your LDraw directory"); + + QLabel* lb_path = new QLabel ("LDraw path:"); + le_path = new QLineEdit; + btn_findPath = new QPushButton; + btn_findPath->setIcon (getIcon ("folder")); + + btn_tryConfigure = new QPushButton ("Configure"); + btn_tryConfigure->setIcon (getIcon ("settings")); + + btn_cancel = new QPushButton; + + if (validDefault == false) { + btn_cancel->setText ("Exit"); + btn_cancel->setIcon (getIcon ("exit")); + } else { + btn_cancel->setText ("Cancel"); + btn_cancel->setIcon (getIcon ("cancel")); + } + + dbb_buttons = new QDialogButtonBox (QDialogButtonBox::Ok); + dbb_buttons->addButton (btn_tryConfigure, QDialogButtonBox::ActionRole); + dbb_buttons->addButton (btn_cancel, QDialogButtonBox::RejectRole); + okButton ()->setEnabled (false); + + QHBoxLayout* inputLayout = new QHBoxLayout; + inputLayout->addWidget (lb_path); + inputLayout->addWidget (le_path); + inputLayout->addWidget (btn_findPath); + + QVBoxLayout* mainLayout = new QVBoxLayout; + + if (validDefault == false) + mainLayout->addWidget (lb_description); + + mainLayout->addLayout (inputLayout); + mainLayout->addWidget (lb_resolution); + mainLayout->addWidget (dbb_buttons); + setLayout (mainLayout); + + connect (le_path, SIGNAL (textEdited ()), this, SLOT (slot_tryConfigure ())); + connect (btn_findPath, SIGNAL (clicked ()), this, SLOT (slot_findPath ())); + connect (btn_tryConfigure, SIGNAL (clicked ()), this, SLOT (slot_tryConfigure ())); + connect (dbb_buttons, SIGNAL (accepted ()), this, SLOT (accept ())); + connect (dbb_buttons, SIGNAL (rejected ()), this, (validDefault) ? SLOT (reject ()) : SLOT (slot_exit ())); + + setPath (io_ldpath); + if (validDefault) + slot_tryConfigure (); +} + +// ======================================================================================================================================== +QPushButton* LDrawPathDialog::okButton () { + return dbb_buttons->button (QDialogButtonBox::Ok); +} + +// ======================================================================================================================================== +void LDrawPathDialog::setPath (str path) { + le_path->setText (path); +} + +// ======================================================================================================================================== +str LDrawPathDialog::path () const { + return le_path->text (); +} + +// ======================================================================================================================================== +void LDrawPathDialog::slot_findPath () { + str newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path"); + + if (~newpath > 0 && newpath != path ()) { + setPath (newpath); + slot_tryConfigure (); + } +} + + +// ======================================================================================================================================== +void LDrawPathDialog::slot_exit () { + exit (1); +} + +// ======================================================================================================================================== +void LDrawPathDialog::slot_tryConfigure () { + if (LDPaths::tryConfigure (path ()) == false) { + lb_resolution->setText (fmt ("<span style=\"color:red; font-weight: bold;\">%s</span>", LDPaths::getError().chars ())); + okButton ()->setEnabled (false); + return; + } + + lb_resolution->setText ("<span style=\"color: #7A0; font-weight: bold;\">OK!</span>"); + okButton ()->setEnabled (true); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldrawPathDialog.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,57 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LDRAWPATHDIALOG_H +#define LDRAWPATHDIALOG_H + +#include <qdialog.h> +#include "common.h" + +class QLabel; +class QLineEdit; +class QDialogButtonBox; + +class LDrawPathDialog : public QDialog { + Q_OBJECT + +public: + explicit LDrawPathDialog (const bool validDefault, QWidget* parent = null, Qt::WindowFlags f = 0); + str path () const; + void setPath (str path); + void (*callback ()) () const { return m_callback; } + void setCallback (void (*callback) ()) { m_callback = callback; } + +private: + Q_DISABLE_COPY (LDrawPathDialog) + + QLabel* lb_resolution; + QLineEdit* le_path; + QPushButton* btn_findPath, *btn_tryConfigure, *btn_cancel; + QDialogButtonBox* dbb_buttons; + void (*m_callback) (); + const bool m_validDefault; + + QPushButton* okButton (); + +private slots: + void slot_findPath (); + void slot_tryConfigure (); + void slot_exit (); +}; + +#endif // LDRAWPATHDIALOG_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldtypes.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,675 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "common.h" +#include "ldtypes.h" +#include "file.h" +#include "misc.h" +#include "gui.h" + +char const* g_saObjTypeNames[] = { + "subfile", + "radial", + "quadrilateral", + "triangle", + "line", + "condline", + "vertex", + "bfc", + "comment", + "unknown", + "empty", + "unidentified", +}; + +// Should probably get rid of this array sometime +char const* g_saObjTypeIcons[] = { + "subfile", + "radial", + "quad", + "triangle", + "line", + "condline", + "vertex", + "bfc", + "comment", + "error", + "empty", + "error", +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +// LDObject constructors +LDObject::LDObject () { + qObjListEntry = null; + parent = null; + m_hidden = false; +} + +LDGibberish::LDGibberish (str _zContent, str _zReason) { + zContents = _zContent; + zReason = _zReason; +} + +// ============================================================================= +str LDComment::getContents () { + return fmt ("0 %s", text.chars ()); +} + +str LDSubfile::getContents () { + str val = fmt ("1 %d %s ", dColor, vPosition.stringRep (false).chars ()); + val += mMatrix.stringRep (); + val += ' '; + val += zFileName; + return val; +} + +str LDLine::getContents () { + str val = fmt ("2 %d", dColor); + + for (ushort i = 0; i < 2; ++i) + val.appendformat (" %s", vaCoords[i].stringRep (false).chars ()); + + return val; +} + +str LDTriangle::getContents () { + str val = fmt ("3 %d", dColor); + + for (ushort i = 0; i < 3; ++i) + val.appendformat (" %s", vaCoords[i].stringRep (false).chars ()); + + return val; +} + +str LDQuad::getContents () { + str val = fmt ("4 %d", dColor); + + for (ushort i = 0; i < 4; ++i) + val.appendformat (" %s", vaCoords[i].stringRep (false).chars ()); + + return val; +} + +str LDCondLine::getContents () { + str val = fmt ("5 %d", dColor); + + // Add the coordinates + for (ushort i = 0; i < 4; ++i) + val.appendformat (" %s", vaCoords[i].stringRep (false).chars ()); + + return val; +} + +str LDGibberish::getContents () { + return zContents; +} + +str LDVertex::getContents () { + return fmt ("0 !LDFORGE VERTEX %d %s", dColor, vPosition.stringRep (false).chars()); +} + +str LDEmpty::getContents () { + return str (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +const char* LDBFC::statements[] = { + "CERTIFY CCW", + "CCW", + "CERTIFY CW", + "CW", + "NOCERTIFY", + "INVERTNEXT", +}; + +str LDBFC::getContents () { + return fmt ("0 BFC %s", LDBFC::statements[type]); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +vector<LDTriangle*> LDQuad::splitToTriangles () { + // Create the two triangles based on this quadrilateral: + // 0---3 0---3 3 + // | | | / /| + // | | = | / / | + // | | |/ / | + // 1---2 1 1---2 + LDTriangle* tri1 = new LDTriangle; + tri1->vaCoords[0] = vaCoords[0]; + tri1->vaCoords[1] = vaCoords[1]; + tri1->vaCoords[2] = vaCoords[3]; + + LDTriangle* tri2 = new LDTriangle; + tri2->vaCoords[0] = vaCoords[1]; + tri2->vaCoords[1] = vaCoords[2]; + tri2->vaCoords[2] = vaCoords[3]; + + // The triangles also inherit the quad's color + tri1->dColor = tri2->dColor = dColor; + + vector<LDTriangle*> triangles; + triangles.push_back (tri1); + triangles.push_back (tri2); + return triangles; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void LDObject::replace (LDObject* replacement) { + // Replace all instances of the old object with the new object + for (LDObject*& obj : g_curfile->m_objs) + if (obj == this) + obj = replacement; + + // Remove the old object + delete this; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void LDObject::swap (LDObject* other) { + for (LDObject*& obj : g_curfile->m_objs) { + if (obj == this) + obj = other; + else if (obj == other) + obj = this; + } +} + +LDLine::LDLine (vertex v1, vertex v2) { + vaCoords[0] = v1; + vaCoords[1] = v2; +} + +LDObject::~LDObject () { + // Remove this object from the selection array if it is there. + for (ulong i = 0; i < g_win->sel ().size(); ++i) + if (g_win->sel ()[i] == this) + g_win->sel ().erase (g_win->sel ().begin() + i); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +static void transformObject (LDObject* obj, matrix<3> transform, vertex pos, short parentcolor) { + switch (obj->getType()) { + case LDObject::Line: + case LDObject::CondLine: + case LDObject::Triangle: + case LDObject::Quad: + for (short i = 0; i < obj->vertices (); ++i) + obj->vaCoords[i].transform (transform, pos); + break; + + case LDObject::Subfile: + { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + + matrix<3> newMatrix = transform * ref->mMatrix; + ref->vPosition.transform (transform, pos); + ref->mMatrix = newMatrix; + } + break; + + default: + break; + } + + if (obj->dColor == maincolor) + obj->dColor = parentcolor; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +vector<LDObject*> LDSubfile::inlineContents (bool bDeepInline, bool bCache) { + vector<LDObject*> objs, cache; + + // If we have this cached, just clone that + if (bDeepInline && pFile->m_objCache.size ()) { + for (LDObject* obj : pFile->m_objCache) + objs.push_back (obj->clone ()); + } else { + if (!bDeepInline) + bCache = false; + + for (LDObject* obj : pFile->m_objs) { + // Skip those without schemantic meaning + switch (obj->getType ()) { + case LDObject::Comment: + case LDObject::Empty: + case LDObject::Gibberish: + case LDObject::Unidentified: + case LDObject::Vertex: + continue; + + case LDObject::BFC: + // Filter non-INVERTNEXT statements + if (static_cast<LDBFC*> (obj)->type != LDBFC::InvertNext) + continue; + break; + + default: + break; + } + + // Got another sub-file reference, inline it if we're deep-inlining. If not, + // just add it into the objects normally. Also, we only cache immediate + // subfiles and this is not one. Yay, recursion! + if (bDeepInline && obj->getType() == LDObject::Subfile) { + LDSubfile* ref = static_cast<LDSubfile*> (obj); + + vector<LDObject*> otherobjs = ref->inlineContents (true, false); + + for (LDObject* otherobj : otherobjs) { + // Cache this object, if desired + if (bCache) + cache.push_back (otherobj->clone ()); + + objs.push_back (otherobj); + } + } else { + if (bCache) + cache.push_back (obj->clone ()); + + objs.push_back (obj->clone ()); + } + } + + if (bCache) + pFile->m_objCache = cache; + } + + // Transform the objects + for (LDObject* obj : objs) { + // Set the parent now so we know what inlined this. + obj->parent = this; + + transformObject (obj, mMatrix, vPosition, dColor); + } + + return objs; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +long LDObject::getIndex (OpenFile* pFile) { + for (ulong i = 0; i < pFile->m_objs.size(); ++i) + if (pFile->m_objs[i] == this) + return i; + + return -1; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void LDObject::moveObjects (std::vector<LDObject*> objs, const bool bUp) { + // If we move down, we need to iterate the array in reverse order. + const long start = bUp ? 0 : (objs.size() - 1); + const long end = bUp ? objs.size() : -1; + const long incr = bUp ? 1 : -1; + + for (long i = start; i != end; i += incr) { + LDObject* obj = objs[i]; + + const long lIndex = obj->getIndex (g_curfile), + lTarget = lIndex + (bUp ? -1 : 1); + + if ((bUp == true and lIndex == 0) or + (bUp == false and lIndex == (long)(g_curfile->m_objs.size() - 1))) + { + // One of the objects hit the extrema. If this happens, this should be the first + // object to be iterated on. Thus, nothing has changed yet and it's safe to just + // abort the entire operation. + assert (i == start); + return; + } + + obj->swap (g_curfile->m_objs[lTarget]); + } +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +str LDObject::objectListContents (const std::vector<LDObject*>& objs) { + bool firstDetails = true; + str text = ""; + + if (objs.size() == 0) + return "nothing"; // :) + + for (long i = 0; i < LDObject::NumTypes; ++i) { + LDObject::Type objType = (LDObject::Type) i; + ulong objCount = 0; + + for (LDObject* obj : objs) + if (obj->getType() == objType) + objCount++; + + if (objCount == 0) + continue; + + if (!firstDetails) + text += ", "; + + str noun = fmt ("%s%s", g_saObjTypeNames[objType], PLURAL (objCount)); + + // Plural of "vertex" is "vertices". Stupid English. + if (objType == LDObject::Vertex && objCount != 1) + noun = "vertices"; + + text.appendformat ("%lu %s", objCount, noun.chars ()); + firstDetails = false; + } + + return text; +} + +// ============================================================================= +LDObject* LDObject::topLevelParent () { + if (!parent) + return null; + + LDObject* it = this; + + while (it->parent) + it = it->parent; + + return it; +} + + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void LDObject::move (vertex vVector) { vVector = vVector; /* to shut up GCC */ } +void LDEmpty::move (vertex vVector) { vVector = vVector; } +void LDBFC::move (vertex vVector) { vVector = vVector; } +void LDComment::move (vertex vVector) { vVector = vVector; } +void LDGibberish::move (vertex vVector) { vVector = vVector; } + +void LDVertex::move (vertex vVector) { + vPosition += vVector; +} + +void LDSubfile::move (vertex vVector) { + vPosition += vVector; +} + +void LDRadial::move (vertex vVector) { + vPosition += vVector; +} + +void LDLine::move (vertex vVector) { + for (short i = 0; i < 2; ++i) + vaCoords[i] += vVector; +} + +void LDTriangle::move (vertex vVector) { + for (short i = 0; i < 3; ++i) + vaCoords[i] += vVector; +} + +void LDQuad::move (vertex vVector) { + for (short i = 0; i < 4; ++i) + vaCoords[i] += vVector; +} + +void LDCondLine::move (vertex vVector) { + for (short i = 0; i < 4; ++i) + vaCoords[i] += vVector; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +static char const* g_saRadialTypeNames[] = { + "Circle", + "Cylinder", + "Disc", + "Disc Negative", + "Ring", + "Cone", + null +}; + +char const* LDRadial::radialTypeName () { + return g_saRadialTypeNames[eRadialType]; +} + +char const* LDRadial::radialTypeName (const LDRadial::Type eType) { + return g_saRadialTypeNames[eType]; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +std::vector<LDObject*> LDRadial::decompose (bool bTransform) { + std::vector<LDObject*> paObjects; + + for (short i = 0; i < dSegments; ++i) { + double x0 = cos ((i * 2 * pi) / dDivisions), + x1 = cos (((i + 1) * 2 * pi) / dDivisions), + z0 = sin ((i * 2 * pi) / dDivisions), + z1 = sin (((i + 1) * 2 * pi) / dDivisions); + + switch (eRadialType) { + case LDRadial::Circle: + { + vertex v0 (x0, 0.0f, z0), + v1 (x1, 0.0f, z1); + + if (bTransform) { + v0.transform (mMatrix, vPosition); + v1.transform (mMatrix, vPosition); + } + + LDLine* pLine = new LDLine; + pLine->vaCoords[0] = v0; + pLine->vaCoords[1] = v1; + pLine->dColor = edgecolor; + pLine->parent = this; + + paObjects.push_back (pLine); + } + break; + + case LDRadial::Cylinder: + case LDRadial::Ring: + case LDRadial::Cone: + { + double x2, x3, z2, z3; + double y0, y1, y2, y3; + + if (eRadialType == LDRadial::Cylinder) { + x2 = x1; + x3 = x0; + z2 = z1; + z3 = z0; + + y0 = y1 = 0.0f; + y2 = y3 = 1.0f; + } else { + x2 = x1 * (dRingNum + 1); + x3 = x0 * (dRingNum + 1); + z2 = z1 * (dRingNum + 1); + z3 = z0 * (dRingNum + 1); + + x0 *= dRingNum; + x1 *= dRingNum; + z0 *= dRingNum; + z1 *= dRingNum; + + if (eRadialType == LDRadial::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); + + if (bTransform) { + v0.transform (mMatrix, vPosition); + v1.transform (mMatrix, vPosition); + v2.transform (mMatrix, vPosition); + v3.transform (mMatrix, vPosition); + } + + LDQuad* pQuad = new LDQuad; + pQuad->vaCoords[0] = v0; + pQuad->vaCoords[1] = v1; + pQuad->vaCoords[2] = v2; + pQuad->vaCoords[3] = v3; + pQuad->dColor = dColor; + pQuad->parent = this; + + paObjects.push_back (pQuad); + } + break; + + case LDRadial::Disc: + case LDRadial::DiscNeg: + { + double x2, z2; + + if (eRadialType == LDRadial::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); + + if (bTransform) { + v0.transform (mMatrix, vPosition); + v1.transform (mMatrix, vPosition); + v2.transform (mMatrix, vPosition); + } + + LDTriangle* pSeg = new LDTriangle; + pSeg->vaCoords[0] = v0; + pSeg->vaCoords[1] = v1; + pSeg->vaCoords[2] = v2; + pSeg->dColor = dColor; + pSeg->parent = this; + + paObjects.push_back (pSeg); + } + break; + + default: + break; + } + } + + return paObjects; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +str LDRadial::getContents () { + return fmt ("0 !LDFORGE RADIAL %s %d %d %d %d %s %s", + str (radialTypeName()).toupper ().strip (' ').chars (), + dColor, dSegments, dDivisions, dRingNum, + vPosition.stringRep (false).chars(), mMatrix.stringRep().chars()); +} + +char const* g_saRadialNameRoots[] = { + "edge", + "cyli", + "disc", + "ndis", + "ring", + "cone", + null +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +str LDRadial::makeFileName () { + short numer = dSegments, + denom = dDivisions; + + // Simplify the fractional part, but the denominator must be at least 4. + simplify (numer, denom); + + if (denom < 4) { + const short factor = (4 / denom); + + numer *= factor; + denom *= factor; + } + + // Compose some general information: prefix, fraction, root, ring number + str prefix = (dDivisions == 16) ? "" : fmt ("%d/", dDivisions); + str frac = fmt ("%d-%d", numer, denom); + str root = g_saRadialNameRoots[eRadialType]; + str ringNum = (eRadialType == Ring || eRadialType == Cone) ? fmt ("%d", dRingNum) : ""; + + // Truncate the root if necessary (7-16rin4.dat for instance). + // However, always keep the root at least 2 characters. + short extra = (~frac + ~ringNum + ~root) - 8; + root -= min<short> (max<short> (extra, 0), 2); + + // Stick them all together and return the result. + return fmt ("%s%s%s%s", prefix.chars(), frac.chars (), root.chars (), ringNum.chars ()); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +#define CHECK_FOR_OBJ(N) \ + if (type == LDObject::N) \ + return new LD##N; +LDObject* LDObject::getDefault (const LDObject::Type type) { + CHECK_FOR_OBJ (Comment) + CHECK_FOR_OBJ (BFC) + CHECK_FOR_OBJ (Line) + CHECK_FOR_OBJ (CondLine) + CHECK_FOR_OBJ (Radial) + CHECK_FOR_OBJ (Subfile) + CHECK_FOR_OBJ (Triangle) + CHECK_FOR_OBJ (Quad) + CHECK_FOR_OBJ (Empty) + CHECK_FOR_OBJ (BFC) + CHECK_FOR_OBJ (Gibberish) + CHECK_FOR_OBJ (Vertex) + return null; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldtypes.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,387 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LDTYPES_H +#define LDTYPES_H + +#include "common.h" +#include "types.h" + +#define IMPLEMENT_LDTYPE(T, NUMVERTS) \ + LD##T () {} \ + virtual ~LD##T () {} \ + virtual LDObject::Type getType () const { \ + return LDObject::T; \ + } \ + virtual str getContents (); \ + virtual LD##T* clone () { \ + return new LD##T (*this); \ + } \ + virtual void move (vertex vVector); \ + virtual short vertices () const { return NUMVERTS; } \ + +#define LDOBJ_SETCOLORED(V) virtual bool isColored () const { return V; } +#define LDOBJ_COLORED LDOBJ_SETCOLORED (true) +#define LDOBJ_UNCOLORED LDOBJ_SETCOLORED (false) + +#define LDOBJ_CUSTOM_SCHEMANTIC virtual bool isSchemantic () const +#define LDOBJ_SCHEMANTIC LDOBJ_CUSTOM_SCHEMANTIC { return true; } +#define LDOBJ_NON_SCHEMANTIC LDOBJ_CUSTOM_SCHEMANTIC { return false; } + +class QListWidgetItem; +class LDSubfile; + +// ============================================================================= +// LDObject +// +// Base class object for all LD* types. Each LDObject represents a single line +// in the LDraw code file. The virtual method getType returns an enumerator +// which is a token of the object's type. The object can be casted into +// sub-classes based on this enumerator. +// ============================================================================= +class LDObject { +public: + // Object type codes. Codes are sorted in order of significance. + enum Type { + Subfile, // Object represents a sub-file reference + Radial, // Object represents a generic radial + Quad, // Object represents a quadrilateral + Triangle, // Object represents a triangle + Line, // Object represents a line + CondLine, // Object represents a conditional line + Vertex, // Object is a vertex, LDForge extension object + BFC, // Object represents a BFC statement + Comment, // Object represents a comment + Gibberish, // Object is the result of failed parsing + Empty, // Object represents an empty line + Unidentified, // Object is an uninitialized (SHOULD NEVER HAPPEN) + NumTypes // Amount of object types + }; + + LDObject (); + virtual ~LDObject (); + + // Index (i.e. line number) of this object + long getIndex (OpenFile* pFile); + + // Color used by this object. Comments, gibberish and empty entries + // do not use this field. + short dColor; + + // OpenGL list for this object + uint uGLList, uGLPickList; + + // Vertices of this object + vertex vaCoords[4]; + + // Object this object was referenced from, if any + LDObject* parent; + + // Type enumerator of this object + virtual LDObject::Type getType () const { + return LDObject::Unidentified; + }; + + // A string that represents this line + virtual str getContents () { + return ""; + } + + // Creates a new LDObject identical to this one and returns a pointer to it. + virtual LDObject* clone () { + return new LDObject (*this); + } + + // Replace this LDObject with another LDObject. This method deletes the + // object and any pointers to it become invalid. + void replace (LDObject* replacement); + + // Swap this object with another. + void swap (LDObject* other); + + // Moves this object using the given vertex as a movement vector + virtual void move (vertex vVector); + + // What object in the current file ultimately references this? + LDObject* topLevelParent (); + + // Number of vertices this object has + virtual short vertices () const { return 0; } + + // Is this object colored? + virtual bool isColored () const { return false; } + + // Does this object have meaning in the part model? + virtual bool isSchemantic () const { return false; } + + // Returns a sample object by the given value + static LDObject* getDefault (const LDObject::Type type); + + static void moveObjects (std::vector<LDObject*> objs, const bool bUp); + static str objectListContents (const std::vector<LDObject*>& objs); + + // Object list entry for this object + QListWidgetItem* qObjListEntry; + + bool hidden () const { return m_hidden; } + void setHidden (const bool hidden) { m_hidden = hidden; } + +private: + bool m_hidden; +}; + +// ============================================================================= +// LDGibberish +// +// Represents a line in the LDraw file that could not be properly parsed. It is +// represented by a (!) ERROR in the code view. It exists for the purpose of +// allowing garbage lines be debugged and corrected within LDForge. The member +// zContent contains the contents of the unparsable line. +// ============================================================================= +class LDGibberish : public LDObject { +public: + IMPLEMENT_LDTYPE (Gibberish, 0) + LDOBJ_UNCOLORED + LDOBJ_SCHEMANTIC + + LDGibberish (str _zContent, str _zReason); + + // Content of this unknown line + str zContents; + + // Why is this gibberish? + str zReason; +}; + +// ============================================================================= +// LDEmptyLine +// +// Represents an empty line in the LDraw code file. +// ============================================================================= +class LDEmpty : public LDObject { +public: + IMPLEMENT_LDTYPE (Empty, 0) + LDOBJ_UNCOLORED + LDOBJ_NON_SCHEMANTIC +}; + +// ============================================================================= +// LDComment +// +// Represents a code-0 comment in the LDraw code file. Member zText contains +// the text of the comment. +// ============================================================================= +class LDComment : public LDObject { +public: + IMPLEMENT_LDTYPE (Comment, 0) + LDOBJ_UNCOLORED + LDOBJ_NON_SCHEMANTIC + + LDComment (str zText) : text (zText) {} + + str text; // The text of this comment +}; + +// ============================================================================= +// LDBFC +// +// Represents a 0 BFC statement in the LDraw code. eStatement contains the type +// of this statement. +// ============================================================================= +class LDBFC : public LDComment { +public: + enum Type { + CertifyCCW, + CCW, + CertifyCW, + CW, + NoCertify, // Winding becomes disabled (0 BFC NOCERTIFY) + InvertNext, // Winding is inverted for next object (0 BFC INVERTNEXT) + NumStatements + }; + + IMPLEMENT_LDTYPE (BFC, 0) + LDOBJ_UNCOLORED + LDOBJ_CUSTOM_SCHEMANTIC { return (type == InvertNext); } + + LDBFC (const LDBFC::Type eType) : type (eType) {} + + // Statement strings + static const char* statements[]; + + Type type; +}; + +// ============================================================================= +// LDSubfile +// +// Represents a single code-1 subfile reference. +// ============================================================================= +class LDSubfile : public LDObject { +public: + IMPLEMENT_LDTYPE (Subfile, 0) + LDOBJ_COLORED + LDOBJ_SCHEMANTIC + + vertex vPosition; // Position of the subpart (FIXME: should get rid of this) + matrix<3> mMatrix; // Transformation matrix for the subpart + str zFileName; // Filename of the subpart + OpenFile* pFile; // Pointer to opened file for this subfile. null if unopened. + + // Inlines this subfile. Note that return type is an array of heap-allocated + // LDObject-clones, they must be deleted one way or another. + std::vector<LDObject*> inlineContents (bool bDeepInline, bool bCache); +}; + +// ============================================================================= +// LDLine +// +// Represents a single code-2 line in the LDraw code file. v0 and v1 are the end +// points of the line. The line is colored with dColor unless uncolored mode is +// set. +// ============================================================================= +class LDLine : public LDObject { +public: + IMPLEMENT_LDTYPE (Line, 2) + LDOBJ_COLORED + LDOBJ_SCHEMANTIC + + LDLine (vertex v1, vertex v2); +}; + +// ============================================================================= +// LDCondLine +// +// Represents a single code-5 conditional line. The end-points v0 and v1 are +// inherited from LDLine, c0 and c1 are the control points of this line. +// ============================================================================= +class LDCondLine : public LDLine { +public: + IMPLEMENT_LDTYPE (CondLine, 4) + LDOBJ_COLORED + LDOBJ_SCHEMANTIC +}; + +// ============================================================================= +// LDTriangle +// +// Represents a single code-3 triangle in the LDraw code file. Vertices v0, v1 +// and v2 contain the end-points of this triangle. dColor is the color the +// triangle is colored with. +// ============================================================================= +class LDTriangle : public LDObject { +public: + IMPLEMENT_LDTYPE (Triangle, 3) + LDOBJ_COLORED + LDOBJ_SCHEMANTIC + + LDTriangle (vertex _v0, vertex _v1, vertex _v2) { + vaCoords[0] = _v0; + vaCoords[1] = _v1; + vaCoords[2] = _v2; + } +}; + +// ============================================================================= +// LDQuad +// +// Represents a single code-4 quadrilateral. v0, v1, v2 and v3 are the end points +// of the quad, dColor is the color used for the quad. +// ============================================================================= +class LDQuad : public LDObject { +public: + IMPLEMENT_LDTYPE (Quad, 4) + LDOBJ_COLORED + LDOBJ_SCHEMANTIC + + // Split this quad into two triangles (note: heap-allocated) + vector<LDTriangle*> splitToTriangles (); +}; + +// ============================================================================= +// LDVertex +// +// The vertex is an LDForce-specific extension which represents a single +// vertex which can be used as a parameter to tools or to store coordinates +// with. Vertices are a part authoring tool and they should not appear in +// finished parts. +// ============================================================================= +class LDVertex : public LDObject { +public: + IMPLEMENT_LDTYPE (Vertex, 0) // TODO: move vPosition to vaCoords[0] + LDOBJ_COLORED + LDOBJ_NON_SCHEMANTIC + + vertex vPosition; +}; + +// ============================================================================= +// LDRadial +// +// The generic radial primitive (radial for short) is another LDforge-specific +// extension which represents an arbitrary circular primitive. Radials can appear +// as circles, cylinders, rings, cones, discs and disc negatives; the point is to +// allow part authors to add radial primitives to parts without much hassle about +// non-existant primitive parts. +// ============================================================================= +class LDRadial : public LDObject { +public: + enum Type { + Circle, + Cylinder, + Disc, + DiscNeg, + Ring, + Cone, + NumTypes + }; + + IMPLEMENT_LDTYPE (Radial, 0) + LDOBJ_COLORED + LDOBJ_SCHEMANTIC + + LDRadial::Type eRadialType; + vertex vPosition; + matrix<3> mMatrix; + short dDivisions, dSegments, dRingNum; + + LDRadial (LDRadial::Type eRadialType, vertex vPosition, matrix<3> mMatrix, + short dDivisions, short dSegments, short dRingNum) : + eRadialType (eRadialType), vPosition (vPosition), mMatrix (mMatrix), + dDivisions (dDivisions), dSegments (dSegments), dRingNum (dRingNum) {} + + // Returns a set of objects that provide the equivalent of this radial. + // Note: objects are heap-allocated. + std::vector<LDObject*> decompose (bool bTransform); + + // Compose a file name for this radial. + str makeFileName (); + + char const* radialTypeName (); + static char const* radialTypeName (const LDRadial::Type eType); +}; + +// ============================================================================= +// Object type names. Pass the return value of getType as the index to get a +// string representation of the object's type. +extern const char* g_saObjTypeNames[]; + +// Icons for these types +extern const char* g_saObjTypeIcons[]; + +#endif // LDTYPES_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,104 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QApplication> +#include "gui.h" +#include "file.h" +#include "bbox.h" +#include "misc.h" +#include "config.h" +#include "colors.h" +#include "types.h" + +vector<OpenFile*> g_loadedFiles; +OpenFile* g_curfile = null; +ForgeWindow* g_win = null; +bbox g_BBox; +const QApplication* g_app = null; + +const vertex g_origin (0.0f, 0.0f, 0.0f); +const matrix<3> g_identity ({1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}); + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +int main (int argc, char* argv[]) { + // Load or create the configuration + if (!config::load()) { + printf ("Creating configuration file...\n"); + if (config::save ()) + printf ("Configuration file successfully created.\n"); + else + printf ("failed to create configuration file!\n"); + } + + const QApplication app (argc, argv); + LDPaths::initPaths (); + + initColors (); + initPartList (); + + ForgeWindow* win = new ForgeWindow; + + g_app = &app; + + newFile (); + + win->show (); + return app.exec (); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void logf (const char* fmtstr, ...) { + va_list va; + va_start (va, fmtstr); + g_win->logVA (LOG_Normal, fmtstr, va); + va_end (va); +} + +void logf (LogType type, const char* fmtstr, ...) { + va_list va; + va_start (va, fmtstr); + g_win->logVA (type, fmtstr, va); + va_end (va); +} + +void warnf (const char* fmtstr, ...) { + va_list va; + va_start (va, fmtstr); + g_win->logVA (LOG_Warning, fmtstr, va); + va_end (va); +} + +void errf (const char* fmtstr, ...) { + va_list va; + va_start (va, fmtstr); + g_win->logVA (LOG_Error, fmtstr, va); + va_end (va); +} + +#ifndef RELEASE +void devf (const char* fmtstr, ...) { + va_list va; + va_start (va, fmtstr); + g_win->logVA (LOG_Dev, fmtstr, va); + va_end (va); +} +#endif // RELEASE \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,277 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include <locale.h> +#include <qcolor.h> +#include "common.h" +#include "misc.h" +#include "gui.h" + +// Prime number table. +const ushort g_uaPrimes[NUM_PRIMES] = { + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, + 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, + 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, + 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, + 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, + 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, + 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, + 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, + 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, + 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, + 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, + 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, + 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, + 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, + 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, + 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, + 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, + 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, + 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, + 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, + 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, + 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, + 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, + 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, + 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, + 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, + 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, + 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, + 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, + 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, + 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, + 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, + 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, + 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, + 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, + 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, + 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, + 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, + 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, + 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, + 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, + 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, + 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, + 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, + 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, + 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, + 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, + 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, + 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, + 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +// Grid stuff +cfg (int, grid, Grid::Medium); +EXTERN_ACTION (gridCoarse) +EXTERN_ACTION (gridMedium) +EXTERN_ACTION (gridFine) + +cfg (float, grid_coarse_x, 5.0f); +cfg (float, grid_coarse_y, 5.0f); +cfg (float, grid_coarse_z, 5.0f); +cfg (float, grid_coarse_angle, 45.0f); +cfg (float, grid_medium_x, 1.0f); +cfg (float, grid_medium_y, 1.0f); +cfg (float, grid_medium_z, 1.0f); +cfg (float, grid_medium_angle, 22.5f); +cfg (float, grid_fine_x, 0.1f); +cfg (float, grid_fine_y, 0.1f); +cfg (float, grid_fine_z, 0.1f); +cfg (float, grid_fine_angle, 7.5f); + +const gridinfo g_GridInfo[3] = { + { "Coarse", { &grid_coarse_x, &grid_coarse_y, &grid_coarse_z, &grid_coarse_angle }, &ACTION (gridCoarse) }, + { "Medium", { &grid_medium_x, &grid_medium_y, &grid_medium_z, &grid_medium_angle }, &ACTION (gridMedium) }, + { "Fine", { &grid_fine_x, &grid_fine_y, &grid_fine_z, &grid_fine_angle }, &ACTION (gridFine) } +}; + +// ============================================================================= +double Grid::snap (double in, const Grid::Config axis) { + const double gridval = currentGrid ().confs[axis]->value; + const long mult = abs (in / gridval); + const bool neg = (in < 0); + + double out = mult * gridval; + + if (abs<double> (in) - (mult * gridval) > gridval / 2) + out += gridval; + + if (neg && out != 0) + out *= -1; + + return out; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +str ftoa (double fCoord) { + // Disable the locale first so that the decimal point will not + // turn into anything weird (like commas) + setlocale (LC_NUMERIC, "C"); + + str zRep = fmt ("%f", fCoord); + + // Remove trailing zeroes + while (zRep[~zRep - 1] == '0') + zRep -= 1; + + // If there was only zeroes in the decimal place, remove + // the decimal point now. + if (zRep[~zRep - 1] == '.') + zRep -= 1; + + return zRep; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +bool isNumber (str& zToken) { + char* cpPointer = &zToken[0]; + bool bGotDot = false; + + // Allow leading hyphen for negatives + if (*cpPointer == '-') + cpPointer++; + + while (*cpPointer != '\0') { + if (*cpPointer == '.' && !bGotDot) { + // Decimal point + bGotDot = true; + cpPointer++; + continue; + } + + if (*cpPointer >= '0' && *cpPointer <= '9') { + cpPointer++; + continue; // Digit + } + + // If the above cases didn't catch this character, it was + // illegal and this is therefore not a number. + return false; + } + + return true; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void simplify (short& dNum, short& dDenom) { + bool bRepeat; + + do { + bRepeat = false; + + for (ulong x = 0; x < NUM_PRIMES; x++) { + ulong i = NUM_PRIMES - x - 1; + ushort uPrime = g_uaPrimes[i]; + + if (dNum <= uPrime || dDenom <= uPrime) + continue; + + if ((dNum % uPrime == 0) && (dDenom % uPrime == 0)) { + dNum /= uPrime; + dDenom /= uPrime; + bRepeat = true; + break; + } + } + } while (bRepeat); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +StringParser::StringParser (str zInText, char cSeparator) { + zaTokens = zInText.split (cSeparator, true); + dPos = -1; +} + +// ----------------------------------------------------------------------------- +bool StringParser::atBeginning () { + return (dPos == -1); +} + +// ----------------------------------------------------------------------------- +bool StringParser::atEnd () { + return (dPos == (signed) zaTokens.size () - 1); +} + +// ----------------------------------------------------------------------------- +bool StringParser::getToken (str& zVal, const ushort uInPos) { + if (uInPos >= zaTokens.size()) + return false; + + zVal = zaTokens[uInPos]; + return true; +} + +// ----------------------------------------------------------------------------- +bool StringParser::next (str& zVal) { + return getToken (zVal, ++dPos); +} + +// ----------------------------------------------------------------------------- +bool StringParser::peekNext (str& zVal) { + return getToken (zVal, dPos + 1); +} + +// ----------------------------------------------------------------------------- +bool StringParser::findToken (short& dResult, char const* sNeedle, short dArgs) { + for (ushort i = 0; i < (zaTokens.size () - dArgs); ++i) { + if (zaTokens[i] == sNeedle) { + dResult = i; + return true; + } + } + + return false; +} + +// ----------------------------------------------------------------------------- +void StringParser::rewind () { + dPos = -1; +} + +// ----------------------------------------------------------------------------- +void StringParser::seek (short int dAmount, bool bRelative) { + dPos = (bRelative ? dPos : 0) + dAmount; +} + +// ----------------------------------------------------------------------------- +size_t StringParser::size () { + return zaTokens.size(); +} + +// ----------------------------------------------------------------------------- +bool StringParser +::tokenCompare (short int dInPos, const char* sOther) { + str tok; + if (!getToken (tok, dInPos)) + return false; + + return (tok == sOther); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/misc.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,110 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MISC_H +#define MISC_H + +#include "common.h" +#include "str.h" +#include "config.h" + +#define NUM_PRIMES 500 + +class QColor; +class QAction; + +// Prime numbers +extern const ushort g_uaPrimes[NUM_PRIMES]; + +// Returns whether a given string represents a floating point number. +bool isNumber (str& zToken); + +// Converts a float value to a string value. +str ftoa (double fCoord); + +// Simplifies the given fraction. +void simplify (short& dNum, short& dDenom); + +// Grid stuff +typedef struct { + const char* const name; + floatconfig* const confs[4]; + QAction** const actionptr; +} gridinfo; + +extern_cfg (int, grid); +static const short g_NumGrids = 3; +extern const gridinfo g_GridInfo[3]; + +inline const gridinfo& currentGrid () { + return g_GridInfo[grid]; +} + +namespace Grid { + enum Type { + Coarse, + Medium, + Fine + }; + + enum Config { + X, + Y, + Z, + Angle + }; + + double snap (double value, const Grid::Config axis); +}; + +// ============================================================================= +template<class T> void dataswap (T& a, T& b) { + T c = a; + a = b; + b = c; +} + +// ============================================================================= +// StringParser +// +// String parsing utility +// ============================================================================= +class StringParser { +public: + std::vector<str> zaTokens; + short dPos; + + StringParser (str zInText, char cSeparator); + + bool atEnd (); + bool atBeginning (); + bool next (str& zVal); + bool peekNext (str& zVal); + bool getToken (str& zVal, const ushort uInPos); + bool findToken (short& dResult, char const* sNeedle, short dArgs); + size_t size (); + void rewind (); + void seek (short dAmount, bool bRelative); + bool tokenCompare (short int dInPos, const char* sOther); + + str operator[] (const size_t uIndex) { + return zaTokens[uIndex]; + } +}; + +#endif // MISC_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/newPartDialog.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,121 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <qgridlayout.h> +#include "newPartDialog.h" +#include "file.h" + +// ------------------------------------- +enum { + LICENSE_CCAL, + LICENSE_NonCA, + LICENSE_None +}; + +// ------------------------------------- +enum { + BFCBOX_CCW, + BFCBOX_CW, + BFCBOX_None, +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +NewPartDialog::NewPartDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) { + lb_brickIcon = new QLabel; + lb_brickIcon->setPixmap (getIcon ("brick")); + + lb_name = new QLabel ("Name:"); + le_name = new QLineEdit; + le_name->setMinimumWidth (320); + + lb_author = new QLabel ("Author:"); + le_author = new QLineEdit; + + rb_license = new RadioBox ("License", { + "CCAL Redistributable", + "Non-redistributable", + "Don't append a license", + }, LICENSE_CCAL); + + rb_BFC = new RadioBox ("BFC Winding", { + "CCW", + "CW", + "No winding" + }, BFCBOX_CCW); + + QHBoxLayout* boxes = new QHBoxLayout; + boxes->addWidget (rb_license); + boxes->addWidget (rb_BFC); + + IMPLEMENT_DIALOG_BUTTONS + + QGridLayout* layout = new QGridLayout; + layout->addWidget (lb_brickIcon, 0, 0); + layout->addWidget (lb_name, 0, 1); + layout->addWidget (le_name, 0, 2); + layout->addWidget (lb_author, 1, 1); + layout->addWidget (le_author, 1, 2); + layout->addLayout (boxes, 2, 1, 1, 2); + layout->addWidget (bbx_buttons, 3, 2); + + setLayout (layout); + setWindowIcon (getIcon ("brick")); + setWindowTitle (APPNAME ": New Part"); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void NewPartDialog::StaticDialog () { + NewPartDialog dlg (g_win); + if (dlg.exec ()) { + newFile (); + + short idx; + str zAuthor = dlg.le_author->text (); + vector<LDObject*>& objs = g_curfile->m_objs; + + idx = dlg.rb_BFC->value (); + const LDBFC::Type eBFCType = + (idx == BFCBOX_CCW) ? LDBFC::CertifyCCW : + (idx == BFCBOX_CW) ? LDBFC::CertifyCW : + LDBFC::NoCertify; + + idx = dlg.rb_license->value (); + const char* sLicense = + (idx == LICENSE_CCAL) ? "Redistributable under CCAL version 2.0 : see CAreadme.txt" : + (idx == LICENSE_NonCA) ? "Not redistributable : see NonCAreadme.txt" : + null; + + objs.push_back (new LDComment (dlg.le_name->text ())); + objs.push_back (new LDComment ("Name: <untitled>.dat")); + objs.push_back (new LDComment (fmt ("Author: %s", zAuthor.chars()))); + objs.push_back (new LDComment (fmt ("!LDRAW_ORG Unofficial_Part"))); + + if (sLicense != null) + objs.push_back (new LDComment (fmt ("!LICENSE %s", sLicense))); + + objs.push_back (new LDEmpty); + objs.push_back (new LDBFC (eBFCType)); + objs.push_back (new LDEmpty); + + g_win->refresh (); + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/newPartDialog.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,44 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef NEWPARTDIALOG_H +#define NEWPARTDIALOG_H + +#include <qdialog.h> +#include <qlabel.h> +#include <qlineedit.h> +#include <qcombobox.h> +#include <qdialogbuttonbox.h> +#include <qradiobutton.h> +#include <qbuttongroup.h> +#include "gui.h" +#include "radiobox.h" + +class NewPartDialog : public QDialog { +public: + explicit NewPartDialog (QWidget* parent = null, Qt::WindowFlags f = 0); + static void StaticDialog (); + + QLabel* lb_brickIcon, *lb_name, *lb_author, *lb_license, *lb_BFC; + QLineEdit* le_name, *le_author; + + RadioBox* rb_license, *rb_BFC; + QDialogButtonBox* bbx_buttons; +}; + +#endif // NEWPARTDIALOG_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/radiobox.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,97 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <qboxlayout.h> +#include <qradiobutton.h> +#include "radiobox.h" + +static QBoxLayout::Direction makeDirection (Qt::Orientation orient, bool invert = false) { + return (orient == (invert ? Qt::Vertical : Qt::Horizontal)) ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom; +} + +void RadioBox::init (Qt::Orientation orient) { + dir = makeDirection (orient); + + buttonGroup = new QButtonGroup; + currentId = 0; + coreLayout = null; + + coreLayout = new QBoxLayout (makeDirection (orient, true)); + setLayout (coreLayout); + + // Init the first row with a break + rowBreak (); + + connect (buttonGroup, SIGNAL (buttonPressed (QAbstractButton*)), this, SLOT (slot_buttonPressed (QAbstractButton*))); + connect (buttonGroup, SIGNAL (buttonPressed (int)), this, SLOT (slot_buttonPressed (int))); +} + +RadioBox::RadioBox (const QString& title, initlist<char const*> entries, int const defaultId, + const Qt::Orientation orient, QWidget* parent) : QGroupBox (title, parent), defaultId (defaultId) +{ + init (orient); + + for (char const* entry : entries) + addButton (entry); +} + +void RadioBox::rowBreak () { + QBoxLayout* newLayout = new QBoxLayout (dir); + currentLayout = newLayout; + layouts.push_back (newLayout); + + coreLayout->addLayout (newLayout); +} + +void RadioBox::addButton (const char* entry) { + QRadioButton* button = new QRadioButton (entry); + addButton (button); +} + +void RadioBox::addButton (QRadioButton* button) { + bool const selectThis = (currentId == defaultId); + + objects.push_back (button); + buttonGroup->addButton (button, currentId++); + currentLayout->addWidget (button); + + if (selectThis) + button->setChecked (true); +} + +RadioBox& RadioBox::operator<< (QRadioButton* button) { + addButton (button); + return *this; +} + +RadioBox& RadioBox::operator<< (const char* entry) { + addButton (entry); + return *this; +} + +void RadioBox::setCurrentRow (uint row) { + currentLayout = layouts[row]; +} + +void RadioBox::slot_buttonPressed (int btn) { + emit sig_buttonPressed (btn); +} + +void RadioBox::slot_buttonPressed (QAbstractButton* btn) { + emit sig_buttonPressed (btn); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/radiobox.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,115 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RADIOBOX_H +#define RADIOBOX_H + +#include "common.h" +#include <qwidget.h> +#include <QGroupBox> +#include <qradiobutton.h> +#include <qboxlayout.h> +#include <qbuttongroup.h> + +// ============================================================================= +// RadioBox +// +// Convenience widget - is a groupbox of radio buttons. +// ============================================================================= +class RadioBox : public QGroupBox { + Q_OBJECT + +public: + void init (Qt::Orientation orient); + + explicit RadioBox (QWidget* parent = null) : QGroupBox (parent) { + init (Qt::Vertical); + } + + explicit RadioBox (const QString& title, QWidget* parent = null) : QGroupBox (title, parent) { + init (Qt::Vertical); + } + + explicit RadioBox () { + init (Qt::Vertical); + } + + explicit RadioBox (const QString& title, initlist<char const*> entries, int const defaultId, + const Qt::Orientation orient = Qt::Vertical, QWidget* parent = null); + + void rowBreak (); + void setCurrentRow (uint row); + void addButton (const char* entry); + void addButton (QRadioButton* button); + RadioBox& operator<< (QRadioButton* button); + RadioBox& operator<< (const char* entry); + + int value () const { + return buttonGroup->checkedId (); + } + + void setValue (int val) { + buttonGroup->button (val)->setChecked (true); + } + + std::vector<QRadioButton*>::iterator begin () { + return objects.begin (); + } + + std::vector<QRadioButton*>::iterator end () { + return objects.end (); + } + + QRadioButton* operator[] (uint n) const { + return objects[n]; + } + + bool exclusive () const { + return buttonGroup->exclusive (); + } + + void setExclusive (bool val) { + buttonGroup->setExclusive (val); + } + + bool isChecked (int n) const { + return buttonGroup->checkedId () == n; + } + +signals: + void sig_buttonPressed (int btn); + void sig_buttonPressed (QAbstractButton* btn); + +private: + std::vector<QRadioButton*> objects; + std::vector<QBoxLayout*> layouts; + QBoxLayout* coreLayout; + QBoxLayout* currentLayout; + QBoxLayout::Direction dir; + int currentId; + int defaultId; + QButtonGroup* buttonGroup; + + Q_DISABLE_COPY (RadioBox) + +private slots: + void slot_buttonPressed (int btn); + void slot_buttonPressed (QAbstractButton* btn); +}; + +#endif // RADIOBOX_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/setContentsDialog.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,107 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QAbstractButton> +#include <qboxlayout.h> +#include "setContentsDialog.h" +#include "file.h" +#include "gui.h" +#include "history.h" + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +SetContentsDialog::SetContentsDialog (LDObject* obj, QWidget* parent) : QDialog(parent) { + setWindowTitle (APPNAME ": Set Contents"); + + lb_contents = new QLabel ("Set contents:", parent); + + le_contents = new QLineEdit (parent); + le_contents->setText (obj->getContents ().chars()); + le_contents->setWhatsThis ("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\">official file format " + "standard</a> for further information."); + le_contents->setMinimumWidth (384); + + if (obj->getType() == LDObject::Gibberish) { + lb_error = new QLabel; + lb_error->setText (fmt ("<span style=\"color: #900\">%s</span>", + static_cast<LDGibberish*> (obj)->zReason.chars())); + + QPixmap qErrorPixmap = getIcon ("error").scaledToHeight (16); + + lb_errorIcon = new QLabel; + lb_errorIcon->setPixmap (qErrorPixmap); + } + + IMPLEMENT_DIALOG_BUTTONS + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget (lb_contents); + layout->addWidget (le_contents); + + QHBoxLayout* layout2 = new QHBoxLayout; + + if (obj->getType() == LDObject::Gibberish) { + layout2->addWidget (lb_errorIcon); + layout2->addWidget (lb_error); + } + + layout2->addWidget (bbx_buttons); + layout->addLayout (layout2); + setLayout (layout); + + setWindowTitle (APPNAME ": Set Contents"); + setWindowIcon (getIcon ("set-contents")); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void SetContentsDialog::slot_handleButtons (QAbstractButton* qButton) { + qButton = qButton; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void SetContentsDialog::staticDialog (LDObject* obj) { + if (!obj) + return; + + SetContentsDialog dlg (obj, g_win); + if (dlg.exec () == false) + return; + + LDObject* oldobj = obj; + + // Reinterpret it from the text of the input field + obj = parseLine (dlg.le_contents->text ().toStdString ().c_str ()); + + // Mark down the history now before we perform the replacement (which + // destroys the old object) + History::addEntry (new EditHistory ({(ulong) oldobj->getIndex (g_curfile)}, + {oldobj->clone ()}, {obj->clone ()})); + + oldobj->replace (obj); + + // Rebuild stuff after this + g_win->refresh (); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/setContentsDialog.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,47 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SETCONTENTSDIALOG_H + +#include <qdialog.h> +#include <qlineedit.h> +#include <qlabel.h> +#include <qdialogbuttonbox.h> +#include "common.h" + +// ============================================================================= +// SetContentsDialog +// +// Performs the Set Contents dialog on the given LDObject. Object's contents +// are exposed to the user and is reinterpreted if the user accepts the new +// contents. +// ============================================================================= +class SetContentsDialog : public QDialog { +public: + QLabel* lb_contents, *lb_errorIcon, *lb_error; + QLineEdit* le_contents; + QDialogButtonBox* bbx_buttons; + + SetContentsDialog (LDObject* obj, QWidget* parent = null); + static void staticDialog (LDObject* obj); + +private slots: + void slot_handleButtons (QAbstractButton* qButton); +}; + +#endif // SETCONTENTSDIALOG_H \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/str.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,503 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <assert.h> +// #include <initializer_list> +#include "str.h" +#include "common.h" +#include "misc.h" + +#define ITERATE_STRING(u) \ + for (unsigned int u = 0; u < strlen (text); u++) + +// ============================================================================ +// vdynformat: Try to write to a formatted string with size bytes first, if +// that fails, double the size and keep recursing until it works. +char* vdynformat (const char* csFormat, va_list vArgs, long lSize) { + char* buffer = new char[lSize]; + int r = vsnprintf (buffer, lSize - 1, csFormat, vArgs); + if (r > (signed)(lSize - 1) || r < 0) { + delete[] buffer; + buffer = vdynformat (csFormat, vArgs, lSize * 2); + } + return buffer; +} + +// ============================================================================ +str::str () { + text = new char[1]; + clear(); + alloclen = strlen (text); +} + +str::str (const char* c) { + text = new char[1]; + text[0] = '\0'; + curs = alloclen = 0; + append (c); +} + +str::str (char c) { + text = new char[1]; + text[0] = '\0'; + curs = alloclen = 0; + append (c); +} + +str::str (QString c) { + text = new char[1]; + text[0] = '\0'; + curs = alloclen = 0; + append (c); +} + +str::~str () { + // delete[] text; +} + +// ============================================================================ +void str::clear () { + delete[] text; + text = new char[1]; + text[0] = '\0'; + curs = 0; + alloclen = 0; +} + +// ============================================================================ +void str::resize (unsigned int len) { + unsigned int oldlen = strlen (text); + char* oldtext = new char[oldlen]; + strncpy (oldtext, text, oldlen); + + delete[] text; + text = new char[len+1]; + for (unsigned int u = 0; u < len+1; u++) + text[u] = 0; + strncpy (text, oldtext, len); + delete[] oldtext; + + alloclen = len; +} + +// ============================================================================ +void str::dump () { + for (unsigned int u = 0; u <= alloclen; u++) + printf ("\t%u. %u (%c)\n", u, text[u], text[u]); +} + +// ============================================================================ +// Adds a new character at the end of the string. +void str::append (const char c) { + // Out of space, thus resize + if (curs == alloclen) + resize (alloclen + 1); + text[curs] = c; + curs++; +} + +void str::append (const char* c) { + resize (alloclen + strlen (c)); + + for (unsigned int u = 0; u < strlen (c); u++) { + if (c[u] != 0) + append (c[u]); + } +} + +void str::append (str c) { + append (c.chars()); +} + +void str::append (QString c) { + append (c.toUtf8 ().constData ()); +} + +// ============================================================================ +void str::appendformat (const char* c, ...) { + va_list v; + + va_start (v, c); + char* buf = vdynformat (c, v, 256); + va_end (v); + + append (buf); + delete[] buf; +} + +void str::format (const char* fmt, ...) { + clear (); + + va_list v; + + va_start (v, fmt); + char* buf = vdynformat (fmt, v, 256); + va_end (v); + + append (buf); + delete[] buf; +} + +// ============================================================================ +char* str::chars () { + return text; +} + +// ============================================================================ +int str::first (const char* c, unsigned int a) { + unsigned int r = 0; + unsigned int index = 0; + for (; a < alloclen; a++) { + if (text[a] == c[r]) { + if (r == 0) + index = a; + + r++; + if (r == strlen (c)) + return index; + } else { + if (r != 0) { + // If the string sequence broke at this point, we need to + // check this character again, for a new sequence just + // might start right here. + a--; + } + + r = 0; + } + } + + return -1; +} + +// ============================================================================ +int str::last (const char* c, int a) { + if (a == -1) + a = len(); + + int max = strlen (c)-1; + + int r = max; + for (; a >= 0; a--) { + if (text[a] == c[r]) { + r--; + if (r == -1) + return a; + } else { + if (r != max) + a++; + + r = max; + } + } + + return -1; +} + +// ============================================================================ +str str::substr (unsigned int a, unsigned int b) { + if (a > len()) a = len(); + if (b > len()) b = len(); + + if (b == a) + return ""; + + if (b < a) { + printf ("str::substring:: indices %u and %u given, should be the other way around, swapping..\n", a, b); + + // Swap the variables + unsigned int c = a; + a = b; + b = c; + } + + char* s = new char[b - a + 1]; + strncpy (s, text + a, b - a); + s[b - a] = '\0'; + + str other = s; + delete[] s; + return other; +} + +// ============================================================================ +void str::remove (unsigned int idx, unsigned int dellen) { + str s1 = substr (0, idx); + str s2 = substr (idx + dellen, -1); + + clear(); + + append (s1); + append (s2); +} + +// ============================================================================ +str str::trim (int dellen) { + if (dellen > 0) + return substr (0, len() - dellen); + return substr (-dellen, len()); +} + +// ============================================================================ +void str::replace (const char* o, const char* n, unsigned int a) { + for (int idx; (idx = first (o, a)) != -1;) { + str s1 = substr (0, idx); + str s2 = substr (idx + strlen (o), len()); + + clear(); + + append (s1); + append (n); + append (s2); + } +} + +// ============================================================================ +str str::strip (char c) { + return strip ({c}); +} + +str str::strip (std::initializer_list<char> unwanted) { + str cache = text; + uint oldlen = len(); + + char* buf = new char[oldlen]; + char* bufptr = buf; + for (uint i = 0; i < oldlen; i++) { + bool valid = true; + for (const char* j = unwanted.begin(); j < unwanted.end() && valid; j++) + if (text[i] == *j) + valid = false; + + if (valid) + *bufptr++ = text[i]; + } + + *bufptr = '\0'; + assert (bufptr <= buf + oldlen); + + str zResult = buf; + delete[] buf; + + return zResult; +} + +void str::insert (char* c, unsigned int pos) { + str s1 = substr (0, pos); + str s2 = substr (pos, len()); + + clear(); + append (s1); + append (c); + append (s2); +} + +str str::reverse () { + char* buf = new char[len() + 1]; + + for (uint i = 0; i < len(); i++) + buf[i] = text[len() - i - 1]; + buf[len()] = '\0'; + + str other = buf; + delete[] buf; + return other; +} + +str str::repeat (int n) { + assert (n >= 0); + + str other; + for (int i = 0; i < n; i++) + other += text; + return other; +} + +// ============================================================================ +bool str::isnumber () { + ITERATE_STRING (u) { + // Minus sign as the first character is allowed for negatives + if (!u && text[u] == '-') + continue; + + if (text[u] < '0' || text[u] > '9') + return false; + } + return true; +} + +// ============================================================================ +bool str::isword () { + ITERATE_STRING (u) { + // lowercase letters + if (text[u] >= 'a' || text[u] <= 'z') + continue; + + // uppercase letters + if (text[u] >= 'A' || text[u] <= 'Z') + continue; + + return false; + } + return true; +} + +int str::instanceof (const char* c, uint n) { + unsigned int r = 0; + unsigned int index = 0; + unsigned int x = 0; + for (uint a = 0; a < alloclen; a++) { + if (text[a] == c[r]) { + if (r == 0) + index = a; + + r++; + if (r == strlen (c)) { + if (x++ == n) + return index; + r = 0; + } + } else { + if (r != 0) + a--; + r = 0; + } + } + + return -1; +} + +// ============================================================================ +int str::compare (const char* c) { + return strcmp (text, c); +} + +int str::compare (str c) { + return compare (c.chars()); +} + +int str::icompare (const char* c) { + return icompare (str ((char*)c)); +} + +int str::icompare (str b) { + return strcmp (tolower().chars(), b.tolower().chars()); +} + +// ============================================================================ +str str::tolower () { + str n = text; + + for (uint u = 0; u < len(); u++) { + if (n[u] >= 'A' && n[u] < 'Z') + n.text[u] += ('a' - 'A'); + } + + return n; +} + +// ============================================================================ +str str::toupper () { + str n = text; + + for (uint u = 0; u < len(); u++) { + if (n[u] >= 'a' && n[u] < 'z') + n.text[u] -= ('a' - 'A'); + } + + return n; +} + +// ============================================================================ +unsigned str::count (char c) { + unsigned n = 0; + ITERATE_STRING (u) + if (text[u] == c) + n++; + return n; +} + +unsigned str::count (char* c) { + unsigned int r = 0; + unsigned int tmp = 0; + ITERATE_STRING (u) { + if (text[u] == c[r]) { + r++; + if (r == strlen (c)) { + r = 0; + tmp++; + } + } else { + if (r != 0) + u--; + r = 0; + } + } + + return tmp; +} + +// ============================================================================ +std::vector<str> str::split (str del, bool bNoBlanks) { + std::vector<str> res; + unsigned int a = 0; + + // Find all separators and store the text left to them. + while (1) { + int b = first (del, a); + + if (b == -1) + break; + + if (!bNoBlanks || (b - a)) + res.push_back (substr (a, b)); + + a = b + strlen (del); + } + + // Add the string at the right of the last separator + if (!bNoBlanks || (len () - a)) + res.push_back (substr (a, len ())); + return res; +} + +std::vector<str> str::operator/ (str splitstring) {return split(splitstring);} +std::vector<str> str::operator/ (char* splitstring) {return split(splitstring);} +std::vector<str> str::operator/ (const char* splitstring) {return split(splitstring);} + +str& str::operator+= (vertex vrt) { + appendformat ("%s", vrt.stringRep (false).chars()); + return *this; +} + +str fmt (const char* fmt, ...) { + va_list va; + char* buf; + + va_start (va, fmt); + buf = vdynformat (fmt, va, 256); + va_end (va); + + str val = buf; + delete[] buf; + return val; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/str.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,271 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef STR_H +#define STR_H + +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <vector> +#include <QString> + +char* vdynformat (const char* csFormat, va_list vArgs, long int lSize); + +class vertex; + +// Dynamic string object, allocates memory when needed and +// features a good bunch of manipulation methods +class str { +private: + // The actual message + char* text; + + // Where will append() place new characters? + unsigned short curs; + + // Allocated length + unsigned short alloclen; + + // Resize the text buffer to len characters + void resize (unsigned int len); + +public: + // ====================================================================== + str (); + str (const char* c); + str (char c); + str (QString c); + ~str (); + + // ====================================================================== + // METHODS + + // Empty the string + void clear (); + + // Length of the string + size_t len () { + return strlen (text); + } + + // The char* form of the string + char* chars (); + + // Dumps the character table of the string + void dump (); + + // Appends text to the string + void append (const char c); + void append (const char* c); + void append (str c); + void append (QString c); + + // Formats text to the string. + void format (const char* fmt, ...); + + // Appends formatted text to the string. + void appendformat (const char* c, ...); + + // Returns the first occurrence of c in the string, optionally starting + // from a certain position rather than the start. + int first (const char* c, unsigned int a = 0); + + // Returns the last occurrence of c in the string, optionally starting + // from a certain position rather than the end. + int last (const char* c, int a = -1); + + // Returns a substring of the string, from a to b. + str substr (unsigned int a, unsigned int b); + + // Replace a substring with another substring. + void replace (const char* o, const char* n, unsigned int a = 0); + + // Removes a given index from the string, optionally more characters than just 1. + void remove (unsigned int idx, unsigned int dellen=1); + + // Trims the given amount of characters. If negative, the characters + // are removed from the beginning of the string, if positive, from the + // end of the string. + str trim (int dellen); + + // Inserts a substring into a certain position. + void insert (char* c, unsigned int pos); + + // Reverses the string. + str reverse (); + + // Repeats the string a given amount of times. + str repeat (int n); + + // Is the string a number? + bool isnumber (); + + // Is the string a word, i.e consists only of alphabetic letters? + bool isword (); + + // Convert string to lower case + str tolower (); + + // Convert string to upper case + str toupper (); + + // Compare this string with another + int compare (const char* c); + int compare (str c); + int icompare (str c); + int icompare (const char* c); + + // Counts the amount of substrings in the string + unsigned int count (char* s); + unsigned int count (char s); + + // Counts where the given substring is seen for the nth time + int instanceof (const char* s, unsigned n); + + char subscript (uint pos) { + return operator[] (pos); + } + + std::vector<str> split (str del, bool bNoBlanks = false); + + str strip (char c); + str strip (std::initializer_list<char> unwanted); + + // ====================================================================== + // OPERATORS + str operator+ (str& c) { + append (c); + return *this; + } + + str& operator+= (char c) { + append (c); + return *this; + } + + str& operator+= (const char* c) { + append (c); + return *this; + } + + str& operator+= (const str c) { + append (c); + return *this; + } + + str& operator+= (const QString c) { + append (c); + return *this; + } + + str& operator+= (vertex vrt); + + str operator* (const int repcount) { + repeat (repcount); + return *this; + } + + str& operator*= (const int repcount) { + str other = repeat (repcount); + clear (); + append (other); + return *this; + } + + str operator- (const int trimcount) { + return trim (trimcount); + } + + str& operator-= (const int trimcount) { + str other = trim (trimcount); + clear (); + append (other); + return *this; + } + + std::vector<str> operator/ (str splitstring); + std::vector<str> operator/ (char* splitstring); + std::vector<str> operator/ (const char* splitstring); + + int operator% (str splitstring) { + return count (splitstring.chars()); + } + + int operator% (char* splitstring) { + return count (splitstring); + } + + int operator% (const char* splitstring) { + return count (str (splitstring).chars()); + } + + str operator+ () { + return toupper (); + } + + str operator- () { + return tolower (); + } + + str operator! () { + return reverse (); + } + + size_t operator~ () { + return len (); + } + +#define DEFINE_OPERATOR_TYPE(OPER, TYPE) \ + bool operator OPER (TYPE other) {return compare(other) OPER 0;} +#define DEFINE_OPERATOR(OPER) \ + DEFINE_OPERATOR_TYPE (OPER, str) \ + DEFINE_OPERATOR_TYPE (OPER, char*) \ + DEFINE_OPERATOR_TYPE (OPER, const char*) + + DEFINE_OPERATOR (==) + DEFINE_OPERATOR (!=) + DEFINE_OPERATOR (>) + DEFINE_OPERATOR (<) + DEFINE_OPERATOR (>=) + DEFINE_OPERATOR (<=) + + char& operator[] (int pos) { + return text[pos]; + } + + operator char* () const { + return text; + } + + operator QString () const { + return text; + } + + operator int () const { + return atoi (text); + } + + operator uint () const { + return operator int(); + } +}; + +str fmt (const char* fmt, ...); + +#endif // STR_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/types.cpp Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,53 @@ +#include <assert.h> +#include "common.h" +#include "types.h" +#include "misc.h" + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +vertex vertex::midpoint (vertex& other) { + vertex mid; + + for (const Axis ax : g_Axes) + mid[ax] = (m_coords[ax] + other[ax]) / 2; + + return mid; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +str vertex::stringRep (const bool mangled) { + return fmt (mangled ? "(%s, %s, %s)" : "%s %s %s", + ftoa (coord (X)).chars(), + ftoa (coord (Y)).chars(), + ftoa (coord (Z)).chars()); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void vertex::transform (matrix<3> matr, vertex pos) { + double x2 = (matr[0] * x ()) + (matr[1] * y ()) + (matr[2] * z ()) + pos[X]; + double y2 = (matr[3] * x ()) + (matr[4] * y ()) + (matr[5] * z ()) + pos[Y]; + double z2 = (matr[6] * x ()) + (matr[7] * y ()) + (matr[8] * z ()) + pos[Z]; + + x () = x2; + y () = y2; + z () = z2; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +void vertex::transform (matrix<4> matr) { + double x2 = (matr[0] * x ()) + (matr[4] + y ()) + (matr[8] + z ()) + matr[12]; + double y2 = (matr[1] * x ()) + (matr[5] + y ()) + (matr[9] + z ()) + matr[13]; + double z2 = (matr[2] * x ()) + (matr[6] + y ()) + (matr[10] + z ()) + matr[14]; + double w2 = (matr[3] * x ()) + (matr[7] + y ()) + (matr[11] + z ()) + matr[15]; + + x () = x2 / w2; + y () = y2 / w2; + z () = z2 / w2; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/types.h Wed May 08 15:19:06 2013 +0300 @@ -0,0 +1,222 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 Santeri Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TYPES_H +#define TYPES_H + +#include "common.h" +#include "misc.h" + +typedef unsigned int uint; +typedef short unsigned int ushort; +typedef long unsigned int ulong; + +// Typedef out the _t suffices :) +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; +typedef int64_t int64; +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef uint64_t uint64; + +template<class T> using initlist = std::initializer_list<T>; +using std::vector; + +enum Axis { X, Y, Z }; +static const Axis g_Axes[3] = {X, Y, Z}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +// matrix +// +// A templated, mathematical N x N matrix +// ============================================================================= +template<int N> class matrix { +public: + // Constructors + matrix () {} + matrix (initlist<double> vals) { + assert (vals.size() == N * N); + memcpy (&m_vals[0], &(*vals.begin ()), sizeof m_vals); + } + + matrix (double fillval) { + for (short i = 0; i < (N * N); ++i) + m_vals[i] = fillval; + } + + matrix (double vals[]) { + for (short i = 0; i < (N * N); ++i) + m_vals[i] = vals[i]; + } + + template<int M> matrix (matrix<M> other) { + assert (M >= N); + + for (short i = 0; i < M; ++i) + for (short j = 0; j < M; ++j) { + const short idx = (i * M) + j; + m_vals[idx] = other[idx]; + } + } + + matrix<N> mult (matrix<N> other) { + matrix val; + val.zero (); + + for (short i = 0; i < N; ++i) + for (short j = 0; j < N; ++j) + for (short k = 0; k < N; ++k) + val[(i * N) + j] += m_vals[(i * N) + k] * other[(k * N) + j]; + + return val; + } + + matrix<N>& operator= (matrix<N> other) { + memcpy (&m_vals[0], &other.m_vals[0], sizeof (double) * N * N); + return *this; + } + + matrix<N> operator* (matrix<N> other) { + return mult (other); + } + + double& operator[] (const uint idx) { + return m_vals[idx]; + } + + const double& operator[] (const uint idx) const { + return m_vals[idx]; + } + + void zero () { + memset (&m_vals[0], 0, sizeof (double) * N * N); + } + + void puts () const { + for (short i = 0; i < N; ++i) { + for (short j = 0; j < N; ++j) + printf ("%*f\t", 10, m_vals[(i * N) + j]); + + printf ("\n"); + } + } + + str stringRep () const { + str val; + for (short i = 0; i < N * N; ++i) { + if (i > 0) + val += ' '; + + val.appendformat ("%s", ftoa (m_vals[i]).chars()); + } + + return val; + } + +private: + double m_vals[N * N]; +}; + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +// vertex +// +// Vertex class, contains a single point in 3D space. Not to be confused with +// LDVertex, which is a vertex used in an LDraw part file. +// ============================================================================= +class vertex { +public: + double m_coords[3]; + + vertex () {} + vertex (double x, double y, double z) { + m_coords[X] = x; + m_coords[Y] = y; + m_coords[Z] = z; + } + + void move (vertex other) { + for (const Axis ax : g_Axes) + m_coords[ax] += other[ax]; + } + + vertex& operator+= (vertex other) { + move (other); + return *this; + } + + vertex operator/ (const double d) const { + vertex other (*this); + return other /= d; + } + + vertex& operator/= (const double d) { + for (const Axis ax : g_Axes) + m_coords[ax] /= d; + return *this; + } + + bool operator== (const vertex& other) const { + return coord (X) == other[X] && + coord (Y) == other[Y] && + coord (Z) == other[Z]; + } + + bool operator!= (const vertex& other) const { + return !operator== (other); + } + + vertex operator- () const { + return vertex (-m_coords[X], -m_coords[Y], -m_coords[Z]); + } + + double& coord (const ushort n) { + return m_coords[n]; + } + + const double& coord (const ushort n) const { + return m_coords[n]; + } + + double& operator[] (const Axis ax) { + return coord ((ushort) ax); + } + + const double& operator[] (const Axis ax) const { + return coord ((ushort) ax); + } + + double& x () { return m_coords[X]; } + double& y () { return m_coords[Y]; } + double& z () { return m_coords[Z]; } + const double& x () const { return m_coords[X]; } + const double& y () const { return m_coords[Y]; } + const double& z () const { return m_coords[Z]; } + + vertex midpoint (vertex& other); + str stringRep (const bool mangled); + void transform (matrix<3> matr, vertex pos); + void transform (matrix<4> matr); +}; + +#endif // TYPES_H \ No newline at end of file
--- a/str.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,503 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <stdarg.h> -#include <assert.h> -// #include <initializer_list> -#include "str.h" -#include "common.h" -#include "misc.h" - -#define ITERATE_STRING(u) \ - for (unsigned int u = 0; u < strlen (text); u++) - -// ============================================================================ -// vdynformat: Try to write to a formatted string with size bytes first, if -// that fails, double the size and keep recursing until it works. -char* vdynformat (const char* csFormat, va_list vArgs, long lSize) { - char* buffer = new char[lSize]; - int r = vsnprintf (buffer, lSize - 1, csFormat, vArgs); - if (r > (signed)(lSize - 1) || r < 0) { - delete[] buffer; - buffer = vdynformat (csFormat, vArgs, lSize * 2); - } - return buffer; -} - -// ============================================================================ -str::str () { - text = new char[1]; - clear(); - alloclen = strlen (text); -} - -str::str (const char* c) { - text = new char[1]; - text[0] = '\0'; - curs = alloclen = 0; - append (c); -} - -str::str (char c) { - text = new char[1]; - text[0] = '\0'; - curs = alloclen = 0; - append (c); -} - -str::str (QString c) { - text = new char[1]; - text[0] = '\0'; - curs = alloclen = 0; - append (c); -} - -str::~str () { - // delete[] text; -} - -// ============================================================================ -void str::clear () { - delete[] text; - text = new char[1]; - text[0] = '\0'; - curs = 0; - alloclen = 0; -} - -// ============================================================================ -void str::resize (unsigned int len) { - unsigned int oldlen = strlen (text); - char* oldtext = new char[oldlen]; - strncpy (oldtext, text, oldlen); - - delete[] text; - text = new char[len+1]; - for (unsigned int u = 0; u < len+1; u++) - text[u] = 0; - strncpy (text, oldtext, len); - delete[] oldtext; - - alloclen = len; -} - -// ============================================================================ -void str::dump () { - for (unsigned int u = 0; u <= alloclen; u++) - printf ("\t%u. %u (%c)\n", u, text[u], text[u]); -} - -// ============================================================================ -// Adds a new character at the end of the string. -void str::append (const char c) { - // Out of space, thus resize - if (curs == alloclen) - resize (alloclen + 1); - text[curs] = c; - curs++; -} - -void str::append (const char* c) { - resize (alloclen + strlen (c)); - - for (unsigned int u = 0; u < strlen (c); u++) { - if (c[u] != 0) - append (c[u]); - } -} - -void str::append (str c) { - append (c.chars()); -} - -void str::append (QString c) { - append (c.toUtf8 ().constData ()); -} - -// ============================================================================ -void str::appendformat (const char* c, ...) { - va_list v; - - va_start (v, c); - char* buf = vdynformat (c, v, 256); - va_end (v); - - append (buf); - delete[] buf; -} - -void str::format (const char* fmt, ...) { - clear (); - - va_list v; - - va_start (v, fmt); - char* buf = vdynformat (fmt, v, 256); - va_end (v); - - append (buf); - delete[] buf; -} - -// ============================================================================ -char* str::chars () { - return text; -} - -// ============================================================================ -int str::first (const char* c, unsigned int a) { - unsigned int r = 0; - unsigned int index = 0; - for (; a < alloclen; a++) { - if (text[a] == c[r]) { - if (r == 0) - index = a; - - r++; - if (r == strlen (c)) - return index; - } else { - if (r != 0) { - // If the string sequence broke at this point, we need to - // check this character again, for a new sequence just - // might start right here. - a--; - } - - r = 0; - } - } - - return -1; -} - -// ============================================================================ -int str::last (const char* c, int a) { - if (a == -1) - a = len(); - - int max = strlen (c)-1; - - int r = max; - for (; a >= 0; a--) { - if (text[a] == c[r]) { - r--; - if (r == -1) - return a; - } else { - if (r != max) - a++; - - r = max; - } - } - - return -1; -} - -// ============================================================================ -str str::substr (unsigned int a, unsigned int b) { - if (a > len()) a = len(); - if (b > len()) b = len(); - - if (b == a) - return ""; - - if (b < a) { - printf ("str::substring:: indices %u and %u given, should be the other way around, swapping..\n", a, b); - - // Swap the variables - unsigned int c = a; - a = b; - b = c; - } - - char* s = new char[b - a + 1]; - strncpy (s, text + a, b - a); - s[b - a] = '\0'; - - str other = s; - delete[] s; - return other; -} - -// ============================================================================ -void str::remove (unsigned int idx, unsigned int dellen) { - str s1 = substr (0, idx); - str s2 = substr (idx + dellen, -1); - - clear(); - - append (s1); - append (s2); -} - -// ============================================================================ -str str::trim (int dellen) { - if (dellen > 0) - return substr (0, len() - dellen); - return substr (-dellen, len()); -} - -// ============================================================================ -void str::replace (const char* o, const char* n, unsigned int a) { - for (int idx; (idx = first (o, a)) != -1;) { - str s1 = substr (0, idx); - str s2 = substr (idx + strlen (o), len()); - - clear(); - - append (s1); - append (n); - append (s2); - } -} - -// ============================================================================ -str str::strip (char c) { - return strip ({c}); -} - -str str::strip (std::initializer_list<char> unwanted) { - str cache = text; - uint oldlen = len(); - - char* buf = new char[oldlen]; - char* bufptr = buf; - for (uint i = 0; i < oldlen; i++) { - bool valid = true; - for (const char* j = unwanted.begin(); j < unwanted.end() && valid; j++) - if (text[i] == *j) - valid = false; - - if (valid) - *bufptr++ = text[i]; - } - - *bufptr = '\0'; - assert (bufptr <= buf + oldlen); - - str zResult = buf; - delete[] buf; - - return zResult; -} - -void str::insert (char* c, unsigned int pos) { - str s1 = substr (0, pos); - str s2 = substr (pos, len()); - - clear(); - append (s1); - append (c); - append (s2); -} - -str str::reverse () { - char* buf = new char[len() + 1]; - - for (uint i = 0; i < len(); i++) - buf[i] = text[len() - i - 1]; - buf[len()] = '\0'; - - str other = buf; - delete[] buf; - return other; -} - -str str::repeat (int n) { - assert (n >= 0); - - str other; - for (int i = 0; i < n; i++) - other += text; - return other; -} - -// ============================================================================ -bool str::isnumber () { - ITERATE_STRING (u) { - // Minus sign as the first character is allowed for negatives - if (!u && text[u] == '-') - continue; - - if (text[u] < '0' || text[u] > '9') - return false; - } - return true; -} - -// ============================================================================ -bool str::isword () { - ITERATE_STRING (u) { - // lowercase letters - if (text[u] >= 'a' || text[u] <= 'z') - continue; - - // uppercase letters - if (text[u] >= 'A' || text[u] <= 'Z') - continue; - - return false; - } - return true; -} - -int str::instanceof (const char* c, uint n) { - unsigned int r = 0; - unsigned int index = 0; - unsigned int x = 0; - for (uint a = 0; a < alloclen; a++) { - if (text[a] == c[r]) { - if (r == 0) - index = a; - - r++; - if (r == strlen (c)) { - if (x++ == n) - return index; - r = 0; - } - } else { - if (r != 0) - a--; - r = 0; - } - } - - return -1; -} - -// ============================================================================ -int str::compare (const char* c) { - return strcmp (text, c); -} - -int str::compare (str c) { - return compare (c.chars()); -} - -int str::icompare (const char* c) { - return icompare (str ((char*)c)); -} - -int str::icompare (str b) { - return strcmp (tolower().chars(), b.tolower().chars()); -} - -// ============================================================================ -str str::tolower () { - str n = text; - - for (uint u = 0; u < len(); u++) { - if (n[u] >= 'A' && n[u] < 'Z') - n.text[u] += ('a' - 'A'); - } - - return n; -} - -// ============================================================================ -str str::toupper () { - str n = text; - - for (uint u = 0; u < len(); u++) { - if (n[u] >= 'a' && n[u] < 'z') - n.text[u] -= ('a' - 'A'); - } - - return n; -} - -// ============================================================================ -unsigned str::count (char c) { - unsigned n = 0; - ITERATE_STRING (u) - if (text[u] == c) - n++; - return n; -} - -unsigned str::count (char* c) { - unsigned int r = 0; - unsigned int tmp = 0; - ITERATE_STRING (u) { - if (text[u] == c[r]) { - r++; - if (r == strlen (c)) { - r = 0; - tmp++; - } - } else { - if (r != 0) - u--; - r = 0; - } - } - - return tmp; -} - -// ============================================================================ -std::vector<str> str::split (str del, bool bNoBlanks) { - std::vector<str> res; - unsigned int a = 0; - - // Find all separators and store the text left to them. - while (1) { - int b = first (del, a); - - if (b == -1) - break; - - if (!bNoBlanks || (b - a)) - res.push_back (substr (a, b)); - - a = b + strlen (del); - } - - // Add the string at the right of the last separator - if (!bNoBlanks || (len () - a)) - res.push_back (substr (a, len ())); - return res; -} - -std::vector<str> str::operator/ (str splitstring) {return split(splitstring);} -std::vector<str> str::operator/ (char* splitstring) {return split(splitstring);} -std::vector<str> str::operator/ (const char* splitstring) {return split(splitstring);} - -str& str::operator+= (vertex vrt) { - appendformat ("%s", vrt.stringRep (false).chars()); - return *this; -} - -str fmt (const char* fmt, ...) { - va_list va; - char* buf; - - va_start (va, fmt); - buf = vdynformat (fmt, va, 256); - va_end (va); - - str val = buf; - delete[] buf; - return val; -} \ No newline at end of file
--- a/str.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,271 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef STR_H -#define STR_H - -#include <string.h> -#include <stdlib.h> -#include <stdarg.h> -#include <vector> -#include <QString> - -char* vdynformat (const char* csFormat, va_list vArgs, long int lSize); - -class vertex; - -// Dynamic string object, allocates memory when needed and -// features a good bunch of manipulation methods -class str { -private: - // The actual message - char* text; - - // Where will append() place new characters? - unsigned short curs; - - // Allocated length - unsigned short alloclen; - - // Resize the text buffer to len characters - void resize (unsigned int len); - -public: - // ====================================================================== - str (); - str (const char* c); - str (char c); - str (QString c); - ~str (); - - // ====================================================================== - // METHODS - - // Empty the string - void clear (); - - // Length of the string - size_t len () { - return strlen (text); - } - - // The char* form of the string - char* chars (); - - // Dumps the character table of the string - void dump (); - - // Appends text to the string - void append (const char c); - void append (const char* c); - void append (str c); - void append (QString c); - - // Formats text to the string. - void format (const char* fmt, ...); - - // Appends formatted text to the string. - void appendformat (const char* c, ...); - - // Returns the first occurrence of c in the string, optionally starting - // from a certain position rather than the start. - int first (const char* c, unsigned int a = 0); - - // Returns the last occurrence of c in the string, optionally starting - // from a certain position rather than the end. - int last (const char* c, int a = -1); - - // Returns a substring of the string, from a to b. - str substr (unsigned int a, unsigned int b); - - // Replace a substring with another substring. - void replace (const char* o, const char* n, unsigned int a = 0); - - // Removes a given index from the string, optionally more characters than just 1. - void remove (unsigned int idx, unsigned int dellen=1); - - // Trims the given amount of characters. If negative, the characters - // are removed from the beginning of the string, if positive, from the - // end of the string. - str trim (int dellen); - - // Inserts a substring into a certain position. - void insert (char* c, unsigned int pos); - - // Reverses the string. - str reverse (); - - // Repeats the string a given amount of times. - str repeat (int n); - - // Is the string a number? - bool isnumber (); - - // Is the string a word, i.e consists only of alphabetic letters? - bool isword (); - - // Convert string to lower case - str tolower (); - - // Convert string to upper case - str toupper (); - - // Compare this string with another - int compare (const char* c); - int compare (str c); - int icompare (str c); - int icompare (const char* c); - - // Counts the amount of substrings in the string - unsigned int count (char* s); - unsigned int count (char s); - - // Counts where the given substring is seen for the nth time - int instanceof (const char* s, unsigned n); - - char subscript (uint pos) { - return operator[] (pos); - } - - std::vector<str> split (str del, bool bNoBlanks = false); - - str strip (char c); - str strip (std::initializer_list<char> unwanted); - - // ====================================================================== - // OPERATORS - str operator+ (str& c) { - append (c); - return *this; - } - - str& operator+= (char c) { - append (c); - return *this; - } - - str& operator+= (const char* c) { - append (c); - return *this; - } - - str& operator+= (const str c) { - append (c); - return *this; - } - - str& operator+= (const QString c) { - append (c); - return *this; - } - - str& operator+= (vertex vrt); - - str operator* (const int repcount) { - repeat (repcount); - return *this; - } - - str& operator*= (const int repcount) { - str other = repeat (repcount); - clear (); - append (other); - return *this; - } - - str operator- (const int trimcount) { - return trim (trimcount); - } - - str& operator-= (const int trimcount) { - str other = trim (trimcount); - clear (); - append (other); - return *this; - } - - std::vector<str> operator/ (str splitstring); - std::vector<str> operator/ (char* splitstring); - std::vector<str> operator/ (const char* splitstring); - - int operator% (str splitstring) { - return count (splitstring.chars()); - } - - int operator% (char* splitstring) { - return count (splitstring); - } - - int operator% (const char* splitstring) { - return count (str (splitstring).chars()); - } - - str operator+ () { - return toupper (); - } - - str operator- () { - return tolower (); - } - - str operator! () { - return reverse (); - } - - size_t operator~ () { - return len (); - } - -#define DEFINE_OPERATOR_TYPE(OPER, TYPE) \ - bool operator OPER (TYPE other) {return compare(other) OPER 0;} -#define DEFINE_OPERATOR(OPER) \ - DEFINE_OPERATOR_TYPE (OPER, str) \ - DEFINE_OPERATOR_TYPE (OPER, char*) \ - DEFINE_OPERATOR_TYPE (OPER, const char*) - - DEFINE_OPERATOR (==) - DEFINE_OPERATOR (!=) - DEFINE_OPERATOR (>) - DEFINE_OPERATOR (<) - DEFINE_OPERATOR (>=) - DEFINE_OPERATOR (<=) - - char& operator[] (int pos) { - return text[pos]; - } - - operator char* () const { - return text; - } - - operator QString () const { - return text; - } - - operator int () const { - return atoi (text); - } - - operator uint () const { - return operator int(); - } -}; - -str fmt (const char* fmt, ...); - -#endif // STR_H
--- a/types.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -#include <assert.h> -#include "common.h" -#include "types.h" -#include "misc.h" - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -vertex vertex::midpoint (vertex& other) { - vertex mid; - - for (const Axis ax : g_Axes) - mid[ax] = (m_coords[ax] + other[ax]) / 2; - - return mid; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -str vertex::stringRep (const bool mangled) { - return fmt (mangled ? "(%s, %s, %s)" : "%s %s %s", - ftoa (coord (X)).chars(), - ftoa (coord (Y)).chars(), - ftoa (coord (Z)).chars()); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void vertex::transform (matrix<3> matr, vertex pos) { - double x2 = (matr[0] * x ()) + (matr[1] * y ()) + (matr[2] * z ()) + pos[X]; - double y2 = (matr[3] * x ()) + (matr[4] * y ()) + (matr[5] * z ()) + pos[Y]; - double z2 = (matr[6] * x ()) + (matr[7] * y ()) + (matr[8] * z ()) + pos[Z]; - - x () = x2; - y () = y2; - z () = z2; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void vertex::transform (matrix<4> matr) { - double x2 = (matr[0] * x ()) + (matr[4] + y ()) + (matr[8] + z ()) + matr[12]; - double y2 = (matr[1] * x ()) + (matr[5] + y ()) + (matr[9] + z ()) + matr[13]; - double z2 = (matr[2] * x ()) + (matr[6] + y ()) + (matr[10] + z ()) + matr[14]; - double w2 = (matr[3] * x ()) + (matr[7] + y ()) + (matr[11] + z ()) + matr[15]; - - x () = x2 / w2; - y () = y2 / w2; - z () = z2 / w2; -} \ No newline at end of file
--- a/types.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,222 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef TYPES_H -#define TYPES_H - -#include "common.h" -#include "misc.h" - -typedef unsigned int uint; -typedef short unsigned int ushort; -typedef long unsigned int ulong; - -// Typedef out the _t suffices :) -typedef int8_t int8; -typedef int16_t int16; -typedef int32_t int32; -typedef int64_t int64; -typedef uint8_t uint8; -typedef uint16_t uint16; -typedef uint32_t uint32; -typedef uint64_t uint64; - -template<class T> using initlist = std::initializer_list<T>; -using std::vector; - -enum Axis { X, Y, Z }; -static const Axis g_Axes[3] = {X, Y, Z}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -// matrix -// -// A templated, mathematical N x N matrix -// ============================================================================= -template<int N> class matrix { -public: - // Constructors - matrix () {} - matrix (initlist<double> vals) { - assert (vals.size() == N * N); - memcpy (&m_vals[0], &(*vals.begin ()), sizeof m_vals); - } - - matrix (double fillval) { - for (short i = 0; i < (N * N); ++i) - m_vals[i] = fillval; - } - - matrix (double vals[]) { - for (short i = 0; i < (N * N); ++i) - m_vals[i] = vals[i]; - } - - template<int M> matrix (matrix<M> other) { - assert (M >= N); - - for (short i = 0; i < M; ++i) - for (short j = 0; j < M; ++j) { - const short idx = (i * M) + j; - m_vals[idx] = other[idx]; - } - } - - matrix<N> mult (matrix<N> other) { - matrix val; - val.zero (); - - for (short i = 0; i < N; ++i) - for (short j = 0; j < N; ++j) - for (short k = 0; k < N; ++k) - val[(i * N) + j] += m_vals[(i * N) + k] * other[(k * N) + j]; - - return val; - } - - matrix<N>& operator= (matrix<N> other) { - memcpy (&m_vals[0], &other.m_vals[0], sizeof (double) * N * N); - return *this; - } - - matrix<N> operator* (matrix<N> other) { - return mult (other); - } - - double& operator[] (const uint idx) { - return m_vals[idx]; - } - - const double& operator[] (const uint idx) const { - return m_vals[idx]; - } - - void zero () { - memset (&m_vals[0], 0, sizeof (double) * N * N); - } - - void puts () const { - for (short i = 0; i < N; ++i) { - for (short j = 0; j < N; ++j) - printf ("%*f\t", 10, m_vals[(i * N) + j]); - - printf ("\n"); - } - } - - str stringRep () const { - str val; - for (short i = 0; i < N * N; ++i) { - if (i > 0) - val += ' '; - - val.appendformat ("%s", ftoa (m_vals[i]).chars()); - } - - return val; - } - -private: - double m_vals[N * N]; -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -// vertex -// -// Vertex class, contains a single point in 3D space. Not to be confused with -// LDVertex, which is a vertex used in an LDraw part file. -// ============================================================================= -class vertex { -public: - double m_coords[3]; - - vertex () {} - vertex (double x, double y, double z) { - m_coords[X] = x; - m_coords[Y] = y; - m_coords[Z] = z; - } - - void move (vertex other) { - for (const Axis ax : g_Axes) - m_coords[ax] += other[ax]; - } - - vertex& operator+= (vertex other) { - move (other); - return *this; - } - - vertex operator/ (const double d) const { - vertex other (*this); - return other /= d; - } - - vertex& operator/= (const double d) { - for (const Axis ax : g_Axes) - m_coords[ax] /= d; - return *this; - } - - bool operator== (const vertex& other) const { - return coord (X) == other[X] && - coord (Y) == other[Y] && - coord (Z) == other[Z]; - } - - bool operator!= (const vertex& other) const { - return !operator== (other); - } - - vertex operator- () const { - return vertex (-m_coords[X], -m_coords[Y], -m_coords[Z]); - } - - double& coord (const ushort n) { - return m_coords[n]; - } - - const double& coord (const ushort n) const { - return m_coords[n]; - } - - double& operator[] (const Axis ax) { - return coord ((ushort) ax); - } - - const double& operator[] (const Axis ax) const { - return coord ((ushort) ax); - } - - double& x () { return m_coords[X]; } - double& y () { return m_coords[Y]; } - double& z () { return m_coords[Z]; } - const double& x () const { return m_coords[X]; } - const double& y () const { return m_coords[Y]; } - const double& z () const { return m_coords[Z]; } - - vertex midpoint (vertex& other); - str stringRep (const bool mangled); - void transform (matrix<3> matr, vertex pos); - void transform (matrix<4> matr); -}; - -#endif // TYPES_H \ No newline at end of file
--- a/zz_aboutDialog.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,156 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <stdlib.h> -#include <qlabel.h> -#include <qboxlayout.h> -#include <qdialogbuttonbox.h> -#include <qdesktopservices.h> -#include <qurl.h> -#include "common.h" -#include "zz_aboutDialog.h" - -AboutDialog::AboutDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) { - QWidget* mainTab, *licenseTab; - QTabWidget* tabs = new QTabWidget; - - { - mainTab = new QWidget; - - // Application icon - in full 64 x 64 glory. - QLabel* icon = new QLabel; - icon->setPixmap (getIcon ("ldforge")); - - // Heading - application label and copyright information - QLabel* title = new QLabel (fmt ("<b>" APPNAME " v%d.%d</b><br />" - "Copyright (C) 2013 Santeri Piippo", - VERSION_MAJOR, VERSION_MINOR)); - - // Body text - QLabel* info = new QLabel ( - "<p>This software is intended for usage as a parts<br />" - "authoring tool for the <a href=\"http://ldraw.org/\">LDraw</a> parts library.</p>" - - "<p>" APPNAME " is free software, and you are welcome<br />" - "to redistribute it under the terms of GPL v3. See the LICENSE<br />" - "text file or the license tab in this dialog for details. If the<br />" - "license text is not available for some reason, see<br />" - "<a href=\"http://www.gnu.org/licenses/\">http://www.gnu.org/licenses/</a>" - "for the license terms.</p>" - - "<p>The application icon is derived from " - "<a href=\"http://en.wikipedia.org/wiki/File:Anvil,_labelled_en.svg\">this image</a>.</p>" - ); - - // Rest in peace, James. - QLabel* memorial = new QLabel ("In living memory of James Jessiman."); - - QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (icon); - layout->addWidget (title); - layout->addWidget (info); - layout->addWidget (memorial); - - // Align everything to the center. - for (QLabel* label : vector<QLabel*> ({icon, title, info, memorial})) - label->setAlignment (Qt::AlignCenter); - - mainTab->setLayout (layout); - tabs->addTab (mainTab, "About " APPNAME); - } - - { - licenseTab = new QWidget; - - QTextEdit* license = new QTextEdit; - license->setReadOnly (true); - - QFont font ("Monospace"); - font.setStyleHint (QFont::TypeWriter); - font.setPixelSize (10); - - license->setFont (font); - - // Make the text view wide enough to display the license text. - // Why isn't 80 sufficient here? - license->setMinimumWidth (license->fontMetrics ().width ('a') * 85); - - // Try open the license text - FILE* fp = fopen ("LICENSE", "r"); - - if (fp == null) { - // Failed; tell the user how to get the license text instead. - setlocale (LC_ALL, "C"); - char const* text = "Couldn't open LICENSE: %s.<br />" - "See <a href=\"http://www.gnu.org/licenses/\">http://www.gnu.org/licenses/</a> for the GPLv3 text."; - - license->setHtml (fmt (text, strerror (errno))); - } else { - // Figure out file size - fseek (fp, 0, SEEK_END); - const size_t length = ftell (fp); - rewind (fp); - - // Init text buffer and write pointer - char* licenseText = new char[length]; - char* writePtr = &licenseText[0]; - - // Read in the license text - while (true) { - *writePtr = fgetc (fp); - - if (feof (fp)) - break; - - writePtr++; - } - - // Add terminating null character and add the license text to the - // license dialog text view. - *writePtr = '\0'; - license->setText (licenseText); - - // And dump the trash on the way out. - delete[] licenseText; - } - - QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (license); - licenseTab->setLayout (layout); - tabs->addTab (licenseTab, "License"); - } - - QDialogButtonBox* buttons = new QDialogButtonBox (QDialogButtonBox::Close); - QPushButton* helpButton = new QPushButton; - - helpButton->setText ("Mail Author"); - helpButton->setIcon (getIcon ("mail")); - buttons->addButton (static_cast<QAbstractButton*> (helpButton), QDialogButtonBox::HelpRole); - connect (buttons, SIGNAL (helpRequested ()), this, SLOT (slot_mail ())); - connect (buttons, SIGNAL (rejected ()), this, SLOT (reject ())); - - QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (tabs); - layout->addWidget (buttons); - setLayout (layout); - setWindowTitle ("About " APPNAME); -} - -void AboutDialog::slot_mail () { - QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <arezey@gmail.com>?subject=LDForge")); -} \ No newline at end of file
--- a/zz_aboutDialog.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef ZZ_ABOUTDIALOG_H -#define ZZ_ABOUTDIALOG_H - -#include <qdialog.h> -#include "gui.h" - -class AboutDialog : public QDialog { - Q_OBJECT - -public: - AboutDialog (QWidget* parent = nullptr, Qt::WindowFlags f = 0); - QPushButton* pb_mailAuthor; - -private slots: - void slot_mail (); -}; - -#endif // ZZ_ABOUTDIALOG_H \ No newline at end of file
--- a/zz_addObjectDialog.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,511 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <qgridlayout.h> -#include <qradiobutton.h> -#include <qcheckbox.h> -#include "gui.h" -#include "zz_addObjectDialog.h" -#include "file.h" -#include "colors.h" -#include "zz_colorSelectDialog.h" -#include "history.h" -#include "zz_setContentsDialog.h" - -#define APPLY_COORDS(OBJ, N) \ - for (short i = 0; i < N; ++i) \ - for (const Axis ax : g_Axes) \ - OBJ->vaCoords[i][ax] = dlg.dsb_coords[(i * 3) + ax]->value (); - -// ============================================================================= -class SubfileListItem : public QTreeWidgetItem { -public: - SubfileListItem (QTreeWidgetItem* parent, int subfileID) : - QTreeWidgetItem (parent), subfileID (subfileID) {} - SubfileListItem (QTreeWidget* parent, int subfileID) : - QTreeWidgetItem (parent), subfileID (subfileID) {} - - int subfileID; -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -AddObjectDialog::AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent) : - QDialog (parent) -{ - setlocale (LC_ALL, "C"); - - short coordCount = 0; - - switch (type) { - case LDObject::Comment: - le_comment = new QLineEdit; - if (obj) - le_comment->setText (static_cast<LDComment*> (obj)->text); - break; - - case LDObject::Line: - coordCount = 6; - break; - - case LDObject::Triangle: - coordCount = 9; - break; - - case LDObject::Quad: - case LDObject::CondLine: - coordCount = 12; - break; - - case LDObject::Vertex: - coordCount = 3; - break; - - case LDObject::BFC: - rb_bfcType = new RadioBox ("Statement", {}, 0, Qt::Vertical); - - for (int i = 0; i < LDBFC::NumStatements; ++i) - rb_bfcType->addButton (new QRadioButton (LDBFC::statements[i])); - - if (obj) - rb_bfcType->setValue ((int) static_cast<LDBFC*> (obj)->type); - break; - - case LDObject::Subfile: - coordCount = 3; - - enum { - Parts, - Subparts, - Primitives, - HiRes, - }; - - tw_subfileList = new QTreeWidget (); - for (int i : vector<int> ({Parts, Subparts, Primitives, HiRes})) { - SubfileListItem* parentItem = new SubfileListItem (tw_subfileList, -1); - parentItem->setText (0, (i == Parts) ? "Parts" : - (i == Subparts) ? "Subparts" : - (i == Primitives) ? "Primitives" : - "Hi-Res"); - - ulong j = 0; - for (partListEntry& part : g_PartList) { - QList<QTreeWidgetItem*> subfileItems; - - str fileName = part.sName; - const bool isSubpart = fileName.substr (0, 2) == "s\\"; - const bool isPrimitive = str (part.sTitle).substr (0, 9) == "Primitive"; - const bool isHiRes = fileName.substr (0, 3) == "48\\"; - - if ((i == Subparts && isSubpart) || - (i == Primitives && isPrimitive) || - (i == HiRes && isHiRes) || - (i == Parts && !isSubpart && !isPrimitive && !isHiRes)) - { - SubfileListItem* item = new SubfileListItem (parentItem, j); - item->setText (0, fmt ("%s - %s", part.sName, part.sTitle)); - subfileItems.append (item); - } - - j++; - } - - tw_subfileList->addTopLevelItem (parentItem); - } - - connect (tw_subfileList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_subfileTypeChanged ())); - lb_subfileName = new QLabel ("File:"); - le_subfileName = new QLineEdit; - le_subfileName->setFocus (); - - if (obj) { - LDSubfile* ref = static_cast<LDSubfile*> (obj); - le_subfileName->setText (ref->zFileName); - } - break; - - case LDObject::Radial: - coordCount = 3; - - lb_radType = new QLabel ("Type:"); - lb_radResolution = new QLabel ("Resolution:"); - lb_radSegments = new QLabel ("Segments:"); - lb_radRingNum = new QLabel ("Ring number:"); - - rb_radType = new RadioBox ("Type", {}, 0, Qt::Vertical); - - for (int i = 0; i < LDRadial::NumTypes; ++i) { - if (i % (LDRadial::NumTypes / 2) == 0) - rb_radType->rowBreak (); - - rb_radType->addButton (new QRadioButton (LDRadial::radialTypeName ((LDRadial::Type) i))); - } - - connect (rb_radType, SIGNAL (sig_buttonPressed (int)), this, SLOT (slot_radialTypeChanged (int))); - - cb_radHiRes = new QCheckBox ("Hi-Res"); - - sb_radSegments = new QSpinBox; - sb_radSegments->setMinimum (1); - - sb_radRingNum = new QSpinBox; - sb_radRingNum->setEnabled (false); - - if (obj) { - LDRadial* rad = static_cast<LDRadial*> (obj); - - rb_radType->setValue (rad->eRadialType); - sb_radSegments->setValue (rad->dSegments); - cb_radHiRes->setChecked ((rad->dDivisions == 48) ? Qt::Checked : Qt::Unchecked); - sb_radRingNum->setValue (rad->dRingNum); - } - break; - - default: - assert (false); - return; - } - - QPixmap icon = getIcon (fmt ("add-%s", g_saObjTypeIcons[type])); - LDObject* defaults = LDObject::getDefault (type); - - lb_typeIcon = new QLabel; - lb_typeIcon->setPixmap (icon); - - // Show a color edit dialog for the types that actually use the color - if (defaults->isColored ()) { - if (obj != null) - dColor = obj->dColor; - else - dColor = (type == LDObject::CondLine || type == LDObject::Line) ? edgecolor : maincolor; - - pb_color = new QPushButton; - setButtonBackground (pb_color, dColor); - connect (pb_color, SIGNAL (clicked ()), this, SLOT (slot_colorButtonClicked ())); - } - - for (short i = 0; i < coordCount; ++i) { - dsb_coords[i] = new QDoubleSpinBox; - dsb_coords[i]->setDecimals (5); - dsb_coords[i]->setMinimum (-10000.0); - dsb_coords[i]->setMaximum (10000.0); - } - - IMPLEMENT_DIALOG_BUTTONS - - QGridLayout* const layout = new QGridLayout; - layout->addWidget (lb_typeIcon, 0, 0); - - switch (type) { - case LDObject::Line: - case LDObject::CondLine: - case LDObject::Triangle: - case LDObject::Quad: - // Apply coordinates - if (obj) { - for (short i = 0; i < coordCount / 3; ++i) - for (short j = 0; j < 3; ++j) - dsb_coords[(i * 3) + j]->setValue (obj->vaCoords[i].coord (j)); - } - break; - - case LDObject::Comment: - layout->addWidget (le_comment, 0, 1); - break; - - case LDObject::BFC: - layout->addWidget (rb_bfcType, 0, 1); - break; - - case LDObject::Radial: - layout->addWidget (rb_radType, 1, 1, 3, 2); - layout->addWidget (cb_radHiRes, 1, 3); - layout->addWidget (lb_radSegments, 2, 3); - layout->addWidget (sb_radSegments, 2, 4); - layout->addWidget (lb_radRingNum, 3, 3); - layout->addWidget (sb_radRingNum, 3, 4); - - if (obj) - for (short i = 0; i < 3; ++i) - dsb_coords[i]->setValue (static_cast<LDRadial*> (obj)->vPosition.coord (i)); - break; - - case LDObject::Subfile: - layout->addWidget (tw_subfileList, 1, 1, 1, 2); - layout->addWidget (lb_subfileName, 2, 1); - layout->addWidget (le_subfileName, 2, 2); - - if (obj) - for (short i = 0; i < 3; ++i) - dsb_coords[i]->setValue (static_cast<LDSubfile*> (obj)->vPosition.coord (i)); - break; - - default: - break; - } - - if (type == LDObject::Subfile || type == LDObject::Radial) { - QLabel* lb_matrix = new QLabel ("Matrix:"); - le_matrix = new QLineEdit; - // le_matrix->setValidator (new QDoubleValidator); - matrix<3> defval = g_identity; - - if (obj) { - if (obj->getType () == LDObject::Subfile) - defval = static_cast<LDSubfile*> (obj)->mMatrix; - else - defval = static_cast<LDRadial*> (obj)->mMatrix; - } - - le_matrix->setText (defval.stringRep ()); - layout->addWidget (lb_matrix, 4, 1); - layout->addWidget (le_matrix, 4, 2, 1, 3); - } - - if (defaults->isColored ()) - layout->addWidget (pb_color, 1, 0); - - if (coordCount > 0) { - QGridLayout* const qCoordLayout = new QGridLayout; - - for (short i = 0; i < coordCount; ++i) - qCoordLayout->addWidget (dsb_coords[i], (i / 3), (i % 3)); - - layout->addLayout (qCoordLayout, 0, 1, (coordCount / 3), 3); - } - - layout->addWidget (bbx_buttons, 5, 0, 1, 4); - setLayout (layout); - setWindowTitle (fmt (APPNAME ": New %s", - g_saObjTypeNames[type]).chars()); - - setWindowIcon (icon); - delete defaults; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void AddObjectDialog::setButtonBackground (QPushButton* button, short color) { - button->setIcon (getIcon ("palette")); - button->setAutoFillBackground (true); - button->setStyleSheet ( - fmt ("background-color: %s", getColor (color)->zColorString.chars()).chars() - ); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -char* AddObjectDialog::currentSubfileName() { - SubfileListItem* item = static_cast<SubfileListItem*> (tw_subfileList->currentItem ()); - - if (item->subfileID == -1) - return null; // selected a heading - - return g_PartList[item->subfileID].sName; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void AddObjectDialog::slot_colorButtonClicked () { - ColorSelectDialog::staticDialog (dColor, dColor, this); - setButtonBackground (pb_color, dColor); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void AddObjectDialog::slot_radialTypeChanged (int dType) { - LDRadial::Type eType = (LDRadial::Type) dType; - sb_radRingNum->setEnabled (eType == LDRadial::Ring || eType == LDRadial::Cone); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void AddObjectDialog::slot_subfileTypeChanged () { - char* name = currentSubfileName (); - - if (name) - le_subfileName->setText (name); -} - -// ============================================================================= -template<class T> T* initObj (LDObject*& obj) { - if (obj == null) - obj = new T; - - return static_cast<T*> (obj); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void AddObjectDialog::staticDialog (const LDObject::Type type, LDObject* obj) { - setlocale (LC_ALL, "C"); - - // Redirect editing of gibberish to the set contents dialog - if (obj && obj->getType () == LDObject::Gibberish) { - SetContentsDialog::staticDialog (obj); - return; - } - - if (type == LDObject::Empty) - return; // Nothing to edit with empties - - const bool newObject = (obj == null); - AddObjectDialog dlg (type, obj); - - if (obj) - assert (obj->getType () == type); - - if (dlg.exec () == false) - return; - - LDObject* backup = null; - if (!newObject) - backup = obj->clone (); - - matrix<3> transform = g_identity; - if (type == LDObject::Subfile || type == LDObject::Radial) { - vector<str> matrixstrvals = str (dlg.le_matrix->text ()).split (" ", true); - - if (matrixstrvals.size () == 9) { - double matrixvals[9]; - int i = 0; - - for (str val : matrixstrvals) - matrixvals[i++] = atof (val); - - transform = matrix<3> (matrixvals); - } - } - - switch (type) { - case LDObject::Comment: - { - LDComment* comm = initObj<LDComment> (obj); - comm->text = dlg.le_comment->text (); - } - break; - - case LDObject::Line: - { - LDLine* line = initObj<LDLine> (obj); - line->dColor = dlg.dColor; - APPLY_COORDS (line, 2) - } - break; - - case LDObject::Triangle: - { - LDTriangle* tri = initObj<LDTriangle> (obj); - tri->dColor = dlg.dColor; - APPLY_COORDS (tri, 3) - } - break; - - case LDObject::Quad: - { - LDQuad* quad = initObj<LDQuad> (obj); - quad->dColor = dlg.dColor; - APPLY_COORDS (quad, 4) - } - break; - - case LDObject::CondLine: - { - LDCondLine* line = initObj<LDCondLine> (obj); - line->dColor = dlg.dColor; - APPLY_COORDS (line, 4) - } - break; - - case LDObject::BFC: - { - LDBFC* bfc = initObj<LDBFC> (obj); - bfc->type = (LDBFC::Type) dlg.rb_bfcType->value (); - } - break; - - case LDObject::Vertex: - { - LDVertex* vert = initObj<LDVertex> (obj); - vert->dColor = dlg.dColor; - - for (const Axis ax : g_Axes) - vert->vPosition[ax] = dlg.dsb_coords[ax]->value (); - } - break; - - case LDObject::Radial: - { - LDRadial* pRad = initObj<LDRadial> (obj); - pRad->dColor = dlg.dColor; - - for (const Axis ax : g_Axes) - pRad->vPosition[ax] = dlg.dsb_coords[ax]->value (); - - pRad->dDivisions = (dlg.cb_radHiRes->checkState () != Qt::Checked) ? 16 : 48; - pRad->dSegments = min<short> (dlg.sb_radSegments->value (), pRad->dDivisions); - pRad->eRadialType = (LDRadial::Type) dlg.rb_radType->value (); - pRad->dRingNum = dlg.sb_radRingNum->value (); - pRad->mMatrix = transform; - } - break; - - case LDObject::Subfile: - { - str name = dlg.le_subfileName->text (); - if (~name == 0) - return; // no subfile filename - - OpenFile* file = loadSubfile (name); - if (!file) - return; - - LDSubfile* ref = initObj<LDSubfile> (obj); - ref->dColor = dlg.dColor; - - for (const Axis ax : g_Axes) - ref->vPosition[ax] = dlg.dsb_coords[ax]->value (); - - ref->zFileName = name; - ref->mMatrix = transform; - ref->pFile = file; - } - break; - - default: - break; - } - - if (newObject) { - ulong idx = g_win->getInsertionPoint (); - g_curfile->insertObj (idx, obj); - History::addEntry (new AddHistory ({(ulong) idx}, {obj->clone ()})); - } else { - History::addEntry (new EditHistory ({(ulong) obj->getIndex (g_curfile)}, {backup}, {obj->clone ()})); - } - - g_win->refresh (); -} \ No newline at end of file
--- a/zz_addObjectDialog.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef ZZ_ADDOBJECTDIALOG_H -#define ZZ_ADDOBJECTDIALOG_H - -#include "gui.h" -#include "radiobox.h" -#include <qdialog.h> -#include <qlineedit.h> -#include <qdialogbuttonbox.h> -#include <qcheckbox.h> -#include <qspinbox.h> -#include <qlabel.h> -#include <qradiobutton.h> -#include <qlistwidget.h> -#include <qtreewidget.h> - -class AddObjectDialog : public QDialog { - Q_OBJECT - -public: - AddObjectDialog (const LDObject::Type type, LDObject* obj, QWidget* parent = null); - static void staticDialog (const LDObject::Type type, LDObject* obj); - - QLabel* lb_typeIcon; - - // Comment line edit - QLineEdit* le_comment; - - // Coordinate edits for.. anything with coordinates, really. - QDoubleSpinBox* dsb_coords[12]; - - // Color selection dialog button - QPushButton* pb_color; - - // BFC-related widgets - RadioBox* rb_bfcType; - - // Subfile stuff - QTreeWidget* tw_subfileList; - QLineEdit* le_subfileName; - QLabel* lb_subfileName; - - // Radial stuff - QCheckBox* cb_radHiRes; - RadioBox* rb_radType; - QSpinBox* sb_radSegments, *sb_radRingNum; - QLabel* lb_radType, *lb_radResolution, *lb_radSegments, - *lb_radRingNum; - - QLineEdit* le_matrix; - - QDialogButtonBox* bbx_buttons; - -private: - void setButtonBackground (QPushButton* button, short color); - char* currentSubfileName (); - - short dColor; - -private slots: - void slot_colorButtonClicked (); - void slot_radialTypeChanged (int type); - void slot_subfileTypeChanged (); -}; - -#endif // ZZ_ADDOBJECTDIALOG_H \ No newline at end of file
--- a/zz_colorSelectDialog.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,179 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "common.h" -#include "gui.h" -#include <qgraphicsscene.h> -#include <qgraphicsview.h> -#include <qicon.h> -#include <qboxlayout.h> -#include <qgraphicsitem.h> -#include <qevent.h> -#include <qscrollbar.h> -#include "zz_colorSelectDialog.h" -#include "colors.h" -#include "config.h" -#include "misc.h" - -static const short g_dNumColumns = 8; -static const short g_dNumRows = 10; -static const short g_dSquareSize = 32; -static const long g_lWidth = (g_dNumColumns * g_dSquareSize); -static const long g_lHeight = (g_dNumRows * g_dSquareSize); -static const long g_lMaxHeight = ((MAX_COLORS / g_dNumColumns) * g_dSquareSize); - -extern_cfg (str, gl_maincolor); -extern_cfg (float, gl_maincolor_alpha); - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -ColorSelectDialog::ColorSelectDialog (short int defval, QWidget* parent) : QDialog (parent) { - // Remove the default color if it's invalid - if (!getColor (defval)) - defval = -1; - - gs_scene = new QGraphicsScene; - gv_view = new QGraphicsView (gs_scene); - selColor = defval; - - // not really an icon but eh - gs_scene->setBackgroundBrush (getIcon ("checkerboard")); - - gs_scene->setSceneRect (0, 0, g_lWidth, g_lMaxHeight); - gv_view->setSceneRect (0, 0, g_lWidth, g_lMaxHeight); - - drawScene (); - - IMPLEMENT_DIALOG_BUTTONS - - // Set the size of the view - const long lWidth = g_lWidth + 21; // HACK - gv_view->setMaximumWidth (lWidth); - gv_view->setMinimumWidth (lWidth); - gv_view->setMaximumHeight (g_lHeight); - gv_view->setMinimumHeight (g_lHeight); - gv_view->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff); - - // If we have a default color selected, scroll down so that it is visible. - // TODO: find a better way to do this - if (defval >= ((g_dNumColumns * g_dNumRows) - 2)) { - ulong ulNewY = ((defval / g_dNumColumns) - 3) * g_dSquareSize; - gv_view->verticalScrollBar ()->setSliderPosition (ulNewY); - } - - lb_colorInfo = new QLabel; - drawColorInfo (); - - QVBoxLayout* qLayout = new QVBoxLayout; - qLayout->addWidget (gv_view); - qLayout->addWidget (lb_colorInfo); - qLayout->addWidget (bbx_buttons); - setLayout (qLayout); - - setWindowIcon (getIcon ("palette")); - setWindowTitle (APPNAME); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ColorSelectDialog::drawScene () { - const double fPenWidth = 1.0f; - QPen qPen (Qt::black, fPenWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); - - // Draw the color rectangles. - gs_scene->clear (); - for (short i = 0; i < MAX_COLORS; ++i) { - color* meta = getColor (i); - if (!meta) - continue; - - const double x = (i % g_dNumColumns) * g_dSquareSize; - const double y = (i / g_dNumColumns) * g_dSquareSize; - const double w = (g_dSquareSize) - (fPenWidth / 2); - - QColor col = meta->qColor; - - if (i == maincolor) { - // Use the user preferences for main color here - col = gl_maincolor.value.chars (); - col.setAlpha (gl_maincolor_alpha * 255.0f); - } - - bool dark = (luma (col) < 80); - - gs_scene->addRect (x, y, w, w, qPen, col); - QGraphicsTextItem* qText = gs_scene->addText (fmt ("%lu", i).chars()); - qText->setDefaultTextColor ((dark) ? Qt::white : Qt::black); - qText->setPos (x, y); - - if (i == selColor) { - auto curspic = gs_scene->addPixmap (getIcon ("colorcursor")); - curspic->setPos (x, y); - } - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ColorSelectDialog::drawColorInfo () { - color* col = getColor (selColor); - - if (selColor == -1 || !col) { - lb_colorInfo->setText ("---"); - return; - } - - lb_colorInfo->setText (fmt ("%d - %s", - selColor, col->zName.chars())); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ColorSelectDialog::mousePressEvent (QMouseEvent* event) { - QPointF qPoint = gv_view->mapToScene (event->pos ()); - - ulong x = ((ulong)qPoint.x () - (g_dSquareSize / 2)) / g_dSquareSize; - ulong y = ((ulong)qPoint.y () - (g_dSquareSize / 2)) / g_dSquareSize; - ulong idx = (y * g_dNumColumns) + x; - - color* col = getColor (idx); - if (!col) - return; - - selColor = idx; - drawScene (); - drawColorInfo (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -bool ColorSelectDialog::staticDialog (short& val, short int defval, QWidget* parent) { - ColorSelectDialog dlg (defval, parent); - - if (dlg.exec () && dlg.selColor != -1) { - val = dlg.selColor; - return true; - } - - return false; -} \ No newline at end of file
--- a/zz_colorSelectDialog.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef COLORSELECTOR_H -#define COLORSELECTOR_H - -#include <qdialog.h> -#include <qdialogbuttonbox.h> -#include <qgraphicsscene.h> -#include <qlabel.h> -#include "common.h" - -class ColorSelectDialog : public QDialog { - Q_OBJECT - -public: - explicit ColorSelectDialog (short defval = -1, QWidget* parent = null); - static bool staticDialog (short& val, short defval = -1, QWidget* parent = null); - - QGraphicsScene* gs_scene; - QGraphicsView* gv_view; - QLabel* lb_colorInfo; - QDialogButtonBox* bbx_buttons; - short selColor; - -private: - void drawScene (); - void drawColorInfo (); - -private slots: - void mousePressEvent (QMouseEvent* event); -}; - -#endif // COLORSELECTOR_H \ No newline at end of file
--- a/zz_configDialog.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,796 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "common.h" -#include "zz_configDialog.h" -#include "file.h" -#include "config.h" -#include "misc.h" -#include "colors.h" -#include "zz_colorSelectDialog.h" -#include <qgridlayout.h> -#include <qfiledialog.h> -#include <qcolordialog.h> -#include <qboxlayout.h> -#include <qevent.h> -#include <qgroupbox.h> - -extern_cfg (str, gl_bgcolor); -extern_cfg (str, gl_maincolor); -extern_cfg (bool, lv_colorize); -extern_cfg (bool, gl_colorbfc); -extern_cfg (float, gl_maincolor_alpha); -extern_cfg (int, gl_linethickness); -extern_cfg (int, gui_toolbar_iconsize); -extern_cfg (str, gui_colortoolbar); -extern_cfg (bool, gl_selflash); -extern_cfg (bool, edit_schemanticinline); -extern_cfg (bool, gl_blackedges); - -ConfigDialog* g_ConfigDialog = null; - -#define INIT_CHECKBOX(BOX, CFG) \ - BOX->setCheckState (CFG ? Qt::Checked : Qt::Unchecked); - -#define APPLY_CHECKBOX(BTN, CFG) \ - CFG = BTN->checkState() == Qt::Checked; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -ConfigDialog::ConfigDialog (ForgeWindow* parent) : QDialog (parent) { - g_ConfigDialog = this; - tabs = new QTabWidget; - - initMainTab (); - initShortcutsTab (); - initQuickColorTab (); - initGridTab (); - initExtProgTab (); - - IMPLEMENT_DIALOG_BUTTONS - - QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (tabs); - layout->addWidget (bbx_buttons); - setLayout (layout); - - setWindowTitle ("Settings"); - setWindowIcon (getIcon ("settings")); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ConfigDialog::initMainTab () { - mainTab = new QWidget; - - // ========================================================================= - // Background and foreground colors - lb_viewBg = new QLabel ("Background color:"); - pb_viewBg = new QPushButton; - setButtonBackground (pb_viewBg, gl_bgcolor.value); - connect (pb_viewBg, SIGNAL (clicked ()), - this, SLOT (slot_setGLBackground ())); - pb_viewBg->setWhatsThis ("This is the background color for the viewport."); - - lb_viewFg = new QLabel ("Foreground color:"); - pb_viewFg = new QPushButton; - setButtonBackground (pb_viewFg, gl_maincolor.value); - connect (pb_viewFg, SIGNAL (clicked ()), - this, SLOT (slot_setGLForeground ())); - pb_viewFg->setWhatsThis ("This color is used for the main color."); - - // ========================================================================= - // Alpha and line thickness sliders - lb_viewFgAlpha = new QLabel ("Alpha:"); - makeSlider (sl_viewFgAlpha, 1, 10, (gl_maincolor_alpha * 10.0f)); - sl_viewFgAlpha->setWhatsThis ("Opacity of main color in the viewport."); - - lb_lineThickness = new QLabel ("Line thickness:"); - makeSlider (sl_lineThickness, 1, 8, gl_linethickness); - sl_lineThickness->setWhatsThis ("How thick lines should be drawn in the viewport."); - - // ========================================================================= - // Tool bar icon size slider - lb_iconSize = new QLabel ("Toolbar icon size:"); - makeSlider (sl_iconSize, 1, 5, (gui_toolbar_iconsize - 12) / 4); - - // ========================================================================= - // List view colorizer and BFC red/green view checkboxes - cb_colorize = new QCheckBox ("Colorize polygons in object list"); - cb_colorize->setChecked (lv_colorize); - cb_colorize->setWhatsThis ("Makes colored objects (non-16 and 24) appear " - "colored in the object list. A red polygon will have its description " - "written in red text."); - - cb_colorBFC = new QCheckBox ("Red/green BFC view"); - cb_colorBFC->setChecked (gl_colorbfc); - cb_colorBFC->setWhatsThis ("Polygons' front sides become green and back " - "sides red. Not implemented yet."); - - cb_selFlash = new QCheckBox ("Selection flash"); - cb_selFlash->setChecked (gl_selflash); - cb_colorBFC->setWhatsThis ("A pulse effect for clearer selection view."); - - cb_blackEdges = new QCheckBox ("Black edges"); - cb_blackEdges->setWhatsThis ("Makes all edgelines appear black. If this is " - "not set, edge lines take their color as defined in LDConfig.ldr"); - cb_blackEdges->setChecked (gl_blackedges); - - cb_schemanticInline = new QCheckBox ("Schemantic insertion only"); - cb_schemanticInline->setChecked (edit_schemanticinline); - cb_colorBFC->setWhatsThis ("When inserting objects through inlining, file " - "inserting or through external programs, all non-schemantics (those without " - "actual meaning in the part file like comments and such) are filtered out."); - - cb_schemanticInline->setEnabled (false); - cb_colorBFC->setEnabled (false); - - QGridLayout* layout = new QGridLayout; - layout->addWidget (lb_viewBg, 0, 0); - layout->addWidget (pb_viewBg, 0, 1); - layout->addWidget (lb_viewFg, 0, 2); - layout->addWidget (pb_viewFg, 0, 3); - - layout->addWidget (lb_lineThickness, 1, 0); - layout->addWidget (sl_lineThickness, 1, 1); - layout->addWidget (lb_viewFgAlpha, 1, 2); - layout->addWidget (sl_viewFgAlpha, 1, 3); - - layout->addWidget (lb_iconSize, 2, 0); - layout->addWidget (sl_iconSize, 2, 1); - - layout->addWidget (cb_colorize, 3, 0, 1, 4); - layout->addWidget (cb_colorBFC, 4, 0, 1, 4); - layout->addWidget (cb_selFlash, 5, 0, 1, 4); - layout->addWidget (cb_blackEdges, 6, 0, 1, 4); - layout->addWidget (cb_schemanticInline, 7, 0, 1, 4); - mainTab->setLayout (layout); - - // Add the tab to the manager - tabs->addTab (mainTab, "Main settings"); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ConfigDialog::initShortcutsTab () { - shortcutsTab = new QWidget; - lw_shortcutList = new QListWidget; - lw_shortcutList->setAlternatingRowColors (true); - - shortcutsTab->setWhatsThis ("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.\n" - "\tShortcut changes apply immediately after closing this dialog." ); - - // Init table items - ulong i = 0; - for (actionmeta& info : g_ActionMeta) { - QAction* const act = *info.qAct; - - ShortcutListItem* item = new ShortcutListItem; - setShortcutText (item, info); - item->setIcon (act->icon ()); - item->setActionInfo (&info); - - // 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")); - - lw_shortcutList->insertItem (i++, item); - } - - lw_shortcutList->setSortingEnabled (true); - lw_shortcutList->sortItems (); - - pb_setShortcut = new QPushButton ("Set"); - pb_resetShortcut = new QPushButton ("Reset"); - pb_clearShortcut = new QPushButton ("Clear"); - - connect (pb_setShortcut, SIGNAL (clicked ()), this, SLOT (slot_setShortcut ())); - connect (pb_resetShortcut, SIGNAL (clicked ()), this, SLOT (slot_resetShortcut ())); - connect (pb_clearShortcut, SIGNAL (clicked ()), this, SLOT (slot_clearShortcut ())); - - QVBoxLayout* buttonLayout = new QVBoxLayout; - buttonLayout->addWidget (pb_setShortcut); - buttonLayout->addWidget (pb_resetShortcut); - buttonLayout->addWidget (pb_clearShortcut); - buttonLayout->addStretch (10); - - QGridLayout* layout = new QGridLayout; - layout->addWidget (lw_shortcutList, 0, 0); - layout->addLayout (buttonLayout, 0, 1); - shortcutsTab->setLayout (layout); - tabs->addTab (shortcutsTab, "Shortcuts"); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ConfigDialog::initQuickColorTab () { - quickColorTab = new QWidget; - - pb_addColor = new QPushButton (getIcon ("palette"), "Add"); - pb_delColor = new QPushButton (getIcon ("delete"), "Remove"); - pb_changeColor = new QPushButton (getIcon ("palette"), "Set"); - pb_addColorSeparator = new QPushButton ("Add Separator"); - pb_moveColorUp = new QPushButton (getIcon ("arrow-up"), "Move Up"); - pb_moveColorDown = new QPushButton (getIcon ("arrow-down"), "Move Down"); - pb_clearColors = new QPushButton (getIcon ("delete-all"), "Clear"); - lw_quickColors = new QListWidget; - - quickColorMeta = parseQuickColorMeta (); - updateQuickColorList (); - - QVBoxLayout* buttonLayout = new QVBoxLayout; - buttonLayout->addWidget (pb_addColor); - buttonLayout->addWidget (pb_delColor); - buttonLayout->addWidget (pb_changeColor); - buttonLayout->addWidget (pb_addColorSeparator); - buttonLayout->addWidget (pb_moveColorUp); - buttonLayout->addWidget (pb_moveColorDown); - buttonLayout->addWidget (pb_clearColors); - buttonLayout->addStretch (1); - - connect (pb_addColor, SIGNAL (clicked ()), this, SLOT (slot_setColor ())); - connect (pb_delColor, SIGNAL (clicked ()), this, SLOT (slot_delColor ())); - connect (pb_changeColor, SIGNAL (clicked ()), this, SLOT (slot_setColor ())); - connect (pb_addColorSeparator, SIGNAL (clicked ()), this, SLOT (slot_addColorSeparator ())); - connect (pb_moveColorUp, SIGNAL (clicked ()), this, SLOT (slot_moveColor ())); - connect (pb_moveColorDown, SIGNAL (clicked ()), this, SLOT (slot_moveColor ())); - connect (pb_clearColors, SIGNAL (clicked ()), this, SLOT (slot_clearColors ())); - - QGridLayout* layout = new QGridLayout; - layout->addWidget (lw_quickColors, 0, 0); - layout->addLayout (buttonLayout, 0, 1); - - quickColorTab->setLayout (layout); - tabs->addTab (quickColorTab, "Quick Colors"); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ConfigDialog::initGridTab () { - QWidget* tab = new QWidget; - QGridLayout* layout = new QGridLayout; - QVBoxLayout* l2 = new QVBoxLayout; - - QLabel* xlabel = new QLabel ("X"), - *ylabel = new QLabel ("Y"), - *zlabel = new QLabel ("Z"), - *anglabel = new QLabel ("Angle"); - - short i = 1; - for (QLabel* label : std::initializer_list<QLabel*> ({xlabel, ylabel, zlabel, anglabel})) { - label->setAlignment (Qt::AlignCenter); - layout->addWidget (label, 0, i++); - } - - for (int i = 0; i < g_NumGrids; ++i) { - // Icon - lb_gridIcons[i] = new QLabel; - lb_gridIcons[i]->setPixmap (getIcon (fmt ("grid-%s", str (g_GridInfo[i].name).tolower ().chars ()))); - - // Text label - lb_gridLabels[i] = new QLabel (fmt ("%s:", g_GridInfo[i].name)); - - QHBoxLayout* labellayout = new QHBoxLayout; - labellayout->addWidget (lb_gridIcons[i]); - labellayout->addWidget (lb_gridLabels[i]); - layout->addLayout (labellayout, i + 1, 0); - - // Add the widgets - for (int j = 0; j < 4; ++j) { - dsb_gridData[i][j] = new QDoubleSpinBox; - dsb_gridData[i][j]->setValue (g_GridInfo[i].confs[j]->value); - layout->addWidget (dsb_gridData[i][j], i + 1, j + 1); - } - } - - l2->addLayout (layout); - l2->addStretch (1); - - tab->setLayout (l2); - tabs->addTab (tab, "Grids"); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -extern_cfg (str, prog_ytruder); -extern_cfg (str, prog_rectifier); -extern_cfg (str, prog_intersector); -extern_cfg (str, prog_isecalc); -static const struct extProgInfo { - const char* const name, *iconname; - strconfig* const path; - mutable QLineEdit* input; - mutable QPushButton* setPathButton; -} g_extProgInfo[] = { - { "Ytruder", "ytruder", &prog_ytruder, null, null }, - { "Rectifier", "rectifier", &prog_rectifier, null, null }, - { "Intersector", "intersector", &prog_intersector, null, null }, - { "Isecalc", "isecalc", &prog_isecalc, null, null }, -}; - -void ConfigDialog::initExtProgTab () { - QWidget* tab = new QWidget; - QGridLayout* pathsLayout = new QGridLayout; - QGroupBox* pathsBox = new QGroupBox ("Paths", this); - QVBoxLayout* layout = new QVBoxLayout (this); - - ulong row = 0; - for (const extProgInfo& info : g_extProgInfo) { - QLabel* icon = new QLabel, - *progLabel = new QLabel (info.name); - QLineEdit* input = new QLineEdit; - QPushButton* setPathButton = new QPushButton (); - - icon->setPixmap (getIcon (info.iconname)); - input->setText (info.path->value); - setPathButton->setIcon (getIcon ("folder")); - info.input = input; - info.setPathButton = setPathButton; - - connect (setPathButton, SIGNAL (clicked ()), this, SLOT (slot_setExtProgPath ())); - - pathsLayout->addWidget (icon, row, 0); - pathsLayout->addWidget (progLabel, row, 1); - pathsLayout->addWidget (input, row, 2); - pathsLayout->addWidget (setPathButton, row, 3); - ++row; - } - - pathsBox->setLayout (pathsLayout); - layout->addWidget (pathsBox); - layout->addSpacing (10); - - tab->setLayout (layout); - tabs->addTab (tab, "Ext. Programs"); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ConfigDialog::updateQuickColorList (quickColorMetaEntry* pSel) { - for (QListWidgetItem* qItem : quickColorItems) - delete qItem; - - quickColorItems.clear (); - - // Init table items - for (quickColorMetaEntry& entry : quickColorMeta) { - QListWidgetItem* qItem = new QListWidgetItem; - - if (entry.bSeparator) { - qItem->setText ("--------"); - qItem->setIcon (getIcon ("empty")); - } else { - color* col = entry.col; - - if (col == null) { - qItem->setText ("[[unknown color]]"); - qItem->setIcon (getIcon ("error")); - } else { - qItem->setText (col->zName); - qItem->setIcon (getIcon ("palette")); - } - } - - lw_quickColors->addItem (qItem); - quickColorItems.push_back (qItem); - - if (pSel && &entry == pSel) { - lw_quickColors->setCurrentItem (qItem); - lw_quickColors->scrollToItem (qItem); - } - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ConfigDialog::slot_setColor () { - quickColorMetaEntry* entry = null; - QListWidgetItem* item = null; - const bool isNew = static_cast<QPushButton*> (sender ()) == pb_addColor; - - if (isNew == false) { - item = getSelectedQuickColor (); - if (!item) - return; - - ulong ulIdx = getItemRow (item, quickColorItems); - entry = &quickColorMeta[ulIdx]; - - if (entry->bSeparator == true) - return; // don't color separators - } - - short dDefault = entry ? entry->col->index () : -1; - short dValue; - - if (ColorSelectDialog::staticDialog (dValue, dDefault, this) == false) - return; - - if (entry) - entry->col = getColor (dValue); - else { - quickColorMetaEntry entry = {getColor (dValue), null, false}; - - item = getSelectedQuickColor (); - ulong idx; - - if (item) - idx = getItemRow (item, quickColorItems) + 1; - else - idx = quickColorItems.size(); - - quickColorMeta.insert (quickColorMeta.begin() + idx, entry); - entry = quickColorMeta[idx]; - } - - updateQuickColorList (entry); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ConfigDialog::slot_delColor () { - if (lw_quickColors->selectedItems().size() == 0) - return; - - QListWidgetItem* qItem = lw_quickColors->selectedItems ()[0]; - ulong ulIdx = getItemRow (qItem, quickColorItems); - quickColorMeta.erase (quickColorMeta.begin () + ulIdx); - updateQuickColorList (); -} - -// ============================================================================= -void ConfigDialog::slot_moveColor () { - const bool bUp = (static_cast<QPushButton*> (sender()) == pb_moveColorUp); - - if (lw_quickColors->selectedItems().size() == 0) - return; - - QListWidgetItem* qItem = lw_quickColors->selectedItems ()[0]; - ulong ulIdx = getItemRow (qItem, quickColorItems); - - long lDest = bUp ? (ulIdx - 1) : (ulIdx + 1); - - if (lDest < 0 || (ulong)lDest >= quickColorItems.size ()) - return; // destination out of bounds - - quickColorMetaEntry tmp = quickColorMeta[lDest]; - quickColorMeta[lDest] = quickColorMeta[ulIdx]; - quickColorMeta[ulIdx] = tmp; - - updateQuickColorList (&quickColorMeta[lDest]); -} - -// ============================================================================= -void ConfigDialog::slot_addColorSeparator() { - quickColorMeta.push_back ({null, null, true}); - updateQuickColorList (&quickColorMeta[quickColorMeta.size () - 1]); -} - -// ============================================================================= -void ConfigDialog::slot_clearColors () { - quickColorMeta.clear (); - updateQuickColorList (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ConfigDialog::makeSlider (QSlider*& slider, short min, short max, short defval) { - slider = new QSlider (Qt::Horizontal); - slider->setRange (min, max); - slider->setSliderPosition (defval); - slider->setTickPosition (QSlider::TicksAbove); - slider->setTickInterval (1); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -ConfigDialog::~ConfigDialog () { - g_ConfigDialog = null; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ConfigDialog::pickColor (strconfig& cfg, QPushButton* qButton) { - QColorDialog dlg (QColor (cfg.value.chars())); - dlg.setWindowIcon (getIcon ("colorselect")); - - if (dlg.exec ()) { - uchar r = dlg.currentColor ().red (), - g = dlg.currentColor ().green (), - b = dlg.currentColor ().blue (); - cfg.value.format ("#%.2X%.2X%.2X", r, g, b); - setButtonBackground (qButton, cfg.value); - } -} - -void ConfigDialog::slot_setGLBackground () { - pickColor (gl_bgcolor, pb_viewBg); -} - -void ConfigDialog::slot_setGLForeground () { - pickColor (gl_maincolor, pb_viewFg); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ConfigDialog::setButtonBackground (QPushButton* qButton, str zValue) { - qButton->setIcon (getIcon ("colorselect")); - qButton->setAutoFillBackground (true); - qButton->setStyleSheet ( - fmt ("background-color: %s", zValue.chars()).chars() - ); -} - -// ============================================================================= -long ConfigDialog::getItemRow (QListWidgetItem* qItem, std::vector<QListWidgetItem*>& haystack) { - long i = 0; - - for (QListWidgetItem* it : haystack) { - if (it == qItem) - return i; - ++i; - } - - return -1; -} - -// ============================================================================= -QListWidgetItem* ConfigDialog::getSelectedQuickColor () { - if (lw_quickColors->selectedItems().size() == 0) - return null; - - return lw_quickColors->selectedItems ()[0]; -} - -// ============================================================================= -QList<ShortcutListItem*> ConfigDialog::getShortcutSelection () { - QList<ShortcutListItem*> out; - - for (QListWidgetItem* entry : lw_shortcutList->selectedItems ()) - out << static_cast<ShortcutListItem*> (entry); - - return out; -} - -// ============================================================================= -void ConfigDialog::slot_setShortcut () { - QList<ShortcutListItem*> sel = getShortcutSelection (); - - if (sel.size() < 1) - return; - - ShortcutListItem* item = sel[0]; - if (KeySequenceDialog::staticDialog (*(item->getActionInfo ()), this)) - setShortcutText (item, *(item->getActionInfo ())); -} - -// ============================================================================= -void ConfigDialog::slot_resetShortcut () { - QList<ShortcutListItem*> sel = getShortcutSelection (); - - for (ShortcutListItem* item : sel) { - actionmeta* info = item->getActionInfo (); - keyseqconfig* conf = info->conf; - - conf->reset (); - (*info->qAct)->setShortcut (*conf); - - setShortcutText (item, *info); - } -} - -// ============================================================================= -void ConfigDialog::slot_clearShortcut () { - QList<ShortcutListItem*> sel = getShortcutSelection (); - QKeySequence dummy; - - for (ShortcutListItem* item : sel) { - actionmeta* info = item->getActionInfo (); - keyseqconfig* conf = info->conf; - conf->value = dummy; - - (*info->qAct)->setShortcut (*conf); - setShortcutText (item, *info); - } -} - -// ============================================================================= -void ConfigDialog::slot_setExtProgPath () { - const extProgInfo* info = null; - for (const extProgInfo& it : g_extProgInfo) { - if (it.setPathButton == sender ()) { - info = ⁢ - break; - } - } - - assert (info != null); - - str filter; -#ifdef _WIN32 - filter = "Applications (*.exe)(*.exe);;All files (*.*)(*.*)"; -#endif // WIN32 - - str fpath = QFileDialog::getOpenFileName (this, fmt ("Path to %s", info->name), "", filter); - if (!~fpath) - return; - - info->input->setText (fpath); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ConfigDialog::setShortcutText (QListWidgetItem* qItem, actionmeta meta) { - QAction* const act = *meta.qAct; - str zLabel = act->iconText (); - str zKeybind = act->shortcut ().toString (); - - qItem->setText (fmt ("%s (%s)", zLabel.chars () ,zKeybind.chars ()).chars()); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -str ConfigDialog::makeColorToolBarString () { - str val; - - for (quickColorMetaEntry entry : quickColorMeta) { - if (~val > 0) - val += ':'; - - if (entry.bSeparator) - val += '|'; - else - val.appendformat ("%d", entry.col->index ()); - } - - return val; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void ConfigDialog::staticDialog () { - ConfigDialog dlg (g_win); - - if (dlg.exec ()) { - lv_colorize = dlg.cb_colorize->isChecked (); - gl_colorbfc = dlg.cb_colorBFC->isChecked (); - gl_selflash = dlg.cb_selFlash->isChecked (); - edit_schemanticinline = dlg.cb_schemanticInline->isChecked (); - gl_blackedges = dlg.cb_blackEdges->isChecked (); - - gl_maincolor_alpha = ((double)dlg.sl_viewFgAlpha->value ()) / 10.0f; - gl_linethickness = dlg.sl_lineThickness->value (); - gui_toolbar_iconsize = (dlg.sl_iconSize->value () * 4) + 12; - - // Manage the quick color toolbar - g_win->setQuickColorMeta (dlg.quickColorMeta); - gui_colortoolbar = dlg.makeColorToolBarString (); - - // Set the grid settings - for (int i = 0; i < g_NumGrids; ++i) - for (int j = 0; j < 4; ++j) - g_GridInfo[i].confs[j]->value = dlg.dsb_gridData[i][j]->value (); - - // Ext program settings - for (const extProgInfo& info : g_extProgInfo) - *info.path = info.input->text (); - - // Save the config - config::save (); - - // Reload all subfiles as the ldraw path potentially changed. - reloadAllSubfiles (); - - g_win->R ()->setBackground (); - g_win->refresh (); - g_win->updateToolBars (); - } -} - -// ========================================================================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ========================================================================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ========================================================================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ========================================================================================================================= -KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, - Qt::WindowFlags f) : QDialog (parent, f), seq (seq) -{ - lb_output = new QLabel; - IMPLEMENT_DIALOG_BUTTONS - - setWhatsThis ("Into this dialog you can input a key sequence for use as a " - "shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to " - "dismiss."); - - QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (lb_output); - layout->addWidget (bbx_buttons); - setLayout (layout); - - updateOutput (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -bool KeySequenceDialog::staticDialog (actionmeta& meta, QWidget* parent) { - KeySequenceDialog dlg (*meta.conf, parent); - - if (dlg.exec () == false) - return false; - - *meta.conf = dlg.seq; - (*meta.qAct)->setShortcut (*meta.conf); - return true; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void KeySequenceDialog::updateOutput () { - str zShortcut = seq.toString (); - - str zText = fmt ("<center><b>%s</b></center>", zShortcut.chars ()); - - lb_output->setText (zText); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void KeySequenceDialog::keyPressEvent (QKeyEvent* ev) { - seq = ev->key (); - - switch (seq) { - case Qt::Key_Shift: - case Qt::Key_Control: - case Qt::Key_Alt: - case Qt::Key_Meta: - seq = 0; - break; - - default: - break; - } - - seq = (seq | ev->modifiers ()); - - updateOutput (); -} \ No newline at end of file
--- a/zz_configDialog.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,143 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef CONFIGDIALOG_H -#define CONFIGDIALOG_H - -#include "gui.h" -#include <qdialog.h> -#include <qlabel.h> -#include <qlineedit.h> -#include <qdialogbuttonbox.h> -#include <qpushbutton.h> -#include <qcheckbox.h> -#include <qlistwidget.h> -#include <qspinbox.h> - -// ============================================================================= -class ShortcutListItem : public QListWidgetItem { -public: - explicit ShortcutListItem (QListWidget* view = null, int type = Type) : - QListWidgetItem (view, type) {} - - actionmeta* getActionInfo () const { return m_info; } - void setActionInfo (actionmeta* info) { m_info = info; } - -private: - actionmeta* m_info; -}; - -// ============================================================================= -class ConfigDialog : public QDialog { - Q_OBJECT - -public: - QTabWidget* tabs; - QWidget* mainTab, *shortcutsTab, *quickColorTab, *extProgTab; - - // ========================================================================= - // Main tab widgets - QLabel* lb_viewBg, *lb_viewFg, *lb_viewFgAlpha; - QLabel* lb_lineThickness, *lb_iconSize; - QPushButton* pb_viewBg, *pb_viewFg; - QCheckBox* cb_colorize, *cb_colorBFC, *cb_selFlash, *cb_schemanticInline, - *cb_blackEdges; - QSlider* sl_viewFgAlpha, *sl_lineThickness, *sl_iconSize; - - // ========================================================================= - // Shortcuts tab - QListWidget* lw_shortcutList; - QPushButton* pb_setShortcut, *pb_resetShortcut, *pb_clearShortcut; - - // ========================================================================= - // Quick color toolbar tab - QListWidget* lw_quickColors; - QPushButton* pb_addColor, *pb_delColor, *pb_changeColor, *pb_addColorSeparator, - *pb_moveColorUp, *pb_moveColorDown, *pb_clearColors; - std::vector<QListWidgetItem*> quickColorItems; - std::vector<quickColorMetaEntry> quickColorMeta; - - // ========================================================================= - // Grid tab - QLabel* lb_gridLabels[3]; - QLabel* lb_gridIcons[3]; - QDoubleSpinBox* dsb_gridData[3][4]; - - // ========================================================================= - QDialogButtonBox* bbx_buttons; - - ConfigDialog (ForgeWindow* parent); - ~ConfigDialog (); - static void staticDialog (); - -private: - void initMainTab (); - void initShortcutsTab (); - void initQuickColorTab (); - void initGridTab (); - void initExtProgTab (); - - void makeSlider (QSlider*& slider, short min, short max, short defval); - void setButtonBackground (QPushButton* qButton, str zValue); - void pickColor (strconfig& cfg, QPushButton* qButton); - void updateQuickColorList (quickColorMetaEntry* pSel = null); - void setShortcutText (QListWidgetItem* qItem, actionmeta meta); - long getItemRow (QListWidgetItem* qItem, std::vector<QListWidgetItem*>& haystack); - str makeColorToolBarString (); - QListWidgetItem* getSelectedQuickColor (); - QList<ShortcutListItem*> getShortcutSelection (); - -private slots: - void slot_setGLBackground (); - void slot_setGLForeground (); - - void slot_setShortcut (); - void slot_resetShortcut (); - void slot_clearShortcut (); - - void slot_setColor (); - void slot_delColor (); - void slot_addColorSeparator (); - void slot_moveColor (); - void slot_clearColors (); - - void slot_setExtProgPath (); -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -class KeySequenceDialog : public QDialog { - Q_OBJECT - -public: - explicit KeySequenceDialog (QKeySequence seq, QWidget* parent = null, Qt::WindowFlags f = 0); - static bool staticDialog (actionmeta& meta, QWidget* parent = null); - - QLabel* lb_output; - QDialogButtonBox* bbx_buttons; - QKeySequence seq; - -private: - void updateOutput (); - -private slots: - virtual void keyPressEvent (QKeyEvent* ev); -}; - -#endif // CONFIGDIALOG_H \ No newline at end of file
--- a/zz_historyDialog.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,283 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "zz_historyDialog.h" -#include "history.h" -#include "colors.h" -#include <qboxlayout.h> -#include <qmessagebox.h> - -EXTERN_ACTION (undo); -EXTERN_ACTION (redo); - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -HistoryDialog::HistoryDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) { - historyList = new QListWidget; - undoButton = new QPushButton ("Undo"); - redoButton = new QPushButton ("Redo"); - clearButton = new QPushButton ("Clear"); - buttons = new QDialogButtonBox (QDialogButtonBox::Close); - - historyList->setAlternatingRowColors (true); - - undoButton->setIcon (getIcon ("undo")); - redoButton->setIcon (getIcon ("redo")); - - connect (undoButton, SIGNAL (clicked ()), this, SLOT (slot_undo ())); - connect (redoButton, SIGNAL (clicked ()), this, SLOT (slot_redo ())); - connect (clearButton, SIGNAL (clicked ()), this, SLOT (slot_clear ())); - connect (buttons, SIGNAL (rejected ()), this, SLOT (reject ())); - connect (historyList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_selChanged ())); - - QVBoxLayout* qButtonLayout = new QVBoxLayout; - qButtonLayout->setDirection (QBoxLayout::TopToBottom); - qButtonLayout->addWidget (undoButton); - qButtonLayout->addWidget (redoButton); - qButtonLayout->addWidget (clearButton); - qButtonLayout->addStretch (); - - QGridLayout* qLayout = new QGridLayout; - qLayout->setColumnStretch (0, 1); - qLayout->addWidget (historyList, 0, 0, 2, 1); - qLayout->addLayout (qButtonLayout, 0, 1); - qLayout->addWidget (buttons, 1, 1); - - setLayout (qLayout); - setWindowIcon (getIcon ("history")); - setWindowTitle (APPNAME " - Edit history"); - - populateList (); - updateButtons (); - updateSelection (); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void HistoryDialog::populateList () { - historyList->clear (); - - QListWidgetItem* qItem = new QListWidgetItem; - qItem->setText ("[[ initial state ]]"); - qItem->setIcon (getIcon ("empty")); - historyList->addItem (qItem); - - for (HistoryEntry* entry : History::entries ()) { - str text; - QIcon entryIcon; - - switch (entry->type ()) { - case HISTORY_Add: - { - AddHistory* subentry = static_cast<AddHistory*> (entry); - ulong count = subentry->paObjs.size (); - str verb = "Added"; - - switch (subentry->eType) { - case AddHistory::Paste: - verb = "Pasted"; - entryIcon = getIcon ("paste"); - break; - - default: - { - // Determine a common type for these objects. If all objects are of the same - // type, we display its addition icon. Otherwise, we draw a subfile addition - // one as a default. - LDObject::Type eCommonType = LDObject::Unidentified; - for (LDObject* obj : subentry->paObjs) { - if (eCommonType == LDObject::Unidentified or obj->getType() == eCommonType) - eCommonType = obj->getType (); - else { - eCommonType = LDObject::Unidentified; - break; - } - } - - // Set the icon based on the common type decided above. - if (eCommonType == LDObject::Unidentified) - entryIcon = getIcon ("add-subfile"); - else - entryIcon = getIcon (fmt ("add-%s", g_saObjTypeIcons[eCommonType])); - } - break; - } - - text.format ("%s %lu objects\n%s", verb.chars(), count, - LDObject::objectListContents (subentry->paObjs).chars()); - } - break; - - case HISTORY_QuadSplit: - { - QuadSplitHistory* subentry = static_cast<QuadSplitHistory*> (entry); - ulong ulCount = subentry->paQuads.size (); - text.format ("Split %lu quad%s to triangles", ulCount, PLURAL (ulCount)); - - entryIcon = getIcon ("quad-split"); - } - break; - - case HISTORY_Del: - { - DelHistory* subentry = static_cast<DelHistory*> (entry); - ulong count = subentry->cache.size (); - str verb = "Deleted"; - entryIcon = getIcon ("delete"); - - switch (subentry->eType) { - case DelHistory::Cut: - entryIcon = getIcon ("cut"); - verb = "Cut"; - break; - - default: - break; - } - - text.format ("%s %lu objects:\n%s", verb.chars(), count, - LDObject::objectListContents (subentry->cache).chars ()); - } - break; - - case HISTORY_SetColor: - { - SetColorHistory* subentry = static_cast<SetColorHistory*> (entry); - ulong count = subentry->ulaIndices.size (); - text.format ("Set color of %lu objects to %d (%s)", count, - subentry->dNewColor, getColor (subentry->dNewColor)->zName.chars()); - - entryIcon = getIcon ("palette"); - } - break; - - case HISTORY_ListMove: - { - ListMoveHistory* subentry = static_cast<ListMoveHistory*> (entry); - ulong ulCount = subentry->ulaIndices.size (); - - text.format ("Moved %lu objects %s", ulCount, - subentry->bUp ? "up" : "down"); - entryIcon = getIcon (subentry->bUp ? "arrow-up" : "arrow-down"); - } - break; - - case HISTORY_Edit: - { - EditHistory* subentry = static_cast<EditHistory*> (entry); - - text.format ("Edited %u objects\n%s", - subentry->paNewObjs.size(), - LDObject::objectListContents (subentry->paOldObjs).chars ()); - entryIcon = getIcon ("set-contents"); - } - break; - - case HISTORY_Inline: - { - InlineHistory* subentry = static_cast<InlineHistory*> (entry); - - text.format ("%s %lu subfiles:\n%lu resultants", - (subentry->bDeep) ? "Deep-inlined" : "Inlined", - (ulong) subentry->paRefs.size(), - (ulong) subentry->ulaBitIndices.size()); - - entryIcon = getIcon (subentry->bDeep ? "inline-deep" : "inline"); - } - break; - - default: - text = "???"; - break; - } - - QListWidgetItem* item = new QListWidgetItem; - item->setText (text); - item->setIcon (entryIcon); - historyList->addItem (item); - } -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void HistoryDialog::slot_undo () { - History::undo (); - updateButtons (); - updateSelection (); -} - -// ============================================================================= -void HistoryDialog::slot_redo () { - History::redo (); - updateButtons (); - updateSelection (); -} - -void HistoryDialog::updateSelection () { - historyList->setCurrentItem (historyList->item (History::pos () + 1)); -} - -// ============================================================================= -void HistoryDialog::slot_clear () { - if (!confirm ("Are you sure you want to clear the edit history?\nThis cannot be undone.")) - return; // Canceled - - History::clear (); - populateList (); - updateButtons (); -} - -// ============================================================================= -void HistoryDialog::updateButtons () { - undoButton->setEnabled (ACTION (undo)->isEnabled ()); - redoButton->setEnabled (ACTION (redo)->isEnabled ()); -} - -// ============================================================================= -void HistoryDialog::slot_selChanged () { - if (historyList->selectedItems ().size () != 1) - return; - - QListWidgetItem* qItem = historyList->selectedItems ()[0]; - - // Find the index of the edit - long lIdx; - for (lIdx = 0; lIdx < historyList->count (); ++lIdx) - if (historyList->item (lIdx) == qItem) - break; - - // qHistoryList is 0-based, History is -1-based, thus decrement - // the index now - lIdx--; - - if (lIdx == History::pos ()) - return; - - // Seek to the selected edit by repeadetly undoing or redoing. - while (History::pos () != lIdx) { - if (History::pos () > lIdx) - History::undo (); - else - History::redo (); - } - - updateButtons (); -} \ No newline at end of file
--- a/zz_historyDialog.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef HISTORYDIALOG_H -#define HISTORYDIALOG_H - -#include <qdialog.h> -#include <qdialogbuttonbox.h> -#include <qlistwidget.h> -#include <qpushbutton.h> -#include "gui.h" - -class HistoryDialog : public QDialog { - Q_OBJECT - -public: - explicit HistoryDialog (QWidget* parent = null, Qt::WindowFlags f = 0); - void populateList (); - - QListWidget* historyList; - QPushButton* undoButton, *redoButton, *clearButton; - QDialogButtonBox* buttons; - -private: - void updateButtons (); - void updateSelection (); - -private slots: - void slot_undo (); - void slot_redo (); - void slot_clear (); - void slot_selChanged (); -}; - -#endif // HISTORYDIALOG_H \ No newline at end of file
--- a/zz_ldrawPathDialog.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <qlineedit.h> -#include <qpushbutton.h> -#include <qdialogbuttonbox.h> -#include <QFileDialog> -#include "zz_ldrawPathDialog.h" -#include "gui.h" -#include "file.h" - -extern_cfg (str, io_ldpath); - -// ======================================================================================================================================== -LDrawPathDialog::LDrawPathDialog (const bool validDefault, QWidget* parent, Qt::WindowFlags f) - : QDialog (parent, f), m_validDefault (validDefault) -{ - QLabel* lb_description; - lb_resolution = new QLabel ("---"); - - if (validDefault == false) - lb_description = new QLabel ("Please input your LDraw directory"); - - QLabel* lb_path = new QLabel ("LDraw path:"); - le_path = new QLineEdit; - btn_findPath = new QPushButton; - btn_findPath->setIcon (getIcon ("folder")); - - btn_tryConfigure = new QPushButton ("Configure"); - btn_tryConfigure->setIcon (getIcon ("settings")); - - btn_cancel = new QPushButton; - - if (validDefault == false) { - btn_cancel->setText ("Exit"); - btn_cancel->setIcon (getIcon ("exit")); - } else { - btn_cancel->setText ("Cancel"); - btn_cancel->setIcon (getIcon ("cancel")); - } - - dbb_buttons = new QDialogButtonBox (QDialogButtonBox::Ok); - dbb_buttons->addButton (btn_tryConfigure, QDialogButtonBox::ActionRole); - dbb_buttons->addButton (btn_cancel, QDialogButtonBox::RejectRole); - okButton ()->setEnabled (false); - - QHBoxLayout* inputLayout = new QHBoxLayout; - inputLayout->addWidget (lb_path); - inputLayout->addWidget (le_path); - inputLayout->addWidget (btn_findPath); - - QVBoxLayout* mainLayout = new QVBoxLayout; - - if (validDefault == false) - mainLayout->addWidget (lb_description); - - mainLayout->addLayout (inputLayout); - mainLayout->addWidget (lb_resolution); - mainLayout->addWidget (dbb_buttons); - setLayout (mainLayout); - - connect (le_path, SIGNAL (textEdited ()), this, SLOT (slot_tryConfigure ())); - connect (btn_findPath, SIGNAL (clicked ()), this, SLOT (slot_findPath ())); - connect (btn_tryConfigure, SIGNAL (clicked ()), this, SLOT (slot_tryConfigure ())); - connect (dbb_buttons, SIGNAL (accepted ()), this, SLOT (accept ())); - connect (dbb_buttons, SIGNAL (rejected ()), this, (validDefault) ? SLOT (reject ()) : SLOT (slot_exit ())); - - setPath (io_ldpath); - if (validDefault) - slot_tryConfigure (); -} - -// ======================================================================================================================================== -QPushButton* LDrawPathDialog::okButton () { - return dbb_buttons->button (QDialogButtonBox::Ok); -} - -// ======================================================================================================================================== -void LDrawPathDialog::setPath (str path) { - le_path->setText (path); -} - -// ======================================================================================================================================== -str LDrawPathDialog::path () const { - return le_path->text (); -} - -// ======================================================================================================================================== -void LDrawPathDialog::slot_findPath () { - str newpath = QFileDialog::getExistingDirectory (this, "Find LDraw Path"); - - if (~newpath > 0 && newpath != path ()) { - setPath (newpath); - slot_tryConfigure (); - } -} - - -// ======================================================================================================================================== -void LDrawPathDialog::slot_exit () { - exit (1); -} - -// ======================================================================================================================================== -void LDrawPathDialog::slot_tryConfigure () { - if (LDPaths::tryConfigure (path ()) == false) { - lb_resolution->setText (fmt ("<span style=\"color:red; font-weight: bold;\">%s</span>", LDPaths::getError().chars ())); - okButton ()->setEnabled (false); - return; - } - - lb_resolution->setText ("<span style=\"color: #7A0; font-weight: bold;\">OK!</span>"); - okButton ()->setEnabled (true); -} \ No newline at end of file
--- a/zz_ldrawPathDialog.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef LDRAWPATHDIALOG_H -#define LDRAWPATHDIALOG_H - -#include <qdialog.h> -#include "common.h" - -class QLabel; -class QLineEdit; -class QDialogButtonBox; - -class LDrawPathDialog : public QDialog { - Q_OBJECT - -public: - explicit LDrawPathDialog (const bool validDefault, QWidget* parent = null, Qt::WindowFlags f = 0); - str path () const; - void setPath (str path); - void (*callback ()) () const { return m_callback; } - void setCallback (void (*callback) ()) { m_callback = callback; } - -private: - Q_DISABLE_COPY (LDrawPathDialog) - - QLabel* lb_resolution; - QLineEdit* le_path; - QPushButton* btn_findPath, *btn_tryConfigure, *btn_cancel; - QDialogButtonBox* dbb_buttons; - void (*m_callback) (); - const bool m_validDefault; - - QPushButton* okButton (); - -private slots: - void slot_findPath (); - void slot_tryConfigure (); - void slot_exit (); -}; - -#endif // LDRAWPATHDIALOG_H
--- a/zz_newPartDialog.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,121 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <qgridlayout.h> -#include "zz_newPartDialog.h" -#include "file.h" - -// ------------------------------------- -enum { - LICENSE_CCAL, - LICENSE_NonCA, - LICENSE_None -}; - -// ------------------------------------- -enum { - BFCBOX_CCW, - BFCBOX_CW, - BFCBOX_None, -}; - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -NewPartDialog::NewPartDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) { - lb_brickIcon = new QLabel; - lb_brickIcon->setPixmap (getIcon ("brick")); - - lb_name = new QLabel ("Name:"); - le_name = new QLineEdit; - le_name->setMinimumWidth (320); - - lb_author = new QLabel ("Author:"); - le_author = new QLineEdit; - - rb_license = new RadioBox ("License", { - "CCAL Redistributable", - "Non-redistributable", - "Don't append a license", - }, LICENSE_CCAL); - - rb_BFC = new RadioBox ("BFC Winding", { - "CCW", - "CW", - "No winding" - }, BFCBOX_CCW); - - QHBoxLayout* boxes = new QHBoxLayout; - boxes->addWidget (rb_license); - boxes->addWidget (rb_BFC); - - IMPLEMENT_DIALOG_BUTTONS - - QGridLayout* layout = new QGridLayout; - layout->addWidget (lb_brickIcon, 0, 0); - layout->addWidget (lb_name, 0, 1); - layout->addWidget (le_name, 0, 2); - layout->addWidget (lb_author, 1, 1); - layout->addWidget (le_author, 1, 2); - layout->addLayout (boxes, 2, 1, 1, 2); - layout->addWidget (bbx_buttons, 3, 2); - - setLayout (layout); - setWindowIcon (getIcon ("brick")); - setWindowTitle (APPNAME ": New Part"); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void NewPartDialog::StaticDialog () { - NewPartDialog dlg (g_win); - if (dlg.exec ()) { - newFile (); - - short idx; - str zAuthor = dlg.le_author->text (); - vector<LDObject*>& objs = g_curfile->m_objs; - - idx = dlg.rb_BFC->value (); - const LDBFC::Type eBFCType = - (idx == BFCBOX_CCW) ? LDBFC::CertifyCCW : - (idx == BFCBOX_CW) ? LDBFC::CertifyCW : - LDBFC::NoCertify; - - idx = dlg.rb_license->value (); - const char* sLicense = - (idx == LICENSE_CCAL) ? "Redistributable under CCAL version 2.0 : see CAreadme.txt" : - (idx == LICENSE_NonCA) ? "Not redistributable : see NonCAreadme.txt" : - null; - - objs.push_back (new LDComment (dlg.le_name->text ())); - objs.push_back (new LDComment ("Name: <untitled>.dat")); - objs.push_back (new LDComment (fmt ("Author: %s", zAuthor.chars()))); - objs.push_back (new LDComment (fmt ("!LDRAW_ORG Unofficial_Part"))); - - if (sLicense != null) - objs.push_back (new LDComment (fmt ("!LICENSE %s", sLicense))); - - objs.push_back (new LDEmpty); - objs.push_back (new LDBFC (eBFCType)); - objs.push_back (new LDEmpty); - - g_win->refresh (); - } -} \ No newline at end of file
--- a/zz_newPartDialog.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef NEWPARTDIALOG_H -#define NEWPARTDIALOG_H - -#include <qdialog.h> -#include <qlabel.h> -#include <qlineedit.h> -#include <qcombobox.h> -#include <qdialogbuttonbox.h> -#include <qradiobutton.h> -#include <qbuttongroup.h> -#include "gui.h" -#include "radiobox.h" - -class NewPartDialog : public QDialog { -public: - explicit NewPartDialog (QWidget* parent = null, Qt::WindowFlags f = 0); - static void StaticDialog (); - - QLabel* lb_brickIcon, *lb_name, *lb_author, *lb_license, *lb_BFC; - QLineEdit* le_name, *le_author; - - RadioBox* rb_license, *rb_BFC; - QDialogButtonBox* bbx_buttons; -}; - -#endif // NEWPARTDIALOG_H \ No newline at end of file
--- a/zz_setContentsDialog.cpp Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QAbstractButton> -#include <qboxlayout.h> -#include "zz_setContentsDialog.h" -#include "file.h" -#include "gui.h" -#include "history.h" - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -SetContentsDialog::SetContentsDialog (LDObject* obj, QWidget* parent) : QDialog(parent) { - setWindowTitle (APPNAME ": Set Contents"); - - lb_contents = new QLabel ("Set contents:", parent); - - le_contents = new QLineEdit (parent); - le_contents->setText (obj->getContents ().chars()); - le_contents->setWhatsThis ("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\">official file format " - "standard</a> for further information."); - le_contents->setMinimumWidth (384); - - if (obj->getType() == LDObject::Gibberish) { - lb_error = new QLabel; - lb_error->setText (fmt ("<span style=\"color: #900\">%s</span>", - static_cast<LDGibberish*> (obj)->zReason.chars())); - - QPixmap qErrorPixmap = getIcon ("error").scaledToHeight (16); - - lb_errorIcon = new QLabel; - lb_errorIcon->setPixmap (qErrorPixmap); - } - - IMPLEMENT_DIALOG_BUTTONS - - QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (lb_contents); - layout->addWidget (le_contents); - - QHBoxLayout* layout2 = new QHBoxLayout; - - if (obj->getType() == LDObject::Gibberish) { - layout2->addWidget (lb_errorIcon); - layout2->addWidget (lb_error); - } - - layout2->addWidget (bbx_buttons); - layout->addLayout (layout2); - setLayout (layout); - - setWindowTitle (APPNAME ": Set Contents"); - setWindowIcon (getIcon ("set-contents")); -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void SetContentsDialog::slot_handleButtons (QAbstractButton* qButton) { - qButton = qButton; -} - -// ============================================================================= -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -// ============================================================================= -void SetContentsDialog::staticDialog (LDObject* obj) { - if (!obj) - return; - - SetContentsDialog dlg (obj, g_win); - if (dlg.exec () == false) - return; - - LDObject* oldobj = obj; - - // Reinterpret it from the text of the input field - obj = parseLine (dlg.le_contents->text ().toStdString ().c_str ()); - - // Mark down the history now before we perform the replacement (which - // destroys the old object) - History::addEntry (new EditHistory ({(ulong) oldobj->getIndex (g_curfile)}, - {oldobj->clone ()}, {obj->clone ()})); - - oldobj->replace (obj); - - // Rebuild stuff after this - g_win->refresh (); -} \ No newline at end of file
--- a/zz_setContentsDialog.h Wed May 08 14:57:48 2013 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 Santeri Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef SETCONTENTSDIALOG_H - -#include <qdialog.h> -#include <qlineedit.h> -#include <qlabel.h> -#include <qdialogbuttonbox.h> -#include "common.h" - -// ============================================================================= -// SetContentsDialog -// -// Performs the Set Contents dialog on the given LDObject. Object's contents -// are exposed to the user and is reinterpreted if the user accepts the new -// contents. -// ============================================================================= -class SetContentsDialog : public QDialog { -public: - QLabel* lb_contents, *lb_errorIcon, *lb_error; - QLineEdit* le_contents; - QDialogButtonBox* bbx_buttons; - - SetContentsDialog (LDObject* obj, QWidget* parent = null); - static void staticDialog (LDObject* obj); - -private slots: - void slot_handleButtons (QAbstractButton* qButton); -}; - -#endif // SETCONTENTSDIALOG_H \ No newline at end of file