src/libraries.cpp

Mon, 06 Jun 2022 22:01:22 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Mon, 06 Jun 2022 22:01:22 +0300
changeset 200
ca23936b455b
parent 112
5760cbb32bc0
child 201
5d201ee4a9c3
permissions
-rw-r--r--

Giant refactor

/*
 *  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 "libraries.h"
#include "configuration.h"

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

/**
 * @brief Constructs a library manager from settings
 * @param settings Settings to construct from
 * @param parent Parent object
 */
LibraryManager::LibraryManager(Configuration* settings, QObject* parent) :
	QAbstractTableModel{parent}
{
	this->restoreFromSettings(settings);
}

/**
 * @brief Yields a begin-terator for the libraries
 * @return iterator
 */
QVector<Library>::const_iterator LibraryManager::begin() const
{
	return this->libraries.begin();
}

/**
 * @brief Yields an end-iterator for the libraries
 * @return iterator
 */
QVector<Library>::const_iterator LibraryManager::end() const
{
	return this->libraries.end();
}

/**
 * @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 LibraryManager::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 LibraryManager::addLibrary(const Library& library)
{
	Q_EMIT layoutAboutToBeChanged();
	libraries.append(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 LibraryManager::removeLibrary(const int libraryIndex)
{
	Q_ASSERT(isValidIndex(libraryIndex));
	if (isValidIndex(libraryIndex))
	{
		Q_EMIT layoutAboutToBeChanged();
		libraries.remove(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& LibraryManager::library(int libraryIndex) const
{
	Q_ASSERT(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 LibraryManager::setLibraryPath(int 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 LibraryManager::setLibraryRole(int 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 LibraryManager::restoreFromSettings(Configuration* settings)
{
	this->libraries = settings->libraries();
}

/**
 * @brief Saves the libraries to the specified settings object.
 * @param settings Settings object to modify.
 */
void LibraryManager::storeToSettings(Configuration* settings)
{
	settings->setLibraries(this->libraries);
}

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

void LibraryManager::moveLibrary(const int libraryFromIndex, const int 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 LibraryManager::isValidIndex(const int 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
 */
ldraw::ColorTable LibraryManager::loadColorTable(QTextStream& errors)
{
	ldraw::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))
		{
			(void) result.load(file, errors);
		}
	}
	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 LibraryManager::tr("Official library");
	case Library::UnofficialLibrary:
		return LibraryManager::tr("Unofficial library");
	case Library::WorkingLibrary:
		return LibraryManager::tr("Working library");
	}
	return "???";
}

/**
 * @brief Overload necessary to implement QAbstractTableModel
 * @param index
 * @return Item flags
 */
Qt::ItemFlags LibraryManager::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 LibraryManager::data(const QModelIndex& index, int role) const
{
	if (role != Qt::DisplayRole)
	{
		return {};
	}
	else
	{
		const int row = index.row();
		const Column column = static_cast<Column>(index.column());
		const Library& library = this->library(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 LibraryManager::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 LibraryManager::rowCount(const QModelIndex&) const
{
	return this->count();
}

/**
 * @brief LibraryManager::columnCount
 * @returns how many columns there are in the table.
 */
int LibraryManager::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 LibraryManager::signalLibraryChange(int libraryIndex)
{
	Q_ASSERT(isValidIndex(libraryIndex));
	const QModelIndex topLeft = this->index(libraryIndex, 0);
	const QModelIndex bottomRight = this->index(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