| 195 return path.mid (lastpos + 1); |
196 return path.mid (lastpos + 1); |
| 196 |
197 |
| 197 return path; |
198 return path; |
| 198 } |
199 } |
| 199 |
200 |
| 200 QString DocumentManager::findDocumentPath (QString relativePath, bool subdirs) |
201 QString DocumentManager::findDocument(QString name) const |
| 201 { |
202 { |
| 202 // LDraw models use backslashes as path separators. Replace those into forward slashes for Qt. |
203 name = name.replace("\\", "/"); |
| 203 relativePath.replace ("\\", "/"); |
204 |
| 204 |
205 for (const Library& library : ::config->libraries()) |
| 205 // Try find it relative to other currently open documents. We want a file in the immediate vicinity of a current |
206 { |
| 206 // part model to override stock LDraw stuff. |
207 for (const QString& subdirectory : {"parts", "p"}) |
| 207 QString relativeTopDir = Basename (Dirname (relativePath)); |
208 { |
| 208 |
209 QDir dir {library.path + "/" + subdirectory}; |
| 209 for (LDDocument* document : m_documents) |
210 |
| 210 { |
211 if (dir.exists(name)) |
| 211 QString partpath = format ("%1/%2", Dirname (document->fullPath()), relativePath); |
212 return QDir::cleanPath(dir.filePath(name)); |
| 212 QFileInfo fileinfo (partpath); |
213 } |
| 213 |
214 } |
| 214 if (fileinfo.exists()) |
215 |
| 215 { |
216 return {}; |
| 216 // Ensure we don't mix subfiles and 48-primitives with non-subfiles and non-48 |
217 } |
| 217 QString partTopDir = Basename (Dirname (partpath)); |
218 |
| 218 |
219 void DocumentManager::printParseErrorMessage(QString message) |
| 219 for (QString subdir : specialSubdirectories) |
220 { |
| |
221 print(message); |
| |
222 } |
| |
223 |
| |
224 LDDocument* DocumentManager::openDocument( |
| |
225 QString path, |
| |
226 bool search, |
| |
227 bool implicit, |
| |
228 LDDocument* fileToOverride |
| |
229 ) { |
| |
230 if (search and not QFileInfo {path}.exists()) |
| |
231 { |
| |
232 // Convert the file name to lowercase when searching because some parts contain subfile |
| |
233 // subfile references with uppercase file names. I'll assume here that the library will |
| |
234 // always use lowercase file names for the part files. |
| |
235 path = this->findDocument(path.toLower()); |
| |
236 } |
| |
237 |
| |
238 QFile file {path}; |
| |
239 |
| |
240 if (file.open(QIODevice::ReadOnly)) |
| |
241 { |
| |
242 LDDocument* load = fileToOverride; |
| |
243 |
| |
244 if (fileToOverride == nullptr) |
| |
245 load = m_window->newDocument(implicit); |
| |
246 |
| |
247 load->setFullPath(path); |
| |
248 load->setName(LDDocument::shortenName(path)); |
| |
249 |
| |
250 // Loading the file shouldn't count as actual edits to the document. |
| |
251 load->history()->setIgnoring (true); |
| |
252 |
| |
253 Parser parser {file}; |
| |
254 Winding winding = NoWinding; |
| |
255 load->header = parser.parseHeader(winding); |
| |
256 load->setWinding(winding); |
| |
257 parser.parseBody(*load); |
| |
258 file.close(); |
| |
259 |
| |
260 if (m_loadingMainFile) |
| |
261 { |
| |
262 int numWarnings = 0; |
| |
263 |
| |
264 for (LDObject* object : load->objects()) |
| 220 { |
265 { |
| 221 if ((partTopDir == subdir) != (relativeTopDir == subdir)) |
266 if (object->type() == LDObjectType::Error) |
| 222 goto skipthis; |
267 numWarnings += 1; |
| 223 } |
268 } |
| 224 |
269 |
| 225 return partpath; |
270 m_window->changeDocument(load); |
| 226 } |
271 print(tr("File %1 opened successfully (%2 errors)."), load->name(), numWarnings); |
| 227 skipthis: |
272 } |
| 228 continue; |
273 |
| 229 } |
274 load->history()->setIgnoring (false); |
| 230 |
275 return load; |
| 231 if (QFileInfo::exists (relativePath)) |
276 } |
| 232 return relativePath; |
277 else |
| 233 |
278 { |
| 234 // Try with just the LDraw path first |
|
| 235 QString fullPath = format ("%1" DIRSLASH "%2", m_config->lDrawPath(), relativePath); |
|
| 236 |
|
| 237 if (QFileInfo::exists (fullPath)) |
|
| 238 return fullPath; |
|
| 239 |
|
| 240 if (subdirs) |
|
| 241 { |
|
| 242 // Look in sub-directories: parts and p. Also look in the download path, since that's where we download parts |
|
| 243 // from the PT to. |
|
| 244 QStringList dirs = { m_config->lDrawPath(), m_config->downloadFilePath() }; |
|
| 245 for (const QString& topdir : dirs) |
|
| 246 { |
|
| 247 for (const QString& subdir : QStringList ({ "parts", "p" })) |
|
| 248 { |
|
| 249 fullPath = format ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relativePath); |
|
| 250 |
|
| 251 if (QFile::exists (fullPath)) |
|
| 252 return fullPath; |
|
| 253 } |
|
| 254 } |
|
| 255 } |
|
| 256 |
|
| 257 // Did not find the file. |
|
| 258 return ""; |
|
| 259 } |
|
| 260 |
|
| 261 QFile* DocumentManager::openLDrawFile (QString relpath, bool subdirs, QString* pathpointer) |
|
| 262 { |
|
| 263 print ("Opening %1...\n", relpath); |
|
| 264 QString path = findDocumentPath (relpath, subdirs); |
|
| 265 |
|
| 266 if (pathpointer) |
|
| 267 *pathpointer = path; |
|
| 268 |
|
| 269 if (path.isEmpty()) |
|
| 270 return nullptr; |
279 return nullptr; |
| 271 |
280 } |
| 272 QFile* fp = new QFile (path); |
|
| 273 |
|
| 274 if (fp->open (QIODevice::ReadOnly)) |
|
| 275 return fp; |
|
| 276 |
|
| 277 fp->deleteLater(); |
|
| 278 return nullptr; |
|
| 279 } |
|
| 280 |
|
| 281 void DocumentManager::printParseErrorMessage(QString message) |
|
| 282 { |
|
| 283 print(message); |
|
| 284 } |
|
| 285 |
|
| 286 LDDocument* DocumentManager::openDocument (QString path, bool search, bool implicit, LDDocument* fileToOverride) |
|
| 287 { |
|
| 288 // Convert the file name to lowercase when searching because some parts contain subfile |
|
| 289 // subfile references with uppercase file names. I'll assume here that the library will always |
|
| 290 // use lowercase file names for the part files. |
|
| 291 QFile* fp; |
|
| 292 QString fullpath; |
|
| 293 |
|
| 294 if (search) |
|
| 295 { |
|
| 296 fp = openLDrawFile (path.toLower(), true, &fullpath); |
|
| 297 } |
|
| 298 else |
|
| 299 { |
|
| 300 fp = new QFile (path); |
|
| 301 fullpath = path; |
|
| 302 |
|
| 303 if (not fp->open (QIODevice::ReadOnly)) |
|
| 304 { |
|
| 305 delete fp; |
|
| 306 return nullptr; |
|
| 307 } |
|
| 308 } |
|
| 309 |
|
| 310 if (not fp) |
|
| 311 return nullptr; |
|
| 312 |
|
| 313 LDDocument* load = (fileToOverride ? fileToOverride : m_window->newDocument (implicit)); |
|
| 314 load->setFullPath (fullpath); |
|
| 315 load->setName (LDDocument::shortenName (load->fullPath())); |
|
| 316 |
|
| 317 // Loading the file shouldn't count as actual edits to the document. |
|
| 318 load->history()->setIgnoring (true); |
|
| 319 |
|
| 320 int numWarnings; |
|
| 321 Parser parser {*fp}; |
|
| 322 Winding winding = NoWinding; |
|
| 323 load->header = parser.parseHeader(winding); |
|
| 324 load->setWinding(winding); |
|
| 325 parser.parseBody(*load); |
|
| 326 fp->close(); |
|
| 327 fp->deleteLater(); |
|
| 328 |
|
| 329 if (m_loadingMainFile) |
|
| 330 { |
|
| 331 int numWarnings = 0; |
|
| 332 |
|
| 333 for (LDObject* object : load->objects()) |
|
| 334 { |
|
| 335 if (object->type() == LDObjectType::Error) |
|
| 336 numWarnings += 1; |
|
| 337 } |
|
| 338 |
|
| 339 m_window->changeDocument (load); |
|
| 340 print (tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); |
|
| 341 } |
|
| 342 |
|
| 343 load->history()->setIgnoring (false); |
|
| 344 return load; |
|
| 345 } |
281 } |
| 346 |
282 |
| 347 void DocumentManager::addRecentFile (QString path) |
283 void DocumentManager::addRecentFile (QString path) |
| 348 { |
284 { |
| 349 QStringList recentFiles = m_config->recentFiles(); |
285 QStringList recentFiles = m_config->recentFiles(); |