| 1 /* |
|
| 2 * LDForge: LDraw parts authoring CAD |
|
| 3 * Copyright (C) 2013 Santeri Piippo |
|
| 4 * |
|
| 5 * This program is free software: you can redistribute it and/or modify |
|
| 6 * it under the terms of the GNU General Public License as published by |
|
| 7 * the Free Software Foundation, either version 3 of the License, or |
|
| 8 * (at your option) any later version. |
|
| 9 * |
|
| 10 * This program is distributed in the hope that it will be useful, |
|
| 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 13 * GNU General Public License for more details. |
|
| 14 * |
|
| 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/>. |
|
| 17 * ===================================================================== |
|
| 18 * |
|
| 19 * file.cpp: File I/O and management. |
|
| 20 * - File loading, parsing, manipulation, saving, closing. |
|
| 21 * - LDraw path verification. |
|
| 22 */ |
|
| 23 |
|
| 24 #include <QMessageBox> |
|
| 25 #include <QFileDialog> |
|
| 26 #include <QDir> |
|
| 27 #include <QApplication> |
|
| 28 #include "common.h" |
|
| 29 #include "config.h" |
|
| 30 #include "file.h" |
|
| 31 #include "misc.h" |
|
| 32 #include "gui.h" |
|
| 33 #include "history.h" |
|
| 34 #include "dialogs.h" |
|
| 35 #include "gldraw.h" |
|
| 36 #include "gldata.h" |
|
| 37 #include "moc_file.cpp" |
|
| 38 |
|
| 39 cfg (String, io_ldpath, ""); |
|
| 40 cfg (List, io_recentfiles, {}); |
|
| 41 extern_cfg (String, net_downloadpath); |
|
| 42 extern_cfg (Bool, gl_logostuds); |
|
| 43 |
|
| 44 static bool g_loadingMainFile = false; |
|
| 45 static const int g_MaxRecentFiles = 5; |
|
| 46 static bool g_aborted = false; |
|
| 47 static LDFile* g_logoedStud = null; |
|
| 48 static LDFile* g_logoedStud2 = null; |
|
| 49 |
|
| 50 LDFile* LDFile::m_curfile = null; |
|
| 51 |
|
| 52 DEFINE_PROPERTY (QListWidgetItem*, LDFile, listItem, setListItem) |
|
| 53 |
|
| 54 // ============================================================================= |
|
| 55 // ----------------------------------------------------------------------------- |
|
| 56 namespace LDPaths |
|
| 57 { static str pathError; |
|
| 58 |
|
| 59 struct |
|
| 60 { str LDConfigPath; |
|
| 61 str partsPath, primsPath; |
|
| 62 } pathInfo; |
|
| 63 |
|
| 64 void initPaths() |
|
| 65 { if (!tryConfigure (io_ldpath)) |
|
| 66 { LDrawPathDialog dlg (false); |
|
| 67 |
|
| 68 if (!dlg.exec()) |
|
| 69 exit (0); |
|
| 70 |
|
| 71 io_ldpath = dlg.filename(); |
|
| 72 } |
|
| 73 } |
|
| 74 |
|
| 75 bool tryConfigure (str path) |
|
| 76 { QDir dir; |
|
| 77 |
|
| 78 if (!dir.cd (path)) |
|
| 79 { pathError = "Directory does not exist."; |
|
| 80 return false; |
|
| 81 } |
|
| 82 |
|
| 83 QStringList mustHave = { "LDConfig.ldr", "parts", "p" }; |
|
| 84 QStringList contents = dir.entryList (mustHave); |
|
| 85 |
|
| 86 if (contents.size() != mustHave.size()) |
|
| 87 { pathError = "Not an LDraw directory! Must<br />have LDConfig.ldr, parts/ and p/."; |
|
| 88 return false; |
|
| 89 } |
|
| 90 |
|
| 91 pathInfo.partsPath = fmt ("%1" DIRSLASH "parts", path); |
|
| 92 pathInfo.LDConfigPath = fmt ("%1" DIRSLASH "LDConfig.ldr", path); |
|
| 93 pathInfo.primsPath = fmt ("%1" DIRSLASH "p", path); |
|
| 94 |
|
| 95 return true; |
|
| 96 } |
|
| 97 |
|
| 98 // Accessors |
|
| 99 str getError() |
|
| 100 { return pathError; |
|
| 101 } |
|
| 102 |
|
| 103 str ldconfig() |
|
| 104 { return pathInfo.LDConfigPath; |
|
| 105 } |
|
| 106 |
|
| 107 str prims() |
|
| 108 { return pathInfo.primsPath; |
|
| 109 } |
|
| 110 |
|
| 111 str parts() |
|
| 112 { return pathInfo.partsPath; |
|
| 113 } |
|
| 114 } |
|
| 115 |
|
| 116 // ============================================================================= |
|
| 117 // ----------------------------------------------------------------------------- |
|
| 118 LDFile::LDFile() |
|
| 119 { setImplicit (true); |
|
| 120 setSavePos (-1); |
|
| 121 setListItem (null); |
|
| 122 m_history.setFile (this); |
|
| 123 } |
|
| 124 |
|
| 125 // ============================================================================= |
|
| 126 // ----------------------------------------------------------------------------- |
|
| 127 LDFile::~LDFile() |
|
| 128 { // Clear everything from the model |
|
| 129 for (LDObject* obj : objects()) |
|
| 130 delete obj; |
|
| 131 |
|
| 132 // Clear the cache as well |
|
| 133 for (LDObject* obj : cache()) |
|
| 134 delete obj; |
|
| 135 |
|
| 136 // Remove this file from the list of files |
|
| 137 g_loadedFiles.removeOne (this); |
|
| 138 |
|
| 139 // If we just closed the current file, we need to set the current |
|
| 140 // file as something else. |
|
| 141 if (this == LDFile::current()) |
|
| 142 { bool found = false; |
|
| 143 |
|
| 144 // Try find an explicitly loaded file - if we can't find one, |
|
| 145 // we need to create a new file to switch to. |
|
| 146 for (LDFile* file : g_loadedFiles) |
|
| 147 { if (!file->implicit()) |
|
| 148 { LDFile::setCurrent (file); |
|
| 149 found = true; |
|
| 150 break; |
|
| 151 } |
|
| 152 } |
|
| 153 |
|
| 154 if (!found) |
|
| 155 newFile(); |
|
| 156 } |
|
| 157 |
|
| 158 g_win->updateFileList(); |
|
| 159 } |
|
| 160 |
|
| 161 // ============================================================================= |
|
| 162 // ----------------------------------------------------------------------------- |
|
| 163 LDFile* findLoadedFile (str name) |
|
| 164 { for (LDFile * file : g_loadedFiles) |
|
| 165 if (!file->name().isEmpty() && file->getShortName() == name) |
|
| 166 return file; |
|
| 167 |
|
| 168 return null; |
|
| 169 } |
|
| 170 |
|
| 171 // ============================================================================= |
|
| 172 // ----------------------------------------------------------------------------- |
|
| 173 str dirname (str path) |
|
| 174 { long lastpos = path.lastIndexOf (DIRSLASH); |
|
| 175 |
|
| 176 if (lastpos > 0) |
|
| 177 return path.left (lastpos); |
|
| 178 |
|
| 179 #ifndef _WIN32 |
|
| 180 |
|
| 181 if (path[0] == DIRSLASH_CHAR) |
|
| 182 return DIRSLASH; |
|
| 183 |
|
| 184 #endif // _WIN32 |
|
| 185 |
|
| 186 return ""; |
|
| 187 } |
|
| 188 |
|
| 189 // ============================================================================= |
|
| 190 // ----------------------------------------------------------------------------- |
|
| 191 str basename (str path) |
|
| 192 { long lastpos = path.lastIndexOf (DIRSLASH); |
|
| 193 |
|
| 194 if (lastpos != -1) |
|
| 195 return path.mid (lastpos + 1); |
|
| 196 |
|
| 197 return path; |
|
| 198 } |
|
| 199 |
|
| 200 // ============================================================================= |
|
| 201 // ----------------------------------------------------------------------------- |
|
| 202 File* openLDrawFile (str relpath, bool subdirs) |
|
| 203 { log ("Opening %1...\n", relpath); |
|
| 204 File* f = new File; |
|
| 205 str fullPath; |
|
| 206 |
|
| 207 // LDraw models use Windows-style path separators. If we're not on Windows, |
|
| 208 // replace the path separator now before opening any files. |
|
| 209 #ifndef WIN32 |
|
| 210 relpath.replace ("\\", "/"); |
|
| 211 #endif // WIN32 |
|
| 212 |
|
| 213 if (LDFile::current()) |
|
| 214 { // First, try find the file in the current model's file path. We want a file |
|
| 215 // in the immediate vicinity of the current model to override stock LDraw stuff. |
|
| 216 str partpath = fmt ("%1" DIRSLASH "%2", dirname (LDFile::current()->name()), relpath); |
|
| 217 |
|
| 218 if (f->open (partpath, File::Read)) |
|
| 219 return f; |
|
| 220 } |
|
| 221 |
|
| 222 if (f->open (relpath, File::Read)) |
|
| 223 return f; |
|
| 224 |
|
| 225 // Try with just the LDraw path first |
|
| 226 fullPath = fmt ("%1" DIRSLASH "%2", io_ldpath, relpath); |
|
| 227 |
|
| 228 if (f->open (fullPath, File::Read)) |
|
| 229 return f; |
|
| 230 |
|
| 231 if (subdirs) |
|
| 232 { // Look in sub-directories: parts and p. Also look in net_downloadpath, since that's |
|
| 233 // where we download parts from the PT to. |
|
| 234 for (const str& topdir : initlist<str> ({ io_ldpath, net_downloadpath })) |
|
| 235 { for (const str& subdir : initlist<str> ({ "parts", "p" })) |
|
| 236 { fullPath = fmt ("%1" DIRSLASH "%2" DIRSLASH "%3", topdir, subdir, relpath); |
|
| 237 |
|
| 238 if (f->open (fullPath, File::Read)) |
|
| 239 return f; |
|
| 240 } |
|
| 241 } |
|
| 242 } |
|
| 243 |
|
| 244 // Did not find the file. |
|
| 245 log ("Could not find %1.\n", relpath); |
|
| 246 delete f; |
|
| 247 return null; |
|
| 248 } |
|
| 249 |
|
| 250 // ============================================================================= |
|
| 251 // ----------------------------------------------------------------------------- |
|
| 252 void FileLoader::start() |
|
| 253 { setDone (false); |
|
| 254 setProgress (0); |
|
| 255 setAborted (false); |
|
| 256 |
|
| 257 if (concurrent()) |
|
| 258 { g_aborted = false; |
|
| 259 |
|
| 260 // Show a progress dialog if we're loading the main file here so we can |
|
| 261 // show progress updates and keep the WM posted that we're still here. |
|
| 262 // Of course we cannot exec() the dialog because then the dialog would |
|
| 263 // block. |
|
| 264 dlg = new OpenProgressDialog (g_win); |
|
| 265 dlg->setNumLines (lines().size()); |
|
| 266 dlg->setModal (true); |
|
| 267 dlg->show(); |
|
| 268 |
|
| 269 // Connect the loader in so we can show updates |
|
| 270 connect (this, SIGNAL (workDone()), dlg, SLOT (accept())); |
|
| 271 connect (dlg, SIGNAL (rejected()), this, SLOT (abort())); |
|
| 272 } |
|
| 273 else |
|
| 274 dlg = null; |
|
| 275 |
|
| 276 // Begin working |
|
| 277 work (0); |
|
| 278 } |
|
| 279 |
|
| 280 // ============================================================================= |
|
| 281 // ----------------------------------------------------------------------------- |
|
| 282 void FileLoader::work (int i) |
|
| 283 { // User wishes to abort, so stop here now. |
|
| 284 if (aborted()) |
|
| 285 { for (LDObject* obj : m_objs) |
|
| 286 delete obj; |
|
| 287 |
|
| 288 m_objs.clear(); |
|
| 289 setDone (true); |
|
| 290 return; |
|
| 291 } |
|
| 292 |
|
| 293 // Parse up to 300 lines per iteration |
|
| 294 int max = i + 300; |
|
| 295 |
|
| 296 for (; i < max && i < (int) lines().size(); ++i) |
|
| 297 { str line = lines() [i]; |
|
| 298 |
|
| 299 // Trim the trailing newline |
|
| 300 qchar c; |
|
| 301 while (!line.isEmpty() && ((c = line[line.length() - 1]) == '\n' || c == '\r')) |
|
| 302 line.chop (1); |
|
| 303 |
|
| 304 LDObject* obj = parseLine (line); |
|
| 305 |
|
| 306 // Check for parse errors and warn about tthem |
|
| 307 if (obj->getType() == LDObject::Error) |
|
| 308 { log ("Couldn't parse line #%1: %2", m_progress + 1, static_cast<LDError*> (obj)->reason); |
|
| 309 |
|
| 310 if (m_warningsPointer) |
|
| 311 (*m_warningsPointer)++; |
|
| 312 } |
|
| 313 |
|
| 314 m_objs << obj; |
|
| 315 setProgress (i); |
|
| 316 |
|
| 317 // If we have a dialog pointer, update the progress now |
|
| 318 if (concurrent()) |
|
| 319 dlg->updateProgress (i); |
|
| 320 } |
|
| 321 |
|
| 322 // If we're done now, tell the environment we're done and stop. |
|
| 323 if (i >= ((int) lines().size()) - 1) |
|
| 324 { emit workDone(); |
|
| 325 setDone (true); |
|
| 326 return; |
|
| 327 } |
|
| 328 |
|
| 329 // Otherwise, continue, by recursing back. |
|
| 330 if (!done()) |
|
| 331 { // If we have a dialog to show progress output to, we cannot just call |
|
| 332 // work() again immediately as the dialog needs some processor cycles as |
|
| 333 // well. Thus, take a detour through the event loop by using the |
|
| 334 // meta-object system. |
|
| 335 // |
|
| 336 // This terminates the loop here and control goes back to the function |
|
| 337 // which called the file loader. It will keep processing the event loop |
|
| 338 // until we're ready (see loadFileContents), thus the event loop will |
|
| 339 // eventually catch the invokation we throw here and send us back. Though |
|
| 340 // it's not technically recursion anymore, more like a for loop. :P |
|
| 341 if (concurrent()) |
|
| 342 QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection, Q_ARG (int, i)); |
|
| 343 else |
|
| 344 work (i + 1); |
|
| 345 } |
|
| 346 } |
|
| 347 |
|
| 348 // ============================================================================= |
|
| 349 // ----------------------------------------------------------------------------- |
|
| 350 void FileLoader::abort() |
|
| 351 { setAborted (true); |
|
| 352 |
|
| 353 if (concurrent()) |
|
| 354 g_aborted = true; |
|
| 355 } |
|
| 356 |
|
| 357 // ============================================================================= |
|
| 358 // ----------------------------------------------------------------------------- |
|
| 359 QList<LDObject*> loadFileContents (File* f, int* numWarnings, bool* ok) |
|
| 360 { QList<str> lines; |
|
| 361 QList<LDObject*> objs; |
|
| 362 |
|
| 363 if (numWarnings) |
|
| 364 *numWarnings = 0; |
|
| 365 |
|
| 366 // Calculate the amount of lines |
|
| 367 for (str line : *f) |
|
| 368 lines << line; |
|
| 369 |
|
| 370 f->rewind(); |
|
| 371 |
|
| 372 FileLoader* loader = new FileLoader; |
|
| 373 loader->setWarningsPointer (numWarnings); |
|
| 374 loader->setLines (lines); |
|
| 375 loader->setConcurrent (g_loadingMainFile); |
|
| 376 loader->start(); |
|
| 377 |
|
| 378 // After start() returns, if the loader isn't done yet, it's delaying |
|
| 379 // its next iteration through the event loop. We need to catch this here |
|
| 380 // by telling the event loop to tick, which will tick the file loader again. |
|
| 381 // We keep doing this until the file loader is ready. |
|
| 382 while (loader->done() == false) |
|
| 383 qApp->processEvents(); |
|
| 384 |
|
| 385 // If we wanted the success value, supply that now |
|
| 386 if (ok) |
|
| 387 *ok = !loader->aborted(); |
|
| 388 |
|
| 389 objs = loader->objs(); |
|
| 390 return objs; |
|
| 391 } |
|
| 392 |
|
| 393 // ============================================================================= |
|
| 394 // ----------------------------------------------------------------------------- |
|
| 395 LDFile* openDATFile (str path, bool search) |
|
| 396 { // Convert the file name to lowercase since some parts contain uppercase |
|
| 397 // file names. I'll assume here that the library will always use lowercase |
|
| 398 // file names for the actual parts.. |
|
| 399 File* f; |
|
| 400 |
|
| 401 if (search) |
|
| 402 f = openLDrawFile (path.toLower(), true); |
|
| 403 else |
|
| 404 { f = new File (path, File::Read); |
|
| 405 |
|
| 406 if (!*f) |
|
| 407 { delete f; |
|
| 408 return null; |
|
| 409 } |
|
| 410 } |
|
| 411 |
|
| 412 if (!f) |
|
| 413 return null; |
|
| 414 |
|
| 415 LDFile* load = new LDFile; |
|
| 416 load->setName (path); |
|
| 417 |
|
| 418 int numWarnings; |
|
| 419 bool ok; |
|
| 420 QList<LDObject*> objs = loadFileContents (f, &numWarnings, &ok); |
|
| 421 |
|
| 422 if (!ok) |
|
| 423 return null; |
|
| 424 |
|
| 425 for (LDObject* obj : objs) |
|
| 426 load->addObject (obj); |
|
| 427 |
|
| 428 delete f; |
|
| 429 g_loadedFiles << load; |
|
| 430 |
|
| 431 if (g_loadingMainFile) |
|
| 432 { LDFile::setCurrent (load); |
|
| 433 g_win->R()->setFile (load); |
|
| 434 log (QObject::tr ("File %1 parsed successfully (%2 errors)."), path, numWarnings); |
|
| 435 } |
|
| 436 |
|
| 437 return load; |
|
| 438 } |
|
| 439 |
|
| 440 // ============================================================================= |
|
| 441 // ----------------------------------------------------------------------------- |
|
| 442 bool LDFile::safeToClose() |
|
| 443 { typedef QMessageBox msgbox; |
|
| 444 setlocale (LC_ALL, "C"); |
|
| 445 |
|
| 446 // If we have unsaved changes, warn and give the option of saving. |
|
| 447 if (hasUnsavedChanges()) |
|
| 448 { str message = fmt ("There are unsaved changes to %1. Should it be saved?", |
|
| 449 (name().length() > 0) ? name() : "<anonymous>"); |
|
| 450 |
|
| 451 int button = msgbox::question (g_win, "Unsaved Changes", message, |
|
| 452 (msgbox::Yes | msgbox::No | msgbox::Cancel), msgbox::Cancel); |
|
| 453 |
|
| 454 switch (button) |
|
| 455 { case msgbox::Yes: |
|
| 456 |
|
| 457 // If we don't have a file path yet, we have to ask the user for one. |
|
| 458 if (name().length() == 0) |
|
| 459 { str newpath = QFileDialog::getSaveFileName (g_win, "Save As", |
|
| 460 LDFile::current()->name(), "LDraw files (*.dat *.ldr)"); |
|
| 461 |
|
| 462 if (newpath.length() == 0) |
|
| 463 return false; |
|
| 464 |
|
| 465 setName (newpath); |
|
| 466 } |
|
| 467 |
|
| 468 if (!save()) |
|
| 469 { message = fmt (QObject::tr ("Failed to save %1: %2\nDo you still want to close?"), |
|
| 470 name(), strerror (errno)); |
|
| 471 |
|
| 472 if (msgbox::critical (g_win, "Save Failure", message, |
|
| 473 (msgbox::Yes | msgbox::No), msgbox::No) == msgbox::No) |
|
| 474 { return false; |
|
| 475 } |
|
| 476 } |
|
| 477 |
|
| 478 break; |
|
| 479 |
|
| 480 case msgbox::Cancel: |
|
| 481 return false; |
|
| 482 |
|
| 483 default: |
|
| 484 break; |
|
| 485 } |
|
| 486 } |
|
| 487 |
|
| 488 return true; |
|
| 489 } |
|
| 490 |
|
| 491 // ============================================================================= |
|
| 492 // ----------------------------------------------------------------------------- |
|
| 493 void closeAll() |
|
| 494 { // Remove all loaded files and the objects they contain |
|
| 495 QList<LDFile*> files = g_loadedFiles; |
|
| 496 |
|
| 497 for (LDFile * file : files) |
|
| 498 delete file; |
|
| 499 } |
|
| 500 |
|
| 501 // ============================================================================= |
|
| 502 // ----------------------------------------------------------------------------- |
|
| 503 void newFile() |
|
| 504 { // Create a new anonymous file and set it to our current |
|
| 505 LDFile* f = new LDFile; |
|
| 506 f->setName (""); |
|
| 507 f->setImplicit (false); |
|
| 508 g_loadedFiles << f; |
|
| 509 LDFile::setCurrent (f); |
|
| 510 |
|
| 511 LDFile::closeInitialFile(); |
|
| 512 |
|
| 513 g_win->R()->setFile (f); |
|
| 514 g_win->fullRefresh(); |
|
| 515 g_win->updateTitle(); |
|
| 516 f->history().updateActions(); |
|
| 517 } |
|
| 518 |
|
| 519 // ============================================================================= |
|
| 520 // ----------------------------------------------------------------------------- |
|
| 521 void addRecentFile (str path) |
|
| 522 { alias rfiles = io_recentfiles.value; |
|
| 523 int idx = rfiles.indexOf (path); |
|
| 524 |
|
| 525 // If this file already is in the list, pop it out. |
|
| 526 if (idx != -1) |
|
| 527 { if (rfiles.size() == 1) |
|
| 528 return; // only recent file - abort and do nothing |
|
| 529 |
|
| 530 // Pop it out. |
|
| 531 rfiles.removeAt (idx); |
|
| 532 } |
|
| 533 |
|
| 534 // If there's too many recent files, drop one out. |
|
| 535 while (rfiles.size() > (g_MaxRecentFiles - 1)) |
|
| 536 rfiles.removeAt (0); |
|
| 537 |
|
| 538 // Add the file |
|
| 539 rfiles << path; |
|
| 540 |
|
| 541 Config::save(); |
|
| 542 g_win->updateRecentFilesMenu(); |
|
| 543 } |
|
| 544 |
|
| 545 // ============================================================================= |
|
| 546 // Open an LDraw file and set it as the main model |
|
| 547 // ----------------------------------------------------------------------------- |
|
| 548 void openMainFile (str path) |
|
| 549 { g_loadingMainFile = true; |
|
| 550 LDFile* file = openDATFile (path, false); |
|
| 551 |
|
| 552 if (!file) |
|
| 553 { // Loading failed, thus drop down to a new file since we |
|
| 554 // closed everything prior. |
|
| 555 newFile(); |
|
| 556 |
|
| 557 if (!g_aborted) |
|
| 558 { // Tell the user loading failed. |
|
| 559 setlocale (LC_ALL, "C"); |
|
| 560 critical (fmt (QObject::tr ("Failed to open %1: %2"), path, strerror (errno))); |
|
| 561 } |
|
| 562 |
|
| 563 g_loadingMainFile = false; |
|
| 564 return; |
|
| 565 } |
|
| 566 |
|
| 567 file->setImplicit (false); |
|
| 568 |
|
| 569 // If we have an anonymous, unchanged file open as the only open file |
|
| 570 // (aside of the one we just opened), close it now. |
|
| 571 LDFile::closeInitialFile(); |
|
| 572 |
|
| 573 // Rebuild the object tree view now. |
|
| 574 LDFile::setCurrent (file); |
|
| 575 g_win->fullRefresh(); |
|
| 576 |
|
| 577 // Add it to the recent files list. |
|
| 578 addRecentFile (path); |
|
| 579 g_loadingMainFile = false; |
|
| 580 } |
|
| 581 |
|
| 582 // ============================================================================= |
|
| 583 // ----------------------------------------------------------------------------- |
|
| 584 bool LDFile::save (str savepath) |
|
| 585 { if (!savepath.length()) |
|
| 586 savepath = name(); |
|
| 587 |
|
| 588 File f (savepath, File::Write); |
|
| 589 |
|
| 590 if (!f) |
|
| 591 return false; |
|
| 592 |
|
| 593 // If the second object in the list holds the file name, update that now. |
|
| 594 // Only do this if the file is explicitly open. If it's saved into a directory |
|
| 595 // called "s" or "48", prepend that into the name. |
|
| 596 LDComment* fpathComment = null; |
|
| 597 LDObject* first = object (1); |
|
| 598 |
|
| 599 if (!implicit() && first != null && first->getType() == LDObject::Comment) |
|
| 600 { fpathComment = static_cast<LDComment*> (first); |
|
| 601 |
|
| 602 if (fpathComment->text.left (6) == "Name: ") |
|
| 603 { str newname; |
|
| 604 str dir = basename (dirname (savepath)); |
|
| 605 |
|
| 606 if (dir == "s" || dir == "48") |
|
| 607 newname = dir + "\\"; |
|
| 608 |
|
| 609 newname += basename (savepath); |
|
| 610 fpathComment->text = fmt ("Name: %1", newname); |
|
| 611 g_win->buildObjList(); |
|
| 612 } |
|
| 613 } |
|
| 614 |
|
| 615 // File is open, now save the model to it. Note that LDraw requires files to |
|
| 616 // have DOS line endings, so we terminate the lines with \r\n. |
|
| 617 for (LDObject* obj : objects()) |
|
| 618 f.write (obj->raw() + "\r\n"); |
|
| 619 |
|
| 620 // File is saved, now clean up. |
|
| 621 f.close(); |
|
| 622 |
|
| 623 // We have successfully saved, update the save position now. |
|
| 624 setSavePos (history().pos()); |
|
| 625 setName (savepath); |
|
| 626 |
|
| 627 g_win->updateFileListItem (this); |
|
| 628 g_win->updateTitle(); |
|
| 629 return true; |
|
| 630 } |
|
| 631 |
|
| 632 // ============================================================================= |
|
| 633 // ----------------------------------------------------------------------------- |
|
| 634 #define CHECK_TOKEN_COUNT(N) \ |
|
| 635 if (tokens.size() != N) \ |
|
| 636 return new LDError (line, "Bad amount of tokens"); |
|
| 637 |
|
| 638 #define CHECK_TOKEN_NUMBERS(MIN, MAX) \ |
|
| 639 for (int i = MIN; i <= MAX; ++i) \ |
|
| 640 if (!numeric (tokens[i])) \ |
|
| 641 return new LDError (line, fmt ("Token #%1 was `%2`, expected a number", (i + 1), tokens[i])); |
|
| 642 |
|
| 643 // ============================================================================= |
|
| 644 // ----------------------------------------------------------------------------- |
|
| 645 static vertex parseVertex (QStringList& s, const int n) |
|
| 646 { vertex v; |
|
| 647 |
|
| 648 for (const Axis ax : g_Axes) |
|
| 649 v[ax] = s[n + ax].toDouble(); |
|
| 650 |
|
| 651 return v; |
|
| 652 } |
|
| 653 |
|
| 654 // ============================================================================= |
|
| 655 // This is the LDraw code parser function. It takes in a string containing LDraw |
|
| 656 // code and returns the object parsed from it. parseLine never returns null, |
|
| 657 // the object will be LDError if it could not be parsed properly. |
|
| 658 // ----------------------------------------------------------------------------- |
|
| 659 LDObject* parseLine (str line) |
|
| 660 { QStringList tokens = line.split (" ", str::SkipEmptyParts); |
|
| 661 |
|
| 662 if (tokens.size() <= 0) |
|
| 663 { // Line was empty, or only consisted of whitespace |
|
| 664 return new LDEmpty; |
|
| 665 } |
|
| 666 |
|
| 667 if (tokens[0].length() != 1 || tokens[0][0].isDigit() == false) |
|
| 668 return new LDError (line, "Illogical line code"); |
|
| 669 |
|
| 670 int num = tokens[0][0].digitValue(); |
|
| 671 |
|
| 672 switch (num) |
|
| 673 { case 0: |
|
| 674 { // Comment |
|
| 675 str comm = line.mid (line.indexOf ("0") + 1); |
|
| 676 |
|
| 677 // Remove any leading whitespace |
|
| 678 while (comm[0] == ' ') |
|
| 679 comm.remove (0, 1); |
|
| 680 |
|
| 681 // Handle BFC statements |
|
| 682 if (tokens.size() > 2 && tokens[1] == "BFC") |
|
| 683 { for (short i = 0; i < LDBFC::NumStatements; ++i) |
|
| 684 if (comm == fmt ("BFC %1", LDBFC::statements [i])) |
|
| 685 return new LDBFC ( (LDBFC::Type) i); |
|
| 686 |
|
| 687 // MLCAD is notorious for stuffing these statements in parts it |
|
| 688 // creates. The above block only handles valid statements, so we |
|
| 689 // need to handle MLCAD-style invertnext, clip and noclip separately. |
|
| 690 struct |
|
| 691 { const char* a; |
|
| 692 LDBFC::Type b; |
|
| 693 } BFCData[] = |
|
| 694 { { "INVERTNEXT", LDBFC::InvertNext }, |
|
| 695 { "NOCLIP", LDBFC::NoClip }, |
|
| 696 { "CLIP", LDBFC::Clip } |
|
| 697 }; |
|
| 698 |
|
| 699 for (const auto & i : BFCData) |
|
| 700 if (comm == fmt ("BFC CERTIFY %1", i.a)) |
|
| 701 return new LDBFC (i.b); |
|
| 702 } |
|
| 703 |
|
| 704 if (tokens.size() > 2 && tokens[1] == "!LDFORGE") |
|
| 705 { // Handle LDForge-specific types, they're embedded into comments too |
|
| 706 if (tokens[2] == "VERTEX") |
|
| 707 { // Vertex (0 !LDFORGE VERTEX) |
|
| 708 CHECK_TOKEN_COUNT (7) |
|
| 709 CHECK_TOKEN_NUMBERS (3, 6) |
|
| 710 |
|
| 711 LDVertex* obj = new LDVertex; |
|
| 712 obj->setColor (tokens[3].toLong()); |
|
| 713 |
|
| 714 for (const Axis ax : g_Axes) |
|
| 715 obj->pos[ax] = tokens[4 + ax].toDouble(); // 4 - 6 |
|
| 716 |
|
| 717 return obj; |
|
| 718 } elif (tokens[2] == "OVERLAY") |
|
| 719 |
|
| 720 { CHECK_TOKEN_COUNT (9); |
|
| 721 CHECK_TOKEN_NUMBERS (5, 8) |
|
| 722 |
|
| 723 LDOverlay* obj = new LDOverlay; |
|
| 724 obj->setFilename (tokens[3]); |
|
| 725 obj->setCamera (tokens[4].toLong()); |
|
| 726 obj->setX (tokens[5].toLong()); |
|
| 727 obj->setY (tokens[6].toLong()); |
|
| 728 obj->setWidth (tokens[7].toLong()); |
|
| 729 obj->setHeight (tokens[8].toLong()); |
|
| 730 return obj; |
|
| 731 } |
|
| 732 } |
|
| 733 |
|
| 734 // Just a regular comment: |
|
| 735 LDComment* obj = new LDComment; |
|
| 736 obj->text = comm; |
|
| 737 return obj; |
|
| 738 } |
|
| 739 |
|
| 740 case 1: |
|
| 741 { // Subfile |
|
| 742 CHECK_TOKEN_COUNT (15) |
|
| 743 CHECK_TOKEN_NUMBERS (1, 13) |
|
| 744 |
|
| 745 // Try open the file. Disable g_loadingMainFile temporarily since we're |
|
| 746 // not loading the main file now, but the subfile in question. |
|
| 747 bool tmp = g_loadingMainFile; |
|
| 748 g_loadingMainFile = false; |
|
| 749 LDFile* load = getFile (tokens[14]); |
|
| 750 g_loadingMainFile = tmp; |
|
| 751 |
|
| 752 // If we cannot open the file, mark it an error |
|
| 753 if (!load) |
|
| 754 { LDError* obj = new LDError (line, fmt ("Could not open %1", tokens[14])); |
|
| 755 obj->setFileRef (tokens[14]); |
|
| 756 return obj; |
|
| 757 } |
|
| 758 |
|
| 759 LDSubfile* obj = new LDSubfile; |
|
| 760 obj->setColor (tokens[1].toLong()); |
|
| 761 obj->setPosition (parseVertex (tokens, 2)); // 2 - 4 |
|
| 762 |
|
| 763 matrix transform; |
|
| 764 |
|
| 765 for (short i = 0; i < 9; ++i) |
|
| 766 transform[i] = tokens[i + 5].toDouble(); // 5 - 13 |
|
| 767 |
|
| 768 obj->setTransform (transform); |
|
| 769 obj->setFileInfo (load); |
|
| 770 return obj; |
|
| 771 } |
|
| 772 |
|
| 773 case 2: |
|
| 774 { CHECK_TOKEN_COUNT (8) |
|
| 775 CHECK_TOKEN_NUMBERS (1, 7) |
|
| 776 |
|
| 777 // Line |
|
| 778 LDLine* obj = new LDLine; |
|
| 779 obj->setColor (tokens[1].toLong()); |
|
| 780 |
|
| 781 for (short i = 0; i < 2; ++i) |
|
| 782 obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 7 |
|
| 783 |
|
| 784 return obj; |
|
| 785 } |
|
| 786 |
|
| 787 case 3: |
|
| 788 { CHECK_TOKEN_COUNT (11) |
|
| 789 CHECK_TOKEN_NUMBERS (1, 10) |
|
| 790 |
|
| 791 // Triangle |
|
| 792 LDTriangle* obj = new LDTriangle; |
|
| 793 obj->setColor (tokens[1].toLong()); |
|
| 794 |
|
| 795 for (short i = 0; i < 3; ++i) |
|
| 796 obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 10 |
|
| 797 |
|
| 798 return obj; |
|
| 799 } |
|
| 800 |
|
| 801 case 4: |
|
| 802 case 5: |
|
| 803 { CHECK_TOKEN_COUNT (14) |
|
| 804 CHECK_TOKEN_NUMBERS (1, 13) |
|
| 805 |
|
| 806 // Quadrilateral / Conditional line |
|
| 807 LDObject* obj; |
|
| 808 |
|
| 809 if (num == 4) |
|
| 810 obj = new LDQuad; |
|
| 811 else |
|
| 812 obj = new LDCndLine; |
|
| 813 |
|
| 814 obj->setColor (tokens[1].toLong()); |
|
| 815 |
|
| 816 for (short i = 0; i < 4; ++i) |
|
| 817 obj->setVertex (i, parseVertex (tokens, 2 + (i * 3))); // 2 - 13 |
|
| 818 |
|
| 819 return obj; |
|
| 820 } |
|
| 821 |
|
| 822 default: // Strange line we couldn't parse |
|
| 823 return new LDError (line, "Unknown line code number"); |
|
| 824 } |
|
| 825 } |
|
| 826 |
|
| 827 // ============================================================================= |
|
| 828 // ----------------------------------------------------------------------------- |
|
| 829 LDFile* getFile (str filename) |
|
| 830 { // Try find the file in the list of loaded files |
|
| 831 LDFile* load = findLoadedFile (filename); |
|
| 832 |
|
| 833 // If it's not loaded, try open it |
|
| 834 if (!load) |
|
| 835 load = openDATFile (filename, true); |
|
| 836 |
|
| 837 return load; |
|
| 838 } |
|
| 839 |
|
| 840 // ============================================================================= |
|
| 841 // ----------------------------------------------------------------------------- |
|
| 842 void reloadAllSubfiles() |
|
| 843 { if (!LDFile::current()) |
|
| 844 return; |
|
| 845 |
|
| 846 g_loadedFiles.clear(); |
|
| 847 g_loadedFiles << LDFile::current(); |
|
| 848 |
|
| 849 // Go through all objects in the current file and reload the subfiles |
|
| 850 for (LDObject * obj : LDFile::current()->objects()) |
|
| 851 { if (obj->getType() == LDObject::Subfile) |
|
| 852 { LDSubfile* ref = static_cast<LDSubfile*> (obj); |
|
| 853 LDFile* fileInfo = getFile (ref->fileInfo()->name()); |
|
| 854 |
|
| 855 if (fileInfo) |
|
| 856 ref->setFileInfo (fileInfo); |
|
| 857 else |
|
| 858 ref->replace (new LDError (ref->raw(), "Could not open referred file")); |
|
| 859 } |
|
| 860 |
|
| 861 // Reparse gibberish files. It could be that they are invalid because |
|
| 862 // of loading errors. Circumstances may be different now. |
|
| 863 if (obj->getType() == LDObject::Error) |
|
| 864 obj->replace (parseLine (static_cast<LDError*> (obj)->contents)); |
|
| 865 } |
|
| 866 |
|
| 867 // Close all files left unused |
|
| 868 LDFile::closeUnused(); |
|
| 869 } |
|
| 870 |
|
| 871 // ============================================================================= |
|
| 872 // ----------------------------------------------------------------------------- |
|
| 873 int LDFile::addObject (LDObject* obj) |
|
| 874 { m_history.add (new AddHistory (objects().size(), obj)); |
|
| 875 m_objects << obj; |
|
| 876 |
|
| 877 if (obj->getType() == LDObject::Vertex) |
|
| 878 m_vertices << obj; |
|
| 879 |
|
| 880 obj->setFile (this); |
|
| 881 return numObjs() - 1; |
|
| 882 } |
|
| 883 |
|
| 884 // ============================================================================= |
|
| 885 // ----------------------------------------------------------------------------- |
|
| 886 void LDFile::addObjects (const QList<LDObject*> objs) |
|
| 887 { for (LDObject * obj : objs) |
|
| 888 if (obj) |
|
| 889 addObject (obj); |
|
| 890 } |
|
| 891 |
|
| 892 // ============================================================================= |
|
| 893 // ----------------------------------------------------------------------------- |
|
| 894 void LDFile::insertObj (int pos, LDObject* obj) |
|
| 895 { m_history.add (new AddHistory (pos, obj)); |
|
| 896 m_objects.insert (pos, obj); |
|
| 897 obj->setFile (this); |
|
| 898 } |
|
| 899 |
|
| 900 // ============================================================================= |
|
| 901 // ----------------------------------------------------------------------------- |
|
| 902 void LDFile::forgetObject (LDObject* obj) |
|
| 903 { int idx = obj->getIndex(); |
|
| 904 m_history.add (new DelHistory (idx, obj)); |
|
| 905 m_objects.removeAt (idx); |
|
| 906 obj->setFile (null); |
|
| 907 } |
|
| 908 |
|
| 909 // ============================================================================= |
|
| 910 // ----------------------------------------------------------------------------- |
|
| 911 bool safeToCloseAll() |
|
| 912 { for (LDFile* f : g_loadedFiles) |
|
| 913 if (!f->safeToClose()) |
|
| 914 return false; |
|
| 915 |
|
| 916 return true; |
|
| 917 } |
|
| 918 |
|
| 919 // ============================================================================= |
|
| 920 // ----------------------------------------------------------------------------- |
|
| 921 void LDFile::setObject (int idx, LDObject* obj) |
|
| 922 { assert (idx < numObjs()); |
|
| 923 |
|
| 924 // Mark this change to history |
|
| 925 str oldcode = object (idx)->raw(); |
|
| 926 str newcode = obj->raw(); |
|
| 927 m_history << new EditHistory (idx, oldcode, newcode); |
|
| 928 |
|
| 929 obj->setFile (this); |
|
| 930 m_objects[idx] = obj; |
|
| 931 } |
|
| 932 |
|
| 933 // ============================================================================= |
|
| 934 // ----------------------------------------------------------------------------- |
|
| 935 static QList<LDFile*> getFilesUsed (LDFile* node) |
|
| 936 { QList<LDFile*> filesUsed; |
|
| 937 |
|
| 938 for (LDObject * obj : node->objects()) |
|
| 939 { if (obj->getType() != LDObject::Subfile) |
|
| 940 continue; |
|
| 941 |
|
| 942 LDSubfile* ref = static_cast<LDSubfile*> (obj); |
|
| 943 filesUsed << ref->fileInfo(); |
|
| 944 filesUsed << getFilesUsed (ref->fileInfo()); |
|
| 945 } |
|
| 946 |
|
| 947 return filesUsed; |
|
| 948 } |
|
| 949 |
|
| 950 // ============================================================================= |
|
| 951 // Find out which files are unused and close them. |
|
| 952 // ----------------------------------------------------------------------------- |
|
| 953 void LDFile::closeUnused() |
|
| 954 { QList<LDFile*> filesUsed = getFilesUsed (LDFile::current()); |
|
| 955 |
|
| 956 // Anything that's explicitly opened must not be closed |
|
| 957 for (LDFile* file : g_loadedFiles) |
|
| 958 if (!file->implicit()) |
|
| 959 filesUsed << file; |
|
| 960 |
|
| 961 // Remove duplicated entries |
|
| 962 removeDuplicates (filesUsed); |
|
| 963 |
|
| 964 // Close all open files that aren't in filesUsed |
|
| 965 for (LDFile* file : g_loadedFiles) |
|
| 966 { bool isused = false; |
|
| 967 |
|
| 968 for (LDFile* usedFile : filesUsed) |
|
| 969 { if (file == usedFile) |
|
| 970 { isused = true; |
|
| 971 break; |
|
| 972 } |
|
| 973 } |
|
| 974 |
|
| 975 if (!isused) |
|
| 976 delete file; |
|
| 977 } |
|
| 978 |
|
| 979 g_loadedFiles.clear(); |
|
| 980 g_loadedFiles << filesUsed; |
|
| 981 } |
|
| 982 |
|
| 983 // ============================================================================= |
|
| 984 // ----------------------------------------------------------------------------- |
|
| 985 LDObject* LDFile::object (int pos) const |
|
| 986 { if (m_objects.size() <= pos) |
|
| 987 return null; |
|
| 988 |
|
| 989 return m_objects[pos]; |
|
| 990 } |
|
| 991 |
|
| 992 // ============================================================================= |
|
| 993 // ----------------------------------------------------------------------------- |
|
| 994 LDObject* LDFile::obj (int pos) const |
|
| 995 { return object (pos); |
|
| 996 } |
|
| 997 |
|
| 998 // ============================================================================= |
|
| 999 // ----------------------------------------------------------------------------- |
|
| 1000 int LDFile::numObjs() const |
|
| 1001 { return objects().size(); |
|
| 1002 } |
|
| 1003 |
|
| 1004 // ============================================================================= |
|
| 1005 // ----------------------------------------------------------------------------- |
|
| 1006 bool LDFile::hasUnsavedChanges() const |
|
| 1007 { return !implicit() && history().pos() != savePos(); |
|
| 1008 } |
|
| 1009 |
|
| 1010 // ============================================================================= |
|
| 1011 // ----------------------------------------------------------------------------- |
|
| 1012 str LDFile::getShortName() |
|
| 1013 { if (!name().isEmpty()) |
|
| 1014 return basename (name()); |
|
| 1015 |
|
| 1016 if (!defaultName().isEmpty()) |
|
| 1017 return defaultName(); |
|
| 1018 |
|
| 1019 return tr ("<anonymous>"); |
|
| 1020 } |
|
| 1021 |
|
| 1022 // ============================================================================= |
|
| 1023 // ----------------------------------------------------------------------------- |
|
| 1024 QList<LDObject*> LDFile::inlineContents (LDSubfile::InlineFlags flags) |
|
| 1025 { // Possibly substitute with logoed studs: |
|
| 1026 // stud.dat -> stud-logo.dat |
|
| 1027 // stud2.dat -> stud-logo2.dat |
|
| 1028 if (gl_logostuds && (flags & LDSubfile::RendererInline)) |
|
| 1029 { if (name() == "stud.dat" && g_logoedStud) |
|
| 1030 return g_logoedStud->inlineContents (flags); |
|
| 1031 |
|
| 1032 elif (name() == "stud2.dat" && g_logoedStud2) |
|
| 1033 return g_logoedStud2->inlineContents (flags); |
|
| 1034 } |
|
| 1035 |
|
| 1036 QList<LDObject*> objs, objcache; |
|
| 1037 |
|
| 1038 bool deep = flags & LDSubfile::DeepInline, |
|
| 1039 doCache = flags & LDSubfile::CacheInline; |
|
| 1040 |
|
| 1041 // If we have this cached, just clone that |
|
| 1042 if (deep && cache().size()) |
|
| 1043 { for (LDObject * obj : cache()) |
|
| 1044 objs << obj->clone(); |
|
| 1045 } |
|
| 1046 else |
|
| 1047 { if (!deep) |
|
| 1048 doCache = false; |
|
| 1049 |
|
| 1050 for (LDObject * obj : objects()) |
|
| 1051 { // Skip those without scemantic meaning |
|
| 1052 if (!obj->isScemantic()) |
|
| 1053 continue; |
|
| 1054 |
|
| 1055 // Got another sub-file reference, inline it if we're deep-inlining. If not, |
|
| 1056 // just add it into the objects normally. Also, we only cache immediate |
|
| 1057 // subfiles and this is not one. Yay, recursion! |
|
| 1058 if (deep && obj->getType() == LDObject::Subfile) |
|
| 1059 { LDSubfile* ref = static_cast<LDSubfile*> (obj); |
|
| 1060 |
|
| 1061 // We only want to cache immediate subfiles, so shed the caching |
|
| 1062 // flag when recursing deeper in hierarchy. |
|
| 1063 QList<LDObject*> otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline)); |
|
| 1064 |
|
| 1065 for (LDObject * otherobj : otherobjs) |
|
| 1066 { // Cache this object, if desired |
|
| 1067 if (doCache) |
|
| 1068 objcache << otherobj->clone(); |
|
| 1069 |
|
| 1070 objs << otherobj; |
|
| 1071 } |
|
| 1072 } |
|
| 1073 else |
|
| 1074 { if (doCache) |
|
| 1075 objcache << obj->clone(); |
|
| 1076 |
|
| 1077 objs << obj->clone(); |
|
| 1078 } |
|
| 1079 } |
|
| 1080 |
|
| 1081 if (doCache) |
|
| 1082 setCache (objcache); |
|
| 1083 } |
|
| 1084 |
|
| 1085 return objs; |
|
| 1086 } |
|
| 1087 |
|
| 1088 // ============================================================================= |
|
| 1089 // ----------------------------------------------------------------------------- |
|
| 1090 LDFile* LDFile::current() |
|
| 1091 { return m_curfile; |
|
| 1092 } |
|
| 1093 |
|
| 1094 // ============================================================================= |
|
| 1095 // Sets the given file as the current one on display. At some point in time this |
|
| 1096 // was an operation completely unheard of. ;) |
|
| 1097 // |
|
| 1098 // FIXME: f can be temporarily null. This probably should not be the case. |
|
| 1099 // ----------------------------------------------------------------------------- |
|
| 1100 void LDFile::setCurrent (LDFile* f) |
|
| 1101 { // Implicit files were loaded for caching purposes and must never be set |
|
| 1102 // current. |
|
| 1103 if (f && f->implicit()) |
|
| 1104 return; |
|
| 1105 |
|
| 1106 m_curfile = f; |
|
| 1107 |
|
| 1108 if (g_win && f) |
|
| 1109 { // A ton of stuff needs to be updated |
|
| 1110 g_win->updateFileListItem (f); |
|
| 1111 g_win->buildObjList(); |
|
| 1112 g_win->updateTitle(); |
|
| 1113 g_vertexCompiler.needMerge(); |
|
| 1114 g_win->R()->setFile (f); |
|
| 1115 g_win->R()->resetAllAngles(); |
|
| 1116 g_win->R()->repaint(); |
|
| 1117 |
|
| 1118 log ("Changed file to %1", f->getShortName()); |
|
| 1119 } |
|
| 1120 } |
|
| 1121 |
|
| 1122 // ============================================================================= |
|
| 1123 // ----------------------------------------------------------------------------- |
|
| 1124 int LDFile::countExplicitFiles() |
|
| 1125 { int count = 0; |
|
| 1126 |
|
| 1127 for (LDFile* f : g_loadedFiles) |
|
| 1128 if (f->implicit() == false) |
|
| 1129 count++; |
|
| 1130 |
|
| 1131 return count; |
|
| 1132 } |
|
| 1133 |
|
| 1134 // ============================================================================= |
|
| 1135 // This little beauty closes the initial file that was open at first when opening |
|
| 1136 // a new file over it. |
|
| 1137 // ----------------------------------------------------------------------------- |
|
| 1138 void LDFile::closeInitialFile() |
|
| 1139 { if ( |
|
| 1140 countExplicitFiles() == 2 && |
|
| 1141 g_loadedFiles[0]->name() == "" && |
|
| 1142 !g_loadedFiles[0]->hasUnsavedChanges() |
|
| 1143 ) |
|
| 1144 delete g_loadedFiles[0]; |
|
| 1145 } |
|
| 1146 |
|
| 1147 // ============================================================================= |
|
| 1148 // ----------------------------------------------------------------------------- |
|
| 1149 void loadLogoedStuds() |
|
| 1150 { log ("Loading logoed studs...\n"); |
|
| 1151 |
|
| 1152 delete g_logoedStud; |
|
| 1153 delete g_logoedStud2; |
|
| 1154 |
|
| 1155 g_logoedStud = openDATFile ("stud-logo.dat", true); |
|
| 1156 g_logoedStud2 = openDATFile ("stud2-logo.dat", true); |
|
| 1157 } |
|
| 1158 |
|
| 1159 // ============================================================================= |
|
| 1160 // ----------------------------------------------------------------------------- |
|
| 1161 void LDFile::addToSelection (LDObject* obj) // [protected] |
|
| 1162 { if (obj->selected()) |
|
| 1163 return; |
|
| 1164 |
|
| 1165 assert (obj->file() == this); |
|
| 1166 m_sel << obj; |
|
| 1167 obj->setSelected (true); |
|
| 1168 } |
|
| 1169 |
|
| 1170 // ============================================================================= |
|
| 1171 // ----------------------------------------------------------------------------- |
|
| 1172 void LDFile::removeFromSelection (LDObject* obj) // [protected] |
|
| 1173 { if (!obj->selected()) |
|
| 1174 return; |
|
| 1175 |
|
| 1176 assert (obj->file() == this); |
|
| 1177 m_sel.removeOne (obj); |
|
| 1178 obj->setSelected (false); |
|
| 1179 } |
|
| 1180 |
|
| 1181 // ============================================================================= |
|
| 1182 // ----------------------------------------------------------------------------- |
|
| 1183 void LDFile::clearSelection() |
|
| 1184 { for (LDObject* obj : m_sel) |
|
| 1185 removeFromSelection (obj); |
|
| 1186 |
|
| 1187 assert (m_sel.isEmpty()); |
|
| 1188 } |
|
| 1189 |
|
| 1190 // ============================================================================= |
|
| 1191 // ----------------------------------------------------------------------------- |
|
| 1192 const QList<LDObject*>& LDFile::selection() const |
|
| 1193 { return m_sel; |
|
| 1194 } |
|