src/libraries.cpp

Sun, 03 Jul 2022 14:35:06 +0300

author
Teemu Piippo <teemu.s.piippo@gmail.com>
date
Sun, 03 Jul 2022 14:35:06 +0300
changeset 320
af6633412a6c
parent 300
3a4b132b8353
child 380
16f6717a218b
permissions
-rw-r--r--

Cleanup polygonize a bit

/*
 *  LDForge: LDraw parts authoring CAD
 *  Copyright (C) 2013 - 2020 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 <QSettings>
#include "src/libraries.h"
#include "src/settings.h"

/**
 * @brief Constructs a new library manager
 * @param parent Parent object
 */
LibrariesModel::LibrariesModel(QObject* parent):
	QAbstractTableModel{parent}
{
}

/**
 * @brief Searches the libraries for the specified file name.
 * @param fileName File to search for
 * @return Full path to the file, or empty string if not found.
 */
QString LibrariesModel::findFile(QString fileName) const
{
	QString path;
	fileName.replace("\\", "/");
	bool found = false;
	for (const Library& library : this->libraries)
	{
		for (const QString& subdirectory : {"parts", "p"})
		{
			QDir directory = library.path;
			directory.cd(subdirectory);
			QFileInfo fileInfo{directory.absoluteFilePath(fileName)};
			if (fileInfo.exists() && fileInfo.isFile())
			{
				path = fileInfo.absoluteFilePath();
				found = true;
				break;
			}
		}
		if (found)
		{
			break;
		}
	}
	return path;
}

/**
 * @brief Adds a new library to the end of the libraries list.
 * @param library Library to add
 */
void LibrariesModel::addLibrary(const Library& library)
{
	Q_EMIT layoutAboutToBeChanged();
	this->libraries.push_back(library);
	Q_EMIT layoutChanged();
}

/**
 * @brief Removes a library by index. Does nothing if the index is not valid.
 * @param libraryIndex Index of the library
 */
void LibrariesModel::removeLibrary(const index_t libraryIndex)
{
	Q_ASSERT(this->isValidIndex(libraryIndex));
	if (this->isValidIndex(libraryIndex))
	{
		Q_EMIT layoutAboutToBeChanged();
		this->libraries.erase(this->libraries.begin() + static_cast<long>(libraryIndex));
		Q_EMIT layoutChanged();
	}
}

/**
 * @brief Gets a library by index.
 * @warning Index is assumed to be valid.
 * @param libraryIndex Index of the library
 * @return const reference
 */
const Library& LibrariesModel::library(index_t libraryIndex) const
{
	Q_ASSERT(this->isValidIndex(libraryIndex));
	return this->libraries[libraryIndex];
}

/**
 * @brief Changes the path of the specified library
 * @param libraryIndex Index of the library
 * @param path New path
 */
void LibrariesModel::setLibraryPath(index_t libraryIndex, const QDir& path)
{
	if (this->isValidIndex(libraryIndex))
	{
		this->libraries[libraryIndex].path = path;
		this->signalLibraryChange(libraryIndex);
	}
}

/**
 * @brief Changes the role of the specified library
 * @param libraryIndex Index of the library
 * @param role New role
 */
void LibrariesModel::setLibraryRole(index_t libraryIndex, const Library::Role role)
{
	if (this->isValidIndex(libraryIndex))
	{
		this->libraries[libraryIndex].role = role;
		this->signalLibraryChange(libraryIndex);
	}
}

/**
 * @brief Restores the libraries from the specified settings object. All unsaved
 * changes are lost.
 * @param settings Settings object to restore from.
 */
void LibrariesModel::restoreFromSettings()
{
	this->libraries = setting<Setting::Libraries>();
}

/**
 * @brief Saves the libraries to the specified settings object.
 * @param settings Settings object to modify.
 */
void LibrariesModel::storeToSettings()
{
	setSetting<Setting::Libraries>(this->libraries);
}

/**
 * @returns the amount of libraries
 */
index_t LibrariesModel::count() const
{
	return this->libraries.size();
}

void LibrariesModel::moveLibrary(const index_t libraryFromIndex, const index_t libraryToIndex)
{
	if (isValidIndex(libraryFromIndex) and
		(isValidIndex(libraryToIndex) or libraryToIndex == count()) and
		libraryFromIndex != libraryToIndex)
	{
		Q_EMIT layoutAboutToBeChanged();
		const Library library = this->library(libraryFromIndex);
		if (libraryToIndex > libraryFromIndex)
		{
			this->libraries.insert(libraryToIndex, library);
			this->libraries.removeAt(libraryFromIndex);
		}
		else if (libraryToIndex < libraryFromIndex)
		{
			this->libraries.removeAt(libraryFromIndex);
			this->libraries.insert(libraryToIndex, library);
		}
		Q_EMIT layoutChanged();
	}
}

/**
 * @brief Checks whether the specified index points to a valid library.
 * @param libraryIndex Index to check
 * @returns whether or not it is valid
 */
bool LibrariesModel::isValidIndex(const index_t libraryIndex) const
{
	return libraryIndex >= 0 && libraryIndex < this->libraries.size();
}

/**
 * @brief Iterates over libraries and loads LDConfig.ldr from each of them.
 * @param errors Where to stream any encountered errors
 * @return color table
 */
ColorTable LibrariesModel::loadColorTable(QTextStream& errors) const
{
	ColorTable result;
	for (const Library& library : this->libraries)
	{
		const QString path = library.path.filePath("LDConfig.ldr");
		QFile file{path};
		if (file.open(QIODevice::ReadOnly | QIODevice::Text))
		{
			const auto loadedTable = ::loadColorTable(file, errors);
			if (loadedTable) {
				result = std::move(*loadedTable);
			}
			break;
		}
	}
	return result;
}

/**
 * @brief Gets a human-readable string for the specified role
 * @param role Role to get a string for
 * @returns string
 */
QString Library::libraryRoleName(const Role role)
{
	switch (role)
	{
	case Library::OfficialLibrary:
		return LibrariesModel::tr("Official library");
	case Library::UnofficialLibrary:
		return LibrariesModel::tr("Unofficial library");
	case Library::WorkingLibrary:
		return LibrariesModel::tr("Working library");
	}
	return "???";
}

/**
 * @brief Overload necessary to implement QAbstractTableModel
 * @param index
 * @return Item flags
 */
Qt::ItemFlags LibrariesModel::flags(const QModelIndex& index) const
{
	Q_UNUSED(index);
	return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

/**
 * @brief Returns data needed to represent the libraries in a table. This function
 * decides how data is represented in a table view.
 * @param index Index to get data for
 * @param role Role of the data (role as in Qt model-view role, not LDraw library role)
 * @returns variant
 */
QVariant LibrariesModel::data(const QModelIndex& index, int role) const
{
	if (role != Qt::DisplayRole)
	{
		return {};
	}
	else
	{
		const Column column = static_cast<Column>(index.column());
		const Library& library = this->library(index.row());
		switch (column)
		{
		case RoleColumn:
			return Library::libraryRoleName(library.role);
		case PathColumn:
			return library.path.absolutePath();
		}
		return {};
	}
}

/**
 * @brief Returns header texts for a table view. We only implement column headers here.
 * @param section Index of the column (can also be a row but we only have column headers)
 * @param orientation Orientation (we only support horizontal orientation)
 * @param role Role of the data (role as in Qt model-view role, not LDraw library role)
 * @returns variant
 */
QVariant LibrariesModel::headerData(int section, Qt::Orientation orientation, int role) const
{
	if (orientation == Qt::Horizontal and role == Qt::DisplayRole)
	{
		switch(static_cast<Column>(section))
		{
		case PathColumn:
			return tr("Path");
		case RoleColumn:
			return tr("Role");
		}
		return {};
	}
	else
	{
		return {};
	}
}

/**
 * @brief Overload necessary to implement QAbstractTableModel.
 * @returns how many rows there are in the table. Since there is one row per library, this returns
 * the amount of libraries.
 */
int LibrariesModel::rowCount(const QModelIndex&) const
{
	return static_cast<int>(this->count());
}

/**
 * @brief LibraryManager::columnCount
 * @returns how many columns there are in the table.
 */
int LibrariesModel::columnCount(const QModelIndex&) const
{
	return 2;
}

/**
 * @brief Signals view objects that the specified library has changed. This is necessary
 * to update the table widget know when libraries are changed.
 * @param libraryIndex Index of the library.
 */
void LibrariesModel::signalLibraryChange(const index_t libraryIndex)
{
	Q_ASSERT(isValidIndex(libraryIndex));
	const QModelIndex topLeft = this->index(static_cast<int>(libraryIndex), 0);
	const QModelIndex bottomRight = this->index(static_cast<int>(libraryIndex), columnCount({}) - 1);
	Q_EMIT dataChanged(topLeft, bottomRight);
}

/**
 * @brief Overload for operator<< to allow serializing a library into the configuration file.
 * @param stream Stream to write the library into
 * @param library Library to write into the stream
 * @returns the stream
 */
QDataStream& operator<<(QDataStream& stream, const Library& library)
{
	const QString path = library.path.absolutePath();
	const int role = static_cast<int>(library.role);
	return stream << path << role;
}

/**
 * @brief Overload for operator>> to allow serializing a library into the configuration file.
 * @param stream Stream to read the library from
 * @param library Library to obtain from the stream
 * @returns the stream
 */
QDataStream& operator>>(QDataStream& stream, Library& library)
{
	QString path;
	int role;
	QDataStream& result = stream >> path >> role;
	library.path.setPath(path);
	library.role = static_cast<Library::Role>(role);
	return result;
}

bool operator==(const Library& one, const Library& other)
{
	return one.role == other.role and one.path == other.path;
}

mercurial