merge

Sun, 13 Mar 2022 14:53:14 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Sun, 13 Mar 2022 14:53:14 +0200
changeset 179
7b9b85b459de
parent 178
a23024fc98e0 (diff)
parent 175
41628bdab71c (current diff)
child 180
5b7a8f2270ff

merge

--- a/CMakeLists.txt	Fri Mar 11 20:19:26 2022 +0200
+++ b/CMakeLists.txt	Sun Mar 13 14:53:14 2022 +0200
@@ -54,7 +54,6 @@
 	src/gl/gridprogram.cpp
 	src/gl/partrenderer.cpp
 	src/gl/vertexprogram.cpp
-	src/linetypes/comment.cpp
 	src/linetypes/conditionaledge.cpp
 	src/linetypes/edge.cpp
 	src/linetypes/errorline.cpp
@@ -72,6 +71,7 @@
 	src/ui/objecteditor.cpp
 	src/ui/polygonobjecteditor.cpp
 	src/widgets/colorbutton.cpp
+	src/widgets/colorindexinput.cpp
 	src/widgets/colorselectdialog.cpp
 	src/widgets/doublespinbox.cpp
 	src/widgets/matrixeditor.cpp
@@ -112,7 +112,6 @@
 	src/gl/gridprogram.h
 	src/gl/partrenderer.h
 	src/gl/vertexprogram.h
-	src/linetypes/comment.h
 	src/linetypes/conditionaledge.h
 	src/linetypes/edge.h
 	src/linetypes/errorline.h
@@ -132,6 +131,7 @@
 	src/ui/objecteditor.h
 	src/ui/polygonobjecteditor.h
 	src/widgets/colorbutton.h
+	src/widgets/colorindexinput.h
 	src/widgets/colorselectdialog.h
 	src/widgets/doublespinbox.h
 	src/widgets/matrixeditor.h
@@ -148,6 +148,7 @@
 	src/settingseditor/settingseditor.ui
 	src/ui/multiplyfactordialog.ui
 	src/widgets/colorselectdialog.ui
+	src/widgets/colorindexinput.ui
 	src/widgets/matrixeditor.ui
 	src/widgets/vec3editor.ui
 )
--- a/src/colors.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/colors.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -178,6 +178,19 @@
 	}
 }
 
+QColor ldraw::colorEdge(ldraw::Color color, const ldraw::ColorTable& colorTable)
+{
+	if (isDirectColor(color))
+	{
+		QColor const faceColor = directColorFace(color);
+		return (luma(faceColor) < 0.4) ? Qt::white : Qt::black;
+	}
+	else
+	{
+		return colorTable[color].faceColor;
+	}
+}
+
 /**
  * @brief Writes a color index into a @c QDataStream
  * @param stream
@@ -199,3 +212,15 @@
 {
 	return stream >> color.index;
 }
+
+QString ldraw::colorDisplayName(ldraw::Color color, const ldraw::ColorTable &colorTable)
+{
+	if (isDirectColor(color))
+	{
+		return directColorFace(color).name();
+	}
+	else
+	{
+		return colorTable[color].displayName;
+	}
+}
--- a/src/colors.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/colors.h	Sun Mar 13 14:53:14 2022 +0200
@@ -29,6 +29,8 @@
 	bool isDirectColor(Color color);
 	QColor directColorFace(Color color);
 	QColor colorFace(Color color, const ColorTable& colorTable);
+	QColor colorEdge(Color color, const ColorTable& colorTable);
+	QString colorDisplayName(Color color, const ColorTable& colorTable);
 }
 
 /**
--- a/src/document.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/document.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -33,9 +33,9 @@
 	const ldraw::ColorTable& colorTable,
 	QWidget* parent) :
 	QWidget{parent},
+	colorTable{colorTable},
 	model{model},
 	documents{documents},
-	colorTable{colorTable},
 	vertexMap{model},
 	renderer{new Canvas{model, documents, colorTable, this}},
 	ui{*new Ui::Document},
@@ -234,7 +234,7 @@
 	this->renderer->adjustGridToView();
 }
 
-const Model &Document::getModel()
+const Model &Document::getModel() const
 {
 	return *this->model;
 }
--- a/src/document.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/document.h	Sun Mar 13 14:53:14 2022 +0200
@@ -48,8 +48,9 @@
 	void applyToVertices(VertexMap::ApplyFunction fn) const;
 	void handleKeyPress(QKeyEvent* event);
 	void adjustGridToView();
-	const Model& getModel();
+	const Model& getModel() const;
 	const QSet<ldraw::id_t> selectedObjects() const;
+	const ldraw::ColorTable& colorTable;
 Q_SIGNALS:
 	void newStatusText(const QString& newStatusText);
 	void splitterChanged();
@@ -62,7 +63,6 @@
 	void selectTool(class BaseTool* tool);
 	Model* model;
 	DocumentManager* const documents;
-	const ldraw::ColorTable& colorTable;
 	VertexMap vertexMap;
 	Canvas* renderer;
 	Ui::Document& ui;
--- a/src/documentmanager.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/documentmanager.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -22,7 +22,6 @@
 #include <QSaveFile>
 #include "documentmanager.h"
 #include "modeleditor.h"
-#include "linetypes/comment.h"
 #include "linetypes/subfilereference.h"
 #include "parser.h"
 
--- a/src/linetypes/comment.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-#include <QFont>
-#include "comment.h"
-
-QFont ldraw::Comment::textRepresentationFont() const
-{
-	QFont font;
-	font.setItalic(true);
-	return font;
-}
-
-ldraw::Object::Type ldraw::Comment::typeIdentifier() const
-{
-	return Type::Comment;
-}
-
-QString ldraw::Comment::toLDrawCode() const
-{
-	return ("0 // " + this->storedText).trimmed();
-}
-
--- a/src/linetypes/comment.h	Fri Mar 11 20:19:26 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-#pragma once
-#include "object.h"
-#include "metacommand.h"
-
-namespace ldraw
-{
-	class Comment;
-}
-
-class ldraw::Comment : public MetaCommand
-{
-	using MetaCommand::MetaCommand;
-	QFont textRepresentationFont() const override;
-	Type typeIdentifier() const override;
-	QString toLDrawCode() const override;
-};
--- a/src/linetypes/conditionaledge.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/conditionaledge.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -29,3 +29,8 @@
 {
 	return ":/icons/linetype-conditionaledge.png";
 }
+
+QString ldraw::ConditionalEdge::typeName() const
+{
+	return QObject::tr("conditional edge");
+}
--- a/src/linetypes/conditionaledge.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/conditionaledge.h	Sun Mar 13 14:53:14 2022 +0200
@@ -14,4 +14,5 @@
 	Type typeIdentifier() const override;
 	QString toLDrawCode() const override;
 	QString iconName() const override;
+	QString typeName() const override;
 };
--- a/src/linetypes/edge.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/edge.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -34,3 +34,8 @@
 {
 	return ":/icons/linetype-edgeline.png";
 }
+
+QString ldraw::Edge::typeName() const
+{
+	return QObject::tr("edge");
+}
--- a/src/linetypes/edge.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/edge.h	Sun Mar 13 14:53:14 2022 +0200
@@ -15,4 +15,5 @@
 	Type typeIdentifier() const override;
 	QString toLDrawCode() const override;
 	QString iconName() const override;
+	QString typeName() const override;
 };
--- a/src/linetypes/errorline.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/errorline.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -66,3 +66,8 @@
 {
 	return ":/icons/linetype-errorline.png";
 }
+
+QString ldraw::ErrorLine::typeName() const
+{
+	return QObject::tr("error line");
+}
--- a/src/linetypes/errorline.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/errorline.h	Sun Mar 13 14:53:14 2022 +0200
@@ -20,6 +20,7 @@
 	QDataStream& deserialize(QDataStream& stream) override;
 	QString toLDrawCode() const override;
 	QString iconName() const override;
+	QString typeName() const override;
 	QString text;
 	QString message;
 protected:
--- a/src/linetypes/metacommand.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/metacommand.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -61,3 +61,8 @@
 	return ":/icons/chatbubble-ellipses-outline.png";
 }
 
+QString ldraw::MetaCommand::typeName() const
+{
+	return QObject::tr("comment");
+}
+
--- a/src/linetypes/metacommand.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/metacommand.h	Sun Mar 13 14:53:14 2022 +0200
@@ -19,6 +19,7 @@
 	QDataStream& deserialize(QDataStream& stream) override;
 	QString toLDrawCode() const override;
 	QString iconName() const override;
+	QString typeName() const override;
 protected:
 	void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) override;
 };
--- a/src/linetypes/object.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/object.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -161,3 +161,8 @@
 {
 	return "";
 }
+
+QString ldraw::Empty::typeName() const
+{
+	return QObject::tr("empty");
+}
--- a/src/linetypes/object.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/object.h	Sun Mar 13 14:53:14 2022 +0200
@@ -80,6 +80,7 @@
 	virtual Type typeIdentifier() const = 0;
 	virtual QString toLDrawCode() const = 0;
 	virtual QString iconName() const;
+	virtual QString typeName() const = 0;
 
 protected:
 	template<Property property, typename Function>
@@ -146,4 +147,5 @@
 	QString textRepresentation() const override;
 	Type typeIdentifier() const override;
 	QString toLDrawCode() const override;
+	QString typeName() const override;
 };
--- a/src/linetypes/quadrilateral.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/quadrilateral.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -50,3 +50,8 @@
 {
 	return ":/icons/linetype-quadrilateral.png";
 }
+
+QString ldraw::Quadrilateral::typeName() const
+{
+	return QObject::tr("quadrilateral");
+}
--- a/src/linetypes/quadrilateral.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/quadrilateral.h	Sun Mar 13 14:53:14 2022 +0200
@@ -16,4 +16,5 @@
 	Type typeIdentifier() const override;
 	QString toLDrawCode() const override;
 	QString iconName() const override;
+	QString typeName() const override;
 };
--- a/src/linetypes/subfilereference.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/subfilereference.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -143,3 +143,8 @@
 {
 	return ":/icons/linetype-subfile.png";
 }
+
+QString ldraw::SubfileReference::typeName() const
+{
+	return QObject::tr("subfile reference");
+}
--- a/src/linetypes/subfilereference.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/subfilereference.h	Sun Mar 13 14:53:14 2022 +0200
@@ -27,6 +27,7 @@
 	QDataStream& deserialize(QDataStream& stream) override;
 	QString toLDrawCode() const override;
 	QString iconName() const override;
+	QString typeName() const override;
 	glm::mat4 transformation;
 	QString referenceName;
 	bool isInverted = false;
--- a/src/linetypes/triangle.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/triangle.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -47,3 +47,8 @@
 {
 	return ":/icons/linetype-triangle.png";
 }
+
+QString ldraw::Triangle::typeName() const
+{
+	return QObject::tr("triangle");
+}
--- a/src/linetypes/triangle.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/linetypes/triangle.h	Sun Mar 13 14:53:14 2022 +0200
@@ -16,5 +16,6 @@
 	Type typeIdentifier() const override;
 	QString toLDrawCode() const override;
 	QString iconName() const override;
+	QString typeName() const override;
 };
 
--- a/src/mainwindow.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/mainwindow.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -224,6 +224,11 @@
 	return qobject_cast<Document*>(this->ui->tabs->currentWidget());
 }
 
+const Document* MainWindow::currentDocument() const
+{
+	return qobject_cast<const Document*>(this->ui->tabs->currentWidget());
+}
+
 void MainWindow::handleDocumentSplitterChange()
 {
 	Document* currentDocument = this->currentDocument();
@@ -285,24 +290,27 @@
 {
 	if (this->currentDocument() != nullptr)
 	{
-		const ModelId modelId = {0};
-		const QString* path = this->documents.modelPath(modelId);
-		if (path == nullptr or path->isEmpty())
+		const std::optional<ModelId> modelId = this->findCurrentModelId();
+		if (modelId.has_value())
 		{
-			this->actionSaveAs();
-		}
-		else
-		{
-			QString error;
-			QTextStream errorStream{&error};
-			const bool succeeded = this->documents.saveModel(modelId, errorStream);
-			if (not succeeded)
+			const QString* path = this->documents.modelPath(*modelId);
+			if (path == nullptr or path->isEmpty())
 			{
-				QMessageBox::critical(this, tr("Save error"), error);
+				this->actionSaveAs();
 			}
 			else
 			{
-				this->addRecentlyOpenedFile(*path);
+				QString error;
+				QTextStream errorStream{&error};
+				const bool succeeded = this->documents.saveModel(*modelId, errorStream);
+				if (not succeeded)
+				{
+					QMessageBox::critical(this, tr("Save error"), error);
+				}
+				else
+				{
+					this->addRecentlyOpenedFile(*path);
+				}
 			}
 		}
 	}
@@ -315,22 +323,25 @@
 {
 	if (this->currentDocument() != nullptr)
 	{
-		const ModelId modelId = {0};
-		const QString* pathPtr = this->documents.modelPath(modelId);
-		QString defaultPath = (pathPtr != nullptr) ? *pathPtr : "";
-		const QString newPath = QFileDialog::getSaveFileName(
-			this,
-			tr("Save as…"),
-			QFileInfo{defaultPath}.absoluteDir().path(), 
-			tr("LDraw files (*.ldr *dat);;All files (*)")
-		);
-		if (not newPath.isEmpty())
+		const std::optional<ModelId> modelId = this->findCurrentModelId();
+		if (modelId.has_value())
 		{
-			QString error;
-			QTextStream errorStream{&error};
-			this->documents.setModelPath(modelId, newPath, this->libraries, errorStream);
-			this->ui->tabs->setTabText(this->ui->tabs->currentIndex(), QFileInfo{newPath}.fileName());
-			this->actionSave();
+			const QString* pathPtr = this->documents.modelPath(*modelId);
+			QString defaultPath = (pathPtr != nullptr) ? *pathPtr : "";
+			const QString newPath = QFileDialog::getSaveFileName(
+				this,
+				tr("Save as…"),
+				QFileInfo{defaultPath}.absoluteDir().path(), 
+				tr("LDraw files (*.ldr *dat);;All files (*)")
+			);
+			if (not newPath.isEmpty())
+			{
+				QString error;
+				QTextStream errorStream{&error};
+				this->documents.setModelPath(*modelId, newPath, this->libraries, errorStream);
+				this->ui->tabs->setTabText(this->ui->tabs->currentIndex(), QFileInfo{newPath}.fileName());
+				this->actionSave();
+			}
 		}
 	}
 }
@@ -397,6 +408,19 @@
 	}
 }
 
+std::optional<ModelId> MainWindow::findCurrentModelId() const
+{
+	const Document* document = this->currentDocument();
+	if (document != nullptr)
+	{
+		return this->documents.findIdForModel(&document->getModel());
+	}
+	else
+	{
+		return {};
+	}
+}
+
 void MainWindow::changeEvent(QEvent* event)
 {
 	if (event != nullptr)
--- a/src/mainwindow.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/mainwindow.h	Sun Mar 13 14:53:14 2022 +0200
@@ -78,5 +78,7 @@
 	static QString pathToTranslation(const QString& localeCode);
 	void loadColors();
 	Document *currentDocument();
+	const Document *currentDocument() const;
 	void closeDocument(Document* document);
+	std::optional<ModelId> findCurrentModelId() const;
 };
--- a/src/parser.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/parser.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -18,7 +18,6 @@
 
 #include "model.h"
 #include "parser.h"
-#include "linetypes/comment.h"
 #include "linetypes/conditionaledge.h"
 #include "linetypes/edge.h"
 #include "linetypes/errorline.h"
@@ -48,187 +47,6 @@
 	return QString::fromUtf8(this->device.readLine()).trimmed();
 }
 
-/*
- * Parses a single line of the header.
- * Possible parse results:
- *   · ParseSuccess: the header line was parsed successfully.
- *   · ParseFailure: the header line was parsed incorrectly and needs to be handled otherwise.
- *   · StopParsing: the line does not belong in the header and header parsing needs to stop.
- */
-Parser::HeaderParseResult Parser::parseHeaderLine(
-	LDHeader& header,
-	Winding& winding,
-	const QString& line
-) {
-	if (line.isEmpty())
-	{
-		return ParseSuccess;
-	}
-	else if (not line.startsWith("0") or line.startsWith("0 //"))
-	{
-		return StopParsing;
-	}
-	else if (line.startsWith("0 !LDRAW_ORG "))
-	{
-		QStringList tokens = line
-			.mid(strlen("0 !LDRAW_ORG "))
-			.split(" ", Qt::SkipEmptyParts);
-		if (not tokens.isEmpty())
-		{
-			QString partTypeString = tokens[0];
-			// Anything that enters LDForge becomes unofficial in any case if saved.
-			// Therefore we don't need to give the Unofficial type any special
-			// consideration.
-			if (partTypeString.startsWith("Unofficial_"))
-				partTypeString = partTypeString.mid(strlen("Unofficial_"));
-			header.type = headerTypeFromString(partTypeString);
-			header.qualfiers = {};
-			if (tokens.contains("Alias"))
-				header.qualfiers |= LDHeader::Alias;
-			if (tokens.contains("Physical_Color"))
-				header.qualfiers |= LDHeader::PhysicalColour;
-			if (tokens.contains("Flexible_Section"))
-				header.qualfiers |= LDHeader::FlexibleSection;
-			return ParseSuccess;
-		}
-		else
-		{
-			return ParseFailure;
-		}
-	}
-	else if (line == "0 BFC CERTIFY CCW")
-	{
-		winding = Anticlockwise;
-		return ParseSuccess;
-	}
-	else if (line == "0 BFC CERTIFY CW")
-	{
-		winding = Clockwise;
-		return ParseSuccess;
-	}
-	else if (line == "0 BFC NOCERTIFY")
-	{
-		winding = NoWinding;
-		return ParseSuccess;
-	}
-	else if (line.startsWith("0 !HISTORY "))
-	{
-		static const QRegExp historyRegexp {
-			R"(0 !HISTORY\s+(\d{4}-\d{2}-\d{2})\s+)"
-			R"((\{[^}]+|\[[^]]+)[\]}]\s+(.+))"
-		};
-		if (historyRegexp.exactMatch(line))
-		{
-			QString dateString = historyRegexp.capturedTexts().value(1);
-			QString authorWithPrefix = historyRegexp.capturedTexts().value(2);
-			QString description = historyRegexp.capturedTexts().value(3);
-			LDHeader::HistoryEntry historyEntry;
-			historyEntry.date = QDate::fromString(dateString, Qt::ISODate);
-			historyEntry.description = description;
-
-			if (authorWithPrefix[0] == '{')
-				historyEntry.author = authorWithPrefix + "}";
-			else
-				historyEntry.author = authorWithPrefix.mid(1);
-
-			header.history.append(historyEntry);
-			return ParseSuccess;
-		}
-		else
-		{
-			return ParseFailure;
-		}
-	}
-	else if (line.startsWith("0 Author: "))
-	{
-		header.author = line.mid(strlen("0 Author: "));
-		return ParseSuccess;
-	}
-	else if (line.startsWith("0 Name: "))
-	{
-		header.name = line.mid(strlen("0 Name: "));
-		return ParseSuccess;
-	}
-	else if (line.startsWith("0 !HELP "))
-	{
-		if (not header.help.isEmpty())
-			header.help += "\n";
-		header.help += line.mid(strlen("0 !HELP "));
-		return ParseSuccess;
-	}
-	else if (line.startsWith("0 !KEYWORDS "))
-	{
-		if (not header.keywords.isEmpty())
-			header.keywords += "\n";
-		header.keywords += line.mid(strlen("0 !KEYWORDS "));
-		return ParseSuccess;
-	}
-	else if (line.startsWith("0 !CATEGORY "))
-	{
-		header.category = line.mid(strlen("0 !CATEGORY "));
-		return ParseSuccess;
-	}
-	else if (line.startsWith("0 !CMDLINE "))
-	{
-		header.cmdline = line.mid(strlen("0 !CMDLINE "));
-		return ParseSuccess;
-	}
-	else if (line.startsWith(LDHeader::caLicenseString))
-	{
-		header.license = LDHeader::CaLicense;
-		return ParseSuccess;
-	}
-	else if (line.startsWith(LDHeader::nonCaLicenseString))
-	{
-		header.license = LDHeader::NonCaLicense;
-		return ParseSuccess;
-	}
-	else
-	{
-		return ParseFailure;
-	}
-}
-
-/*
- * Parses the header from the device given at construction and returns
- * the resulting header structure.
- */
-LDHeader Parser::parseHeader(Winding& winding)
-{
-	LDHeader header = {};
-	if (not this->device.atEnd())
-	{
-		// Parse the description
-		QString descriptionLine = this->readLine();
-		if (descriptionLine.startsWith("0 "))
-		{
-			header.description = descriptionLine.mid(strlen("0 ")).trimmed();
-			// Parse the rest of the header
-			while (not this->device.atEnd())
-			{
-				const QString& line = this->readLine();
-				auto result = parseHeaderLine(header, winding, line);
-				if (result == ParseFailure)
-				{
-					// Failed to parse this header line, add it as a comment into the body later.
-					this->bag.append(line);
-				}
-				else if (result == StopParsing)
-				{
-					// Header parsing stops, add this line to the body.
-					this->bag.append(line);
-					break;
-				}
-			}
-		}
-		else
-		{
-			this->bag.append(descriptionLine);
-		}
-	}
-	return header;
-}
-
 /**
  * @brief Parses the model body into the given model.
  * @param editor Handle to model edit context
@@ -237,42 +55,22 @@
 {
 	bool invertNext = false;
 	while (not this->device.atEnd())
-		this->bag.append(this->readLine());
-	for (const QString& line : this->bag)
 	{
 		// Some LDraw parts such as 53588.dat can contain "BFC  INVERTNEXT" with multiple inner whitespaces.
 		// So we need to pass the string through QString::simplified to catch these cases.
-		const QString simplifiedLine = line.simplified();
-		if (simplifiedLine == "0 BFC INVERTNEXT" or simplifiedLine == "0 BFC CERTIFY INVERTNEXT")
+		const QString line = this->readLine().simplified();
+		if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT")
 		{
 			invertNext = true;
 			continue;
 		}
-		std::unique_ptr<ldraw::Object> object = parseFromString(line);
-		model.append(std::move(object));
+		model.append(parseFromString(line));
 		if (invertNext)
 		{
 			model[model.size() - 1]->invert();
 		}
 		invertNext = false;
 	}
-	/*
-	// Test quadrilateral splitting by splitting all the quadrilaterals
-	QVector<ldraw::quadrilateralid_t> quadrilateral_ids;
-	for (int i = 0; i < editor.model().size(); i += 1)
-	{
-		const ldraw::id_t id = editor.model().resolve(editor.model().index(i));
-		const ldraw::quadrilateralid_t quad_id = editor.model().checkType<ldraw::Quadrilateral>(id);
-		if (not(quad_id == ldraw::NULL_ID))
-		{
-			quadrilateral_ids.push_back(quad_id);
-		}
-	}
-	for (const ldraw::quadrilateralid_t id : quadrilateral_ids)
-	{
-		ldraw::splitQuadrilateral(editor, id);
-	}
-	*/
 }
 
 static ldraw::Color colorFromString(const QString& colorString)
@@ -347,15 +145,7 @@
 	const QStringList& tokens)
 {
 	Q_UNUSED(tokens)
-	if (line.startsWith("0 //"))
-	{
-		// lol wut
-		return std::make_unique<ldraw::Comment>(line.mid(std::strlen("0 //")).trimmed());
-	}
-	else
-	{
-		return std::make_unique<ldraw::MetaCommand>(line.mid(1).trimmed());
-	}
+	return std::make_unique<ldraw::MetaCommand>(line.mid(1).trimmed());
 }
 
 static std::unique_ptr<ldraw::SubfileReference> parseType1Line(
--- a/src/parser.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/parser.h	Sun Mar 13 14:53:14 2022 +0200
@@ -29,13 +29,9 @@
 public:
 	enum { EndOfModel = -1 };
 	Parser(QIODevice& device, QObject* parent = nullptr);
-	LDHeader parseHeader(Winding& winding);
 	void parseBody(Model &model);
 	static std::unique_ptr<ldraw::Object> parseFromString(QString line);
 private:
-	enum HeaderParseResult {ParseSuccess, ParseFailure, StopParsing};
 	QString readLine();
-	HeaderParseResult parseHeaderLine(LDHeader& header, Winding& winding, const QString& line);
 	QIODevice& device;
-	QStringList bag;
 };
--- a/src/ui/objecteditor.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/ui/objecteditor.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -1,33 +1,108 @@
 #include <QVBoxLayout>
+#include <QFormLayout>
+#include <QPushButton>
+#include <QLabel>
+#include <QSpinBox>
 #include "objecteditor.h"
 #include "document.h"
+#include "modeleditor.h"
+#include "widgets/colorbutton.h"
+#include "widgets/colorindexinput.h"
+
+template<ldraw::Property property>
+static void makeColorEditor()
+{
+	QString propertyName = ldraw::PropertyTraits<property>::name;
+}
 
 ObjectEditor::ObjectEditor(Document* document, const ldraw::id_t id) :
 	QWidget{document},
-	document{document}
+	document{document},
+	formContainer{new QWidget{this}},
+	objectTypeNameLabel{new QLabel{this}},
+	objectTypeIconLabel{new QLabel{this}}
 {
+	this->polygonEditor.emplace(this->document, id);
+	this->setLayout(new QVBoxLayout{this});
+	QWidget* objectTitleLayoutContainer = new QWidget{this};
+	QLayout* objectTitleLayout = new QHBoxLayout{objectTitleLayoutContainer};
+	objectTitleLayoutContainer->setLayout(objectTitleLayout);
+	objectTitleLayout->addWidget(this->objectTypeIconLabel);
+	objectTitleLayout->addWidget(this->objectTypeNameLabel);
+	objectTitleLayout->addWidget(new QSplitter{Qt::Horizontal, this});
+	this->layout()->addWidget(objectTitleLayoutContainer);
+	this->layout()->addWidget(&*this->polygonEditor);
+	this->layout()->addWidget(formContainer);
 	this->setObjectId(id);
-	this->setLayout(new QVBoxLayout{this});
+	
+	QWidget* const parent = this->formContainer;
+	QFormLayout* formLayout = new QFormLayout{parent};
+	this->formContainer->setLayout(formLayout);
+	QLabel* colorLabel = new QLabel{"Color", parent};
+	ColorIndexInput* colorWidget = new ColorIndexInput{document, {0}, parent};
+	formLayout->addRow(colorLabel, colorWidget);
+	connect(colorWidget, &ColorIndexInput::colorChanged, [this](ldraw::Color color)
+	{
+		const QModelIndex index = this->document->getModel().find(this->objectId);
+		if (index.isValid())
+		{
+			this->document->editModel()->modifyObjectAt<ldraw::ColoredObject>(
+				index.row(),
+				[color](ldraw::ColoredObject* object)
+				{
+					object->colorIndex = color;
+				}
+			);
+		}
+	});
+	this->propertyWidgets[ldraw::Property::Color] = {colorLabel, colorWidget};
+}
+
+QString titleCase(const QString& string)
+{
+	return string.left(1).toUpper() + string.mid(1);
 }
 
 void ObjectEditor::setObjectId(const ldraw::id_t id)
 {
 	this->objectId = id;
 	const ldraw::Object* object = this->document->getModel().get(id);
-	if (object != nullptr and object->numPoints() > 0)
+	this->clear();
+	if (object != nullptr)
 	{
-		if (not this->polygonEditor.has_value())
+		this->objectTypeNameLabel->setText("<b>" + titleCase(object->typeName()) + "</b>");
+		this->objectTypeIconLabel->setPixmap(QPixmap{object->iconName()}.scaledToWidth(24));
+		if (object->numPoints() > 0)
 		{
-			this->polygonEditor.emplace(this->document, id);
-			this->layout()->addWidget(&*this->polygonEditor);
+			this->polygonEditor->setObjectId(id);
 		}
 		else
 		{
-			this->polygonEditor->setObjectId(id);
+			this->polygonEditor->clear();
+		}
+		constexpr ldraw::Property prop = ldraw::Property::Color;
+		QVariant color = object->getProperty(prop);
+		this->propertyWidgets[prop].first->setVisible(not color.isNull());
+		this->propertyWidgets[prop].second->setVisible(not color.isNull());
+		if (not color.isNull())
+		{
+			static_cast<ColorIndexInput*>(this->propertyWidgets[prop].second)->setSelectedColor(color.value<ldraw::Color>());
 		}
 	}
 	else
 	{
-		this->polygonEditor.reset();
+		for (auto& pair : this->propertyWidgets)
+		{
+			pair.first->setVisible(false);
+			pair.second->setVisible(false);
+		}
 	}
 }
+
+void ObjectEditor::clear()
+{
+	this->objectTypeNameLabel->clear();
+	this->objectTypeIconLabel->clear();
+	this->polygonEditor->clear();
+	delete this->formContainer->layout();
+}
--- a/src/ui/objecteditor.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/ui/objecteditor.h	Sun Mar 13 14:53:14 2022 +0200
@@ -12,8 +12,13 @@
 public:
 	explicit ObjectEditor(Document* document, ldraw::id_t id = ldraw::NULL_ID);
 	void setObjectId(ldraw::id_t id);
+	void clear();
 private:
 	Document* const document;
 	ldraw::id_t objectId = ldraw::NULL_ID;
 	std::optional<PolygonObjectEditor> polygonEditor;
+	QWidget* formContainer;
+	class QLabel* objectTypeNameLabel;
+	class QLabel* objectTypeIconLabel;
+	QMap<ldraw::Property, QPair<QWidget*, QWidget*>> propertyWidgets;
 };
--- a/src/ui/polygonobjecteditor.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/ui/polygonobjecteditor.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -14,6 +14,7 @@
 	storedObjectId{ldraw::NULL_ID.value}
 {
 	this->splitter.emplace(Qt::Vertical, this);
+	this->buildWidgets();
 	this->setObjectId(id);
 }
 
@@ -30,44 +31,60 @@
 void PolygonObjectEditor::setObjectId(ldraw::id_t id)
 {
 	this->storedObjectId = id;
-	this->buildWidgets();
+	const QModelIndex index = this->document->getModel().find(this->objectId());
+	if (index.isValid())
+	{
+		for (int n : {0, 1, 2, 3})
+		{
+			const ldraw::Object* const object = this->document->getModel()[index.row()];
+			const ldraw::Property property = ldraw::pointProperty(n);
+			const QVariant value = object->getProperty(property);
+			this->widgets[n]->setVisible(value.isValid());
+			this->formLayout->itemAt(n, QFormLayout::LabelRole)->widget()->setVisible(value.isValid());
+			if (value.isValid())
+			{
+				this->widgets[n]->setValue(value.value<glm::vec3>());
+			}
+		}
+	}
+	else
+	{
+		for (int n : {0, 1, 2, 3})
+		{
+			this->widgets[n]->setVisible(false);
+			this->formLayout->itemAt(n, QFormLayout::LabelRole)->widget()->setVisible(false);
+		}
+	}
+}
+
+void PolygonObjectEditor::clear()
+{
+	this->setObjectId(ldraw::NULL_ID);
 }
 
 void PolygonObjectEditor::buildWidgets()
 {
-	this->widgets.clear();
-	delete this->layout();
-	QFormLayout* layout = new QFormLayout{this};
-	this->setLayout(layout);
+	this->formLayout = new QFormLayout{this};
+	this->setLayout(this->formLayout);
 	for (int n : {0, 1, 2, 3})
 	{
 		this->setupPointWidget(n);
 	}
-	for (std::unique_ptr<QWidget>& widget : this->widgets)
+	for (std::unique_ptr<Vec3Editor>& widget : this->widgets)
 	{
 		const QString label = widget->property(LABEL_NAME).toString();
-		layout->addRow(label, widget.get());
+		this->formLayout->addRow(label, widget.get());
 	}
-	layout->addRow("", &*this->splitter);
+	this->formLayout->addRow("", &*this->splitter);
 }
 
 void PolygonObjectEditor::setupPointWidget(int n)
 {
-	const QModelIndex index = this->document->getModel().find(this->objectId());
-	if (index.isValid())
-	{
-		const ldraw::Object* const object = this->document->getModel()[index.row()];
-		const ldraw::Property property = ldraw::pointProperty(n);
-		const QVariant value = object->getProperty(property);
-		if (value.isValid())
-		{
-			std::unique_ptr<Vec3Editor> editor = std::make_unique<Vec3Editor>(value.value<glm::vec3>(), this);
-			QObject::connect(editor.get(), &Vec3Editor::valueChanged, this, &PolygonObjectEditor::pointChanged);
-			editor->setProperty(INDEX_NAME, QVariant::fromValue(n));
-			editor->setProperty(LABEL_NAME, &ldraw::traits(property).name[0]);
-			this->widgets.push_back(std::move(editor));
-		}
-	}
+	std::unique_ptr<Vec3Editor> editor = std::make_unique<Vec3Editor>(glm::vec3{}, this);
+	QObject::connect(editor.get(), &Vec3Editor::valueChanged, this, &PolygonObjectEditor::pointChanged);
+	editor->setProperty(INDEX_NAME, QVariant::fromValue(n));
+	editor->setProperty(LABEL_NAME, &ldraw::traits(ldraw::pointProperty(n)).name[0]);
+	this->widgets.push_back(std::move(editor));
 }
 
 void PolygonObjectEditor::pointChanged(const glm::vec3& value)
--- a/src/ui/polygonobjecteditor.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/ui/polygonobjecteditor.h	Sun Mar 13 14:53:14 2022 +0200
@@ -13,12 +13,14 @@
 	~PolygonObjectEditor();
 	ldraw::id_t objectId() const;
 	void setObjectId(ldraw::id_t id);
+	void clear();
 private:
 	void buildWidgets();
 	void setupPointWidget(int n);
 	Q_SLOT void pointChanged(const glm::vec3& value);
 	Document* document;
 	ldraw::id_t storedObjectId;
-	std::vector<std::unique_ptr<QWidget>> widgets;
+	std::vector<std::unique_ptr<Vec3Editor>> widgets;
 	std::optional<QSplitter> splitter;
+	class QFormLayout* formLayout = nullptr;
 };
--- a/src/uiutilities.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/uiutilities.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -16,6 +16,7 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <QStyleFactory>
 #include "uiutilities.h"
 
 QVector<QAction*> uiutilities::collectActions(QObject* subject)
@@ -42,3 +43,12 @@
 	}
 	return result;
 }
+
+void uiutilities::colorizeWidget(QWidget* widget, const QColor& color)
+{
+	QPalette pal{color};
+	widget->setPalette(pal);
+	widget->setAutoFillBackground(true);
+	widget->setStyle(QStyleFactory::create("Fusion"));
+	widget->update();
+}
\ No newline at end of file
--- a/src/uiutilities.h	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/uiutilities.h	Sun Mar 13 14:53:14 2022 +0200
@@ -24,4 +24,5 @@
 	using KeySequenceMap = QMap<QString, QKeySequence>;
 	QVector<QAction*> collectActions(QObject* subject);
 	KeySequenceMap makeKeySequenceMap(const QVector<QAction*>& actions);
+	void colorizeWidget(QWidget* widget, const QColor& color);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/colorindexinput.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -0,0 +1,43 @@
+#include "colorindexinput.h"
+#include "ui_colorindexinput.h"
+#include "colorselectdialog.h"
+#include "uiutilities.h"
+
+ColorIndexInput::ColorIndexInput(Document *document, ldraw::Color color, QWidget *parent) :
+	QWidget{parent},
+	document{document},
+	ui{*new Ui_ColorIndexInput}
+{
+	this->ui.setupUi(this);
+	connect(this->ui.button, &QPushButton::clicked, [this]()
+	{
+		ColorSelectDialog dialog{this->document->colorTable, this->document};
+		const int result = dialog.exec();
+		if (result == QDialog::Accepted)
+		{
+			this->ui.index->setValue(dialog.currentColor().index);
+		}
+	});
+	connect(this->ui.index, qOverload<int>(&QSpinBox::valueChanged), [this](int value)
+	{
+		this->ui.button->setText(ldraw::colorDisplayName({value}, this->document->colorTable));
+		uiutilities::colorizeWidget(this->ui.button, ldraw::colorFace({value}, this->document->colorTable));
+		Q_EMIT this->colorChanged({value});
+	});
+	this->ui.index->setValue(color.index);
+}
+
+ColorIndexInput::~ColorIndexInput()
+{
+	delete &this->ui;
+}
+
+ldraw::Color ColorIndexInput::selectedColor() const
+{
+	return {this->ui.index->value()};
+}
+
+void ColorIndexInput::setSelectedColor(ldraw::Color color)
+{
+	this->ui.index->setValue(color.index);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/colorindexinput.h	Sun Mar 13 14:53:14 2022 +0200
@@ -0,0 +1,17 @@
+#pragma once
+#include "document.h"
+
+class ColorIndexInput : public QWidget
+{
+	Q_OBJECT
+public:
+	ColorIndexInput(Document *document, ldraw::Color color = ldraw::MAIN_COLOR, QWidget *parent = nullptr);
+	~ColorIndexInput();
+	ldraw::Color selectedColor() const;
+	void setSelectedColor(ldraw::Color color);
+Q_SIGNALS:
+	void colorChanged(ldraw::Color color);
+private:
+	Document* const document;
+	class Ui_ColorIndexInput& ui;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/colorindexinput.ui	Sun Mar 13 14:53:14 2022 +0200
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ColorIndexInput</class>
+ <widget class="QWidget" name="ColorIndexInput">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>370</width>
+    <height>47</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout_2">
+   <item>
+    <widget class="QSpinBox" name="index">
+     <property name="suffix">
+      <string/>
+     </property>
+     <property name="maximum">
+      <number>2147483647</number>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QPushButton" name="button">
+     <property name="text">
+      <string>…</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <spacer name="horizontalSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>40</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
--- a/src/widgets/colorselectdialog.cpp	Fri Mar 11 20:19:26 2022 +0200
+++ b/src/widgets/colorselectdialog.cpp	Sun Mar 13 14:53:14 2022 +0200
@@ -1,7 +1,9 @@
 #include <QColorDialog>
 #include <QTableView>
+#include <QStyleFactory>
 #include "colorselectdialog.h"
 #include "ui_colorselectdialog.h"
+#include "uiutilities.h"
 
 ColorSelectDialog::ColorSelectDialog(const ldraw::ColorTable& colorTable, QWidget *parent) :
 	QDialog{parent},
@@ -29,14 +31,6 @@
 	return {button->property("_colorIndex").value<qint32>()};
 }
 
-QString styleSheetForColor(const QColor& color)
-{
-	QColor const textColor = (luma(color) < 0.4) ? Qt::white : Qt::black;
-	return QString{"background-color: %1; color: %2;"}
-		.arg(color.name())
-		.arg(textColor.name());
-}
-
 void ColorSelectDialog::makeColorButtons()
 {
 	this->buttons.reserve(this->colorTable.size());
@@ -48,8 +42,9 @@
 		const qint32 index = iterator->first;
 		const ldraw::ColorDefinition& colordef = iterator->second;
 		QPushButton* const button = new QPushButton{QString::number(index), this};
+		button->setMinimumSize({40, 40});
 		button->setToolTip(colordef.displayName);
-		button->setStyleSheet(styleSheetForColor(colordef.faceColor));
+		uiutilities::colorizeWidget(button, ldraw::colorFace({index}, colorTable));
 		button->setProperty("_colorIndex", index);
 		button->setCheckable(true);
 		connect(button, &QAbstractButton::clicked, this, &ColorSelectDialog::handleButtonClick);
@@ -95,17 +90,8 @@
 
 void ColorSelectDialog::updateSelectedColorTexts()
 {
-	if (ldraw::isDirectColor(this->selectedColor))
-	{
-		this->ui.selectedColorName->setText(ldraw::directColorFace(this->selectedColor).name());
-	}
-	else
-	{
-		const ldraw::ColorDefinition& colordef = this->colorTable[this->selectedColor];
-		this->ui.selectedColorName->setText(colordef.displayName);
-	}
-	const QColor colorFace = ldraw::colorFace(this->selectedColor, this->colorTable);
-	this->ui.selectedColorName->setStyleSheet(styleSheetForColor(colorFace));
+	this->ui.selectedColorName->setText(ldraw::colorDisplayName(this->selectedColor, this->colorTable));
+	uiutilities::colorizeWidget(this->ui.selectedColorName, ldraw::colorFace(this->selectedColor, colorTable));
 	this->ui.colorIndex->setValue(this->selectedColor.index);
 	for (QPushButton* button : this->buttons)
 	{
@@ -157,3 +143,8 @@
 	this->selectedColor = color;
 	this->updateSelectedColorTexts();
 }
+
+ldraw::Color ColorSelectDialog::currentColor() const
+{
+	return this->selectedColor;
+}

mercurial