| 14 * |
14 * |
| 15 * You should have received a copy of the GNU General Public License |
15 * You should have received a copy of the GNU General Public License |
| 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 17 */ |
17 */ |
| 18 |
18 |
| 19 #include <QBrush> |
|
| 20 #include <QFile> |
|
| 21 #include <QFileInfo> |
|
| 22 #include <QFont> |
|
| 23 #include <QSaveFile> |
|
| 24 #include "model.h" |
19 #include "model.h" |
| 25 #include "modeleditor.h" |
|
| 26 #include "documentmanager.h" |
|
| 27 |
20 |
| 28 /** |
21 QString modelElementToString(const ModelElement &element) |
| 29 * @brief Constructs a model |
22 { |
| 30 * @param parent QObject parent to pass forward |
23 return std::visit(overloaded{ |
| 31 */ |
24 [](const Colored<SubfileReference>& ref) { |
| |
25 return QStringLiteral("1 %1 %2 %3") |
| |
26 .arg(ref.color.index) |
| |
27 .arg(transformToString(ref.transformation)) |
| |
28 .arg(ref.name); |
| |
29 }, |
| |
30 [](const Colored<LineSegment>& seg) { |
| |
31 return QStringLiteral("2 %1 %2 %3") |
| |
32 .arg(seg.color.index) |
| |
33 .arg(vertexToString(seg.p1)) |
| |
34 .arg(vertexToString(seg.p2)); |
| |
35 }, |
| |
36 [](const Colored<Triangle>& triangle) { |
| |
37 return QStringLiteral("3 %1 %2 %3 %4") |
| |
38 .arg(triangle.color.index) |
| |
39 .arg(vertexToString(triangle.p1)) |
| |
40 .arg(vertexToString(triangle.p2)) |
| |
41 .arg(vertexToString(triangle.p3)); |
| |
42 }, |
| |
43 [](const Colored<Quadrilateral>& quad) { |
| |
44 return QStringLiteral("4 %1 %2 %3 %4 %5") |
| |
45 .arg(quad.color.index) |
| |
46 .arg(vertexToString(quad.p1)) |
| |
47 .arg(vertexToString(quad.p2)) |
| |
48 .arg(vertexToString(quad.p3)) |
| |
49 .arg(vertexToString(quad.p4)); |
| |
50 }, |
| |
51 [](const Colored<ConditionalEdge>& cedge) { |
| |
52 return QStringLiteral("5 %1 %2 %3 %4 %5") |
| |
53 .arg(cedge.color.index) |
| |
54 .arg(vertexToString(cedge.p1)) |
| |
55 .arg(vertexToString(cedge.p2)) |
| |
56 .arg(vertexToString(cedge.c1)) |
| |
57 .arg(vertexToString(cedge.c2)); |
| |
58 }, |
| |
59 [](const Comment& comment) { |
| |
60 return "0 " + comment.text; |
| |
61 }, |
| |
62 [](const Empty&) { |
| |
63 return QStringLiteral(""); |
| |
64 }, |
| |
65 [](const ParseError& parseError) { |
| |
66 return parseError.code; |
| |
67 }, |
| |
68 }, element); |
| |
69 } |
| |
70 |
| 32 Model::Model(QObject *parent) : |
71 Model::Model(QObject *parent) : |
| 33 QAbstractListModel{parent} |
72 QAbstractListModel{parent} |
| 34 { |
73 { |
| 35 } |
74 } |
| 36 |
75 |
| 37 /** |
76 Model::~Model() |
| 38 * @returns the amount of elements in the model |
|
| 39 */ |
|
| 40 int Model::size() const |
|
| 41 { |
77 { |
| 42 return static_cast<int>(this->body.size()); |
|
| 43 } |
78 } |
| 44 |
79 |
| 45 /** |
80 ModelId Model::append(const ModelElement &value) |
| 46 * @brief Looks up the object ID at the specified index. If out of bounds, returns NULL_ID. |
|
| 47 * @param index Index of object to look up |
|
| 48 * @return object ID |
|
| 49 */ |
|
| 50 ldraw::id_t Model::at(int index) const |
|
| 51 { |
81 { |
| 52 if (index >= 0 and index < this->size()) |
82 const int position = static_cast<int>(this->body.size()); |
| 53 { |
83 const ModelId id = this->runningId; |
| 54 return this->body[index]->id; |
84 this->runningId.value += 1; |
| 55 } |
85 Q_EMIT this->beginInsertRows({}, position, position); |
| 56 else |
86 this->body.push_back({value, id}); |
| 57 { |
87 this->positions[id] = position; |
| 58 return ldraw::NULL_ID; |
88 Q_EMIT this->endInsertRows(); |
| |
89 return id; |
| |
90 } |
| |
91 |
| |
92 const ModelElement &Model::at(int position) const |
| |
93 { |
| |
94 return this->body[position].data; |
| |
95 } |
| |
96 |
| |
97 ModelId Model::idAt(int position) const |
| |
98 { |
| |
99 return this->body[position].id; |
| |
100 } |
| |
101 |
| |
102 void Model::assignAt(int position, const ModelElement &element) |
| |
103 { |
| |
104 this->body[position].data = element; |
| |
105 const QModelIndex index = this->index(position); |
| |
106 Q_EMIT this->dataChanged(index, index); |
| |
107 } |
| |
108 |
| |
109 std::optional<int> Model::find(ModelId id) const |
| |
110 { |
| |
111 return pointerToOptional(findInMap(this->positions, id)); |
| |
112 } |
| |
113 |
| |
114 void Model::remove(int index) |
| |
115 { |
| |
116 if (index >= 0 and index < this->size()) { |
| |
117 Q_EMIT this->beginRemoveRows({}, index, index); |
| |
118 this->body.erase(this->body.begin() + index); |
| |
119 Q_EMIT this->endRemoveRows(); |
| 59 } |
120 } |
| 60 } |
121 } |
| 61 |
122 |
| 62 /** |
123 int Model::rowCount(const QModelIndex &) const |
| 63 * @brief @overload QAbstractListModel::rowCount |
|
| 64 * @return size |
|
| 65 */ |
|
| 66 int Model::rowCount(const QModelIndex&) const |
|
| 67 { |
124 { |
| 68 return this->size(); |
125 return this->size(); |
| 69 } |
126 } |
| 70 |
127 |
| 71 /** |
128 QVariant Model::data(const QModelIndex &index, int role) const |
| 72 * @brief @overload QAbstractListModel::data |
|
| 73 * @param index |
|
| 74 * @param role |
|
| 75 * @return QVariant |
|
| 76 */ |
|
| 77 QVariant Model::data(const QModelIndex& index, int role) const |
|
| 78 { |
129 { |
| 79 const ldraw::Object* object = (*this)[index.row()]; |
130 const int i = index.row(); |
| 80 switch(role) |
131 switch(role) |
| 81 { |
132 { |
| |
133 /* |
| 82 case Qt::DecorationRole: |
134 case Qt::DecorationRole: |
| 83 return QPixmap{object->iconName()}.scaledToHeight(24); |
135 return QPixmap{object->iconName()}.scaledToHeight(24); |
| |
136 */ |
| 84 case Qt::DisplayRole: |
137 case Qt::DisplayRole: |
| 85 return object->textRepresentation(); |
138 return modelElementToString(this->body[i].data); |
| |
139 /* |
| 86 case Qt::ForegroundRole: |
140 case Qt::ForegroundRole: |
| 87 return object->textRepresentationForeground(); |
141 return object->textRepresentationForeground(); |
| 88 case Qt::BackgroundRole: |
142 case Qt::BackgroundRole: |
| 89 return object->textRepresentationBackground(); |
143 return object->textRepresentationBackground(); |
| 90 case Qt::FontRole: |
144 case Qt::FontRole: |
| 91 return object->textRepresentationFont(); |
145 return object->textRepresentationFont(); |
| |
146 */ |
| 92 default: |
147 default: |
| 93 return {}; |
148 return {}; |
| 94 } |
149 } |
| 95 } |
150 } |
| 96 |
151 |
| 97 /** |
152 const ModelElement &Model::operator[](int index) const |
| 98 * @brief Finds the position of the specified object in the model |
|
| 99 * @param id Object id to look for |
|
| 100 * @return model index |
|
| 101 */ |
|
| 102 QModelIndex Model::find(ldraw::id_t id) const |
|
| 103 { |
153 { |
| 104 if (this->needObjectsByIdRebuild) |
154 return this->body[index].data; |
| 105 { |
155 } |
| 106 this->objectsById.clear(); |
156 |
| 107 for (std::size_t i = 0; i < this->body.size(); ++i) |
157 int Model::size() const |
| 108 { |
158 { |
| 109 this->objectsById[this->body[i]->id] = i; |
159 return this->body.size(); |
| 110 } |
160 } |
| 111 this->needObjectsByIdRebuild = false; |
161 |
| 112 } |
162 void save(const Model &model, QIODevice *device) |
| 113 const auto it = this->objectsById.find(id); |
163 { |
| 114 if (it != this->objectsById.end()) |
164 QTextStream out{device}; |
| 115 { |
165 for (int i = 0; i < model.size(); ++i) { |
| 116 return this->index(it->second); |
166 out << modelElementToString(model[i]) << "\r\n"; |
| 117 } |
|
| 118 else |
|
| 119 { |
|
| 120 return {}; |
|
| 121 } |
167 } |
| 122 } |
168 } |
| 123 |
169 |
| 124 /** |
170 /** |
| 125 * @brief Gets an object id by position in the model |
|
| 126 * @param index Position of the object in the model |
|
| 127 * @return id |
|
| 128 */ |
|
| 129 ldraw::id_t Model::idAt(const QModelIndex& index) const |
|
| 130 { |
|
| 131 return (*this)[index.row()]->id; |
|
| 132 } |
|
| 133 |
|
| 134 #if 0 |
|
| 135 /** |
|
| 136 * @brief Sets the path to the model |
171 * @brief Sets the path to the model |
| 137 * @param path New path to use |
172 * @param path New path to use |
| 138 */ |
173 */ |
| 139 void Model::setPath(const QString &path) |
174 void updateHeaderNameField(Model& model, const QString &name) |
| 140 { |
175 { |
| 141 this->storedPath = path; |
|
| 142 this->header.name = QFileInfo{path}.fileName(); |
|
| 143 // Update the "Name: 1234.dat" comment |
176 // Update the "Name: 1234.dat" comment |
| 144 if (this->body.size() >= 2) |
177 if (model.size() >= 2) { |
| 145 { |
178 if (const Comment* nameObject = std::get_if<Comment>(&model[1])) { |
| 146 const ldraw::id_t id = this->body[1]->id; |
179 if (nameObject->text.startsWith("Name: ")) { |
| 147 if (this->isA<ldraw::MetaCommand>(id)) |
180 model[1] = Comment{"Name: " + name}; |
| 148 { |
|
| 149 const QString& textBody = this->body[1]->getProperty<ldraw::Property::Text>(); |
|
| 150 if (textBody.startsWith("Name: ")) |
|
| 151 { |
|
| 152 auto editor = this->edit(); |
|
| 153 editor.setObjectProperty<ldraw::Property::Text>(id, "Name: " + this->header.name); |
|
| 154 } |
181 } |
| 155 } |
182 } |
| 156 } |
183 } |
| 157 } |
184 } |
| 158 #endif |
|
| 159 |
|
| 160 /** |
|
| 161 * @brief Adds the given object into the model. |
|
| 162 * @param object r-value reference to the object |
|
| 163 */ |
|
| 164 ldraw::id_t Model::append(ModelObjectPointer&& object) |
|
| 165 { |
|
| 166 const int position = static_cast<int>(this->body.size()); |
|
| 167 Q_EMIT this->beginInsertRows({}, position, position); |
|
| 168 this->body.push_back(std::move(object)); |
|
| 169 Q_EMIT this->endInsertRows(); |
|
| 170 const ldraw::id_t id = this->body.back()->id; |
|
| 171 this->objectsById[id] = this->body.size() - 1; |
|
| 172 return id; |
|
| 173 } |
|
| 174 |
|
| 175 /** |
|
| 176 * @brief Removes the object at the specified position |
|
| 177 * @param position |
|
| 178 */ |
|
| 179 void Model::remove(int position) |
|
| 180 { |
|
| 181 if (position >= 0 and position < signed_cast(this->body.size())) |
|
| 182 { |
|
| 183 Q_EMIT this->beginRemoveRows({}, position, position); |
|
| 184 this->body.erase(std::begin(this->body) + position); |
|
| 185 this->needObjectsByIdRebuild = true; |
|
| 186 Q_EMIT this->endRemoveRows(); |
|
| 187 } |
|
| 188 } |
|
| 189 |
|
| 190 void Model::emitDataChangedSignal(int position) |
|
| 191 { |
|
| 192 Q_EMIT this->dataChanged(this->index(position), this->index(position)); |
|
| 193 } |
|
| 194 |
|
| 195 /** |
|
| 196 * @brief Gets the object pointer at the specified position |
|
| 197 * @param index Position of the object |
|
| 198 * @returns object pointer |
|
| 199 */ |
|
| 200 ldraw::Object* Model::operator[](int index) |
|
| 201 { |
|
| 202 if (index >= 0 and index < this->size()) |
|
| 203 { |
|
| 204 return this->body[index].get(); |
|
| 205 } |
|
| 206 else |
|
| 207 { |
|
| 208 throw std::out_of_range{"index out of range"}; |
|
| 209 } |
|
| 210 } |
|
| 211 |
|
| 212 /** |
|
| 213 * @brief Gets the object pointer at the specified position |
|
| 214 * @param index Position of the object |
|
| 215 * @returns object pointer |
|
| 216 */ |
|
| 217 const ldraw::Object* Model::operator[](int index) const |
|
| 218 { |
|
| 219 if (index >= 0 and index < this->size()) |
|
| 220 { |
|
| 221 return this->body[index].get(); |
|
| 222 } |
|
| 223 else |
|
| 224 { |
|
| 225 throw std::out_of_range{"index out of range"}; |
|
| 226 } |
|
| 227 } |
|
| 228 |
|
| 229 /** |
|
| 230 * @brief Gets an object pointer by id. Used by the editing context to actually modify objects. |
|
| 231 * @param id |
|
| 232 * @return object pointer |
|
| 233 */ |
|
| 234 ldraw::Object* Model::findObjectById(const ldraw::id_t id) |
|
| 235 { |
|
| 236 const QModelIndex index = this->find(id); |
|
| 237 if (index.isValid()) |
|
| 238 { |
|
| 239 return (*this)[index.row()]; |
|
| 240 } |
|
| 241 else |
|
| 242 { |
|
| 243 return nullptr; |
|
| 244 } |
|
| 245 } |
|
| 246 |
|
| 247 const ldraw::Object* Model::findObjectById(const ldraw::id_t id) const |
|
| 248 { |
|
| 249 const QModelIndex index = this->find(id); |
|
| 250 if (index.isValid()) |
|
| 251 { |
|
| 252 return (*this)[index.row()]; |
|
| 253 } |
|
| 254 else |
|
| 255 { |
|
| 256 return nullptr; |
|
| 257 } |
|
| 258 } |
|
| 259 |
|
| 260 /** |
|
| 261 * @brief Attempts the save the model |
|
| 262 */ |
|
| 263 void save(const Model &model, QIODevice *device) |
|
| 264 { |
|
| 265 QTextStream out{device}; |
|
| 266 applyToModel<ldraw::Object>(model, [&](const ldraw::Object* object) { |
|
| 267 out << object->toLDrawCode() << "\r\n"; |
|
| 268 }); |
|
| 269 } |
|