Wed, 25 May 2022 20:36:34 +0300
Fix pick() picking from weird places on the screen with high DPI scaling
glReadPixels reads data from the frame buffer, which contains data after
high DPI scaling, so any reads to that need to take this scaling into account
/* * 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 <QBrush> #include <QFile> #include <QFileInfo> #include <QFont> #include <QSaveFile> #include "model.h" #include "modeleditor.h" #include "documentmanager.h" /** * @brief Constructs a model * @param parent QObject parent to pass forward */ Model::Model(QObject *parent) : QAbstractListModel{parent} { } /** * @returns the amount of elements in the model */ int Model::size() const { return static_cast<int>(this->body.size()); } /** * @brief Looks up the object ID at the specified index. If out of bounds, returns NULL_ID. * @param index Index of object to look up * @return object ID */ ldraw::id_t Model::at(int index) const { if (index >= 0 and index < this->size()) { return this->body[index]->id; } else { return ldraw::NULL_ID; } } /** * @brief @overload QAbstractListModel::rowCount * @return size */ int Model::rowCount(const QModelIndex&) const { return this->size(); } /** * @brief @overload QAbstractListModel::data * @param index * @param role * @return QVariant */ QVariant Model::data(const QModelIndex& index, int role) const { const ldraw::Object* object = (*this)[index.row()]; switch(role) { case Qt::DecorationRole: return QPixmap{object->iconName()}.scaledToHeight(24); case Qt::DisplayRole: return object->textRepresentation(); case Qt::ForegroundRole: return object->textRepresentationForeground(); case Qt::BackgroundRole: return object->textRepresentationBackground(); case Qt::FontRole: return object->textRepresentationFont(); default: return {}; } } /** * @brief Finds the position of the specified object in the model * @param id Object id to look for * @return model index */ QModelIndex Model::find(ldraw::id_t id) const { if (this->needObjectsByIdRebuild) { this->objectsById.clear(); for (std::size_t i = 0; i < this->body.size(); ++i) { this->objectsById[this->body[i]->id] = i; } this->needObjectsByIdRebuild = false; } const auto it = this->objectsById.find(id); if (it != this->objectsById.end()) { return this->index(it->second); } else { return {}; } } /** * @brief Gets an object id by position in the model * @param index Position of the object in the model * @return id */ ldraw::id_t Model::idAt(const QModelIndex& index) const { return (*this)[index.row()]->id; } #if 0 /** * @brief Sets the path to the model * @param path New path to use */ void Model::setPath(const QString &path) { this->storedPath = path; this->header.name = QFileInfo{path}.fileName(); // Update the "Name: 1234.dat" comment if (this->body.size() >= 2) { const ldraw::id_t id = this->body[1]->id; if (this->isA<ldraw::MetaCommand>(id)) { const QString& textBody = this->body[1]->getProperty<ldraw::Property::Text>(); if (textBody.startsWith("Name: ")) { auto editor = this->edit(); editor.setObjectProperty<ldraw::Property::Text>(id, "Name: " + this->header.name); } } } } #endif /** * @brief Adds the given object into the model. * @param object r-value reference to the object */ ldraw::id_t Model::append(ModelObjectPointer&& object) { const int position = static_cast<int>(this->body.size()); Q_EMIT this->beginInsertRows({}, position, position); this->body.push_back(std::move(object)); Q_EMIT this->endInsertRows(); const ldraw::id_t id = this->body.back()->id; this->objectsById[id] = this->body.size() - 1; return id; } /** * @brief Removes the object at the specified position * @param position */ void Model::remove(int position) { if (position >= 0 and position < signed_cast(this->body.size())) { Q_EMIT this->beginRemoveRows({}, position, position); this->body.erase(std::begin(this->body) + position); this->needObjectsByIdRebuild = true; Q_EMIT this->endRemoveRows(); } } void Model::emitDataChangedSignal(int position) { Q_EMIT this->dataChanged(this->index(position), this->index(position)); } /** * @brief Gets the object pointer at the specified position * @param index Position of the object * @returns object pointer */ ldraw::Object* Model::operator[](int index) { if (index >= 0 and index < this->size()) { return this->body[index].get(); } else { throw std::out_of_range{"index out of range"}; } } /** * @brief Gets the object pointer at the specified position * @param index Position of the object * @returns object pointer */ const ldraw::Object* Model::operator[](int index) const { if (index >= 0 and index < this->size()) { return this->body[index].get(); } else { throw std::out_of_range{"index out of range"}; } } /** * @brief Gets an object pointer by id. Used by the editing context to actually modify objects. * @param id * @return object pointer */ ldraw::Object* Model::findObjectById(const ldraw::id_t id) { const QModelIndex index = this->find(id); if (index.isValid()) { return (*this)[index.row()]; } else { return nullptr; } } const ldraw::Object* Model::findObjectById(const ldraw::id_t id) const { const QModelIndex index = this->find(id); if (index.isValid()) { return (*this)[index.row()]; } else { return nullptr; } } /** * @brief Attempts the save the model */ void save(const Model &model, QIODevice *device) { QTextStream out{device}; applyToModel<ldraw::Object>(model, [&](const ldraw::Object* object) { out << object->toLDrawCode() << "\r\n"; }); }