src/documentmanager.cpp

Thu, 04 Jan 2018 22:52:04 +0200

author
Santeri Piippo
date
Thu, 04 Jan 2018 22:52:04 +0200
changeset 1224
5a31b6d4bf81
parent 1222
34def2630300
child 1231
ce0c9f2e6b9c
permissions
-rw-r--r--

undo config change

/*
 *  LDForge: LDraw parts authoring CAD
 *  Copyright (C) 2013 - 2018 Teemu Piippo
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <QApplication>
#include <QFileInfo>
#include <QFile>
#include "documentmanager.h"
#include "ldDocument.h"
#include "mainwindow.h"
#include "partdownloader.h"
#include "documentloader.h"
#include "glRenderer.h"

ConfigOption(QStringList RecentFiles)
ConfigOption(bool TryDownloadMissingFiles = false)

enum
{
	MaxRecentFiles = 10
};

DocumentManager::DocumentManager(QObject* parent) :
	QObject(parent),
	HierarchyElement(parent),
	m_loadingMainFile(false),
	m_isLoadingLogoedStuds(false),
	m_logoedStud(nullptr),
	m_logoedStud2(nullptr) {}

DocumentManager::~DocumentManager()
{
	clear();
}

void DocumentManager::clear()
{
	for (LDDocument* document : m_documents)
	{
		document->close();
		delete document;
	}

	m_documents.clear();
}

LDDocument* DocumentManager::getDocumentByName(QString filename)
{
	LDDocument* doc = findDocumentByName(filename);

	if (doc == nullptr)
	{
		bool tmp = m_loadingMainFile;
		m_loadingMainFile = false;
		doc = openDocument(filename, true, true);
		m_loadingMainFile = tmp;
	}

	return doc;
}

void DocumentManager::openMainModel(QString path)
{
	// If there's already a file with the same name, this file must replace it.
	LDDocument* documentToReplace = nullptr;
	LDDocument* file = nullptr;
	QString shortName = LDDocument::shortenName(path);

	for (LDDocument* doc : m_documents)
	{
		if (doc->name() == shortName)
		{
			documentToReplace = doc;
			break;
		}
	}

	// We cannot open this file if the document this would replace is not
	// safe to close.
	if (documentToReplace and not documentToReplace->isSafeToClose())
		return;

	m_loadingMainFile = true;

	// If we're replacing an existing document, clear the document and
	// make it ready for being loaded to.
	if (documentToReplace)
	{
		file = documentToReplace;
		file->clear();
	}

	bool aborted;
	file = openDocument(path, false, false, file, &aborted);

	if (file == nullptr)
	{
		if (not aborted)
		{
			// Tell the user loading failed.
			setlocale(LC_ALL, "C");
			Critical(format(tr("Failed to open %1: %2"), path, strerror(errno)));
		}

		m_loadingMainFile = false;
		return;
	}

	file->openForEditing();
	m_window->closeInitialDocument();
	m_window->changeDocument(file);
	m_window->doFullRefresh();
	addRecentFile(path);
	m_loadingMainFile = false;

	// If there were problems loading subfile references, try see if we can find these
	// files on the parts tracker.
	QStringList unknowns;

	for (LDObject* obj : file->objects())
	{
		if (obj->type() != OBJ_Error or static_cast<LDError*>(obj)->fileReferenced().isEmpty())
			continue;

		unknowns << static_cast<LDError*>(obj)->fileReferenced();
	}

	if (config->tryDownloadMissingFiles() and not unknowns.isEmpty())
	{
		PartDownloader dl(m_window);
		dl.setSourceType(PartDownloader::PartsTracker);
		dl.setPrimaryFile(file);

		for (QString const& unknown : unknowns)
			dl.downloadFromPartsTracker(unknown);

		dl.exec();
		dl.checkIfFinished();
		file->reloadAllSubfiles();
	}
}

LDDocument* DocumentManager::findDocumentByName(QString name)
{
	for (LDDocument* document : m_documents)
	{
		if (isOneOf(name, document->name(), document->defaultName()))
			return document;
	}

	return nullptr;
}

QString Dirname(QString path)
{
	int lastpos = path.lastIndexOf(DIRSLASH);

	if (lastpos > 0)
		return path.left(lastpos);

#ifndef _WIN32
	if (path[0] == DIRSLASH_CHAR)
		return DIRSLASH;
#endif // _WIN32

	return "";
}

QString Basename(QString path)
{
	int lastpos = path.lastIndexOf(DIRSLASH);

	if (lastpos != -1)
		return path.mid(lastpos + 1);

	return path;
}

QString DocumentManager::findDocumentPath(QString relativePath, bool subdirs)
{
	// LDraw models use backslashes as path separators. Replace those into forward slashes for Qt.
	relativePath.replace("\\", "/");

	// Try find it relative to other currently open documents. We want a file in the immediate vicinity of a current
	// part model to override stock LDraw stuff.
	QString relativeTopDir = Basename(Dirname(relativePath));

	for (LDDocument* document : m_documents)
	{
		QString partpath = format("%1/%2", Dirname(document->fullPath()), relativePath);
		QFileInfo fileinfo(partpath);

		if (fileinfo.exists())
		{
			// Ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48
			QString partTopDir = Basename(Dirname(partpath));

			for (QString subdir : g_specialSubdirectories)
			{
				if ((partTopDir == subdir) != (relativeTopDir == subdir))
					goto skipthis;
			}

			return partpath;
		}
skipthis:
		continue;
	}

	if (QFileInfo::exists(relativePath))
		return relativePath;

	// Try with just the LDraw path first
	QString fullPath = format("%1" DIRSLASH "%2", config->lDrawPath(), relativePath);

	if (QFileInfo::exists(fullPath))
		return fullPath;

	if (subdirs)
	{
		// Look in sub-directories: parts and p. Also look in the download path, since that's where we download parts
		// from the PT to.
		QStringList dirs = { config->lDrawPath(), config->downloadFilePath() };
		for (const QString& topdir : dirs)
		{
			for (const QString& subdir : QStringList({ "parts", "p" }))
			{
				fullPath = format("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relativePath);

				if (QFile::exists(fullPath))
					return fullPath;
			}
		}
	}

	// Did not find the file.
	return "";
}

QFile* DocumentManager::openLDrawFile(QString relpath, bool subdirs, QString* pathpointer)
{
	print("Opening %1...\n", relpath);
	QString path = findDocumentPath(relpath, subdirs);

	if (pathpointer)
		*pathpointer = path;

	if (path.isEmpty())
		return nullptr;

	QFile* fp = new QFile(path);

	if (fp->open(QIODevice::ReadOnly))
		return fp;

	fp->deleteLater();
	return nullptr;
}

LDObjectList DocumentManager::loadFileContents(QFile* fp, int* numWarnings, bool* ok)
{
	LDObjectList objs;

	if (numWarnings)
		*numWarnings = 0;

	DocumentLoader* loader = new DocumentLoader(m_loadingMainFile);
	loader->read(fp);
	loader->start();

	// After start() returns, if the loader isn't done yet, it's delaying
	// its next iteration through the event loop. We need to catch this here
	// by telling the event loop to tick, which will tick the file loader again.
	// We keep doing this until the file loader is ready.
	while (not loader->isDone())
		qApp->processEvents();

	// If we wanted the success value, supply that now
	if (ok)
		*ok = not loader->hasAborted();

	objs = loader->objects();
	delete loader;
	return objs;
}

LDDocument* DocumentManager::openDocument(QString path, bool search, bool implicit, LDDocument* fileToOverride, bool* aborted)
{
	// Convert the file name to lowercase when searching because some parts contain subfile
	// subfile references with uppercase file names. I'll assume here that the library will always
	// use lowercase file names for the part files.
	QFile* fp;
	QString fullpath;

	if (search)
	{
		fp = openLDrawFile(path.toLower(), true, &fullpath);
	}
	else
	{
		fp = new QFile(path);
		fullpath = path;

		if (not fp->open(QIODevice::ReadOnly))
		{
			delete fp;
			return nullptr;
		}
	}

	if (not fp)
		return nullptr;

	LDDocument* load = (fileToOverride ? fileToOverride : m_window->newDocument(implicit));
	load->setFullPath(fullpath);
	load->setName(LDDocument::shortenName(load->fullPath()));

	// Loading the file shouldn't count as actual edits to the document.
	load->history()->setIgnoring(true);

	int numWarnings;
	bool ok;
	LDObjectList objs = loadFileContents(fp, &numWarnings, &ok);
	fp->close();
	fp->deleteLater();

	if (aborted)
		*aborted = ok == false;

	if (not ok)
	{
		load->close();
		return nullptr;
	}

	load->addObjects(objs);

	if (m_loadingMainFile)
	{
		m_window->changeDocument(load);
		m_window->renderer()->setDocument(load);
		print(tr("File %1 parsed successfully(%2 errors)."), path, numWarnings);
	}

	load->history()->setIgnoring(false);
	return load;
}

void DocumentManager::addRecentFile(QString path)
{
	QStringList recentFiles = config->recentFiles();
	int idx = recentFiles.indexOf(path);

	// If this file already is in the list, pop it out.
	if (idx != -1)
	{
		if (idx == recentFiles.size() - 1)
			return; // first recent file - abort and do nothing

		recentFiles.removeAt(idx);
	}

	// If there's too many recent files, drop one out.
	while (recentFiles.size() >(MaxRecentFiles - 1))
		recentFiles.removeAt(0);

	// Add the file
	recentFiles << path;
	config->setRecentFiles(recentFiles);
	m_window->syncSettings();
	m_window->updateRecentFilesMenu();
}

bool DocumentManager::isSafeToCloseAll()
{
	for (LDDocument* document : m_documents)
	{
		if (not document->isSafeToClose())
			return false;
	}

	return true;
}

void DocumentManager::loadLogoedStuds()
{
	if (m_isLoadingLogoedStuds or(m_logoedStud and m_logoedStud2))
		return;

	m_isLoadingLogoedStuds = true;
	m_logoedStud = openDocument("stud-logo.dat", true, true);
	m_logoedStud2 = openDocument("stud2-logo.dat", true, true);
	m_isLoadingLogoedStuds = false;

	if (m_logoedStud and m_logoedStud2)
		print(tr("Logoed studs loaded.\n"));
}

bool DocumentManager::preInline(LDDocument* doc, LDObjectList& objs, bool deep, bool renderinline)
{
	// Possibly substitute with logoed studs:
	// stud.dat -> stud-logo.dat
	// stud2.dat -> stud-logo2.dat
	if (config->useLogoStuds() and renderinline)
	{
		// Ensure logoed studs are loaded first
		loadLogoedStuds();

		if (doc->name() == "stud.dat" and m_logoedStud)
		{
			objs = m_logoedStud->inlineContents(deep, renderinline);
			return true;
		}
		else if (doc->name() == "stud2.dat" and m_logoedStud2)
		{
			objs = m_logoedStud2->inlineContents(deep, renderinline);
			return true;
		}
	}
	return false;
}

LDDocument* DocumentManager::createNew()
{
	LDDocument* document = new LDDocument(this);
	m_documents.insert(document);
	return document;
}

mercurial