Sun, 09 Apr 2023 16:27:22 +0300
Also connect up "Select all"
/* * 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; }