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 } |
|