src/libraries.cpp

Fri, 01 Jul 2022 16:46:43 +0300

author
Teemu Piippo <teemu.s.piippo@gmail.com>
date
Fri, 01 Jul 2022 16:46:43 +0300
changeset 312
2637134bc37c
parent 300
3a4b132b8353
child 380
16f6717a218b
permissions
-rw-r--r--

Fix right click to delete not really working properly
Instead of removing the point that had been added, it would remove
the point that is being drawn, which would cause it to overwrite the
previous point using the new point, causing a bit of a delay

/*
 *  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