Fri, 24 May 2013 03:22:11 +0300
Added a progress dialog for file loading to respond to desktops while loading files. With large files the no-response policy could be a bad thing. My first real use case of multi-threading...
src/addObjectDialog.cpp | file | annotate | diff | comparison | revisions | |
src/colors.cpp | file | annotate | diff | comparison | revisions | |
src/common.h | file | annotate | diff | comparison | revisions | |
src/dialogs.cpp | file | annotate | diff | comparison | revisions | |
src/dialogs.h | file | annotate | diff | comparison | revisions | |
src/file.cpp | file | annotate | diff | comparison | revisions | |
src/file.h | file | annotate | diff | comparison | revisions |
--- a/src/addObjectDialog.cpp Thu May 23 17:37:25 2013 +0300 +++ b/src/addObjectDialog.cpp Fri May 24 03:22:11 2013 +0300 @@ -185,7 +185,7 @@ break; default: - assert (false); + critical (fmt ("Unhandled LDObject type %d (%s) in AddObjectDialog", (int) type, g_saObjTypeNames[type])); return; }
--- a/src/colors.cpp Thu May 23 17:37:25 2013 +0300 +++ b/src/colors.cpp Fri May 24 03:22:11 2013 +0300 @@ -20,6 +20,7 @@ #include "colors.h" #include "file.h" #include "misc.h" +#include "gui.h" #include <qcolor.h> static color* g_LDColors[MAX_COLORS]; @@ -79,8 +80,10 @@ void parseLDConfig () { FILE* fp = openLDrawFile ("LDConfig.ldr", false); - if (!fp) + if (!fp) { + critical (fmt ("Unable to open LDConfig.ldr for parsing! (%s)", strerror (errno))); return; + } // Read in the lines char buf[1024];
--- a/src/common.h Thu May 23 17:37:25 2013 +0300 +++ b/src/common.h Fri May 24 03:22:11 2013 +0300 @@ -88,13 +88,42 @@ private: \ T m_##GET; \ public: \ - const T& GET () const { return m_##GET; } \ + T const& GET () const { return m_##GET; } \ // Read/write private property with get and set accessors #define PROPERTY(T, GET, SET) \ READ_PROPERTY(T, GET) \ void SET (T val) { m_##GET = val; } +// Property that triggers a callback when it is changed +#define CALLBACK_PROPERTY(T, GET, SET) \ + READ_PROPERTY(T, GET) \ + void callback_##SET (); \ + void SET (T val) { m_##GET = val; callback_##SET (); } + +// Property with thread locking, use when multiple threads access the same property +// Comes with a callback function for detecting when the value is changed. +#define THREAD_PROPERTY(T, GET, SET) \ +private: \ + T m_##GET; \ + bool m_threadLock_##GET; \ +public: \ + const T& GET () const { \ + while (m_threadLock_##GET) \ + ; \ + return m_##GET; \ + } \ + void callback_##SET (); \ + void SET (T val) { \ + while (m_threadLock_##GET) \ + ; \ + \ + m_threadLock_##GET = true; \ + m_##GET = val; \ + callback_##SET (); \ + m_threadLock_##GET = false; \ + } + #ifdef null #undef null #endif // null @@ -112,6 +141,7 @@ void assertionFailure (const char* file, const ulong line, const char* funcname, const char* expr); void fatalError (const char* file, const ulong line, const char* funcname, str errmsg); + #define assert(N) \ (N) ? ((void)(0)) : assertionFailure(__FILE__, __LINE__, FUNCNAME, #N) @@ -169,6 +199,8 @@ static const double pi = 3.14159265358979323846f; #ifdef IN_IDE_PARSER // KDevelop workarounds: +#error IN_IDE_PARSER is defined (this code is only for KDevelop workarounds) + // Current function name static const char* __func__ = ""; @@ -179,6 +211,8 @@ #ifndef va_end #define va_end(va) #endif // va_end + +typedef void FILE; // :| #endif // IN_IDE_PARSER // -----------------------------------------------------------------------------
--- a/src/dialogs.cpp Thu May 23 17:37:25 2013 +0300 +++ b/src/dialogs.cpp Fri May 24 03:22:11 2013 +0300 @@ -25,6 +25,7 @@ #include <QPushButton> #include <QBoxLayout> #include <QGridLayout> +#include <qprogressbar.h> #include "dialogs.h" #include "widgets.h" @@ -423,6 +424,9 @@ g_win->fullRefresh (); } +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= RotationPointDialog::RotationPointDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) { rb_rotpoint = new RadioBox ("Rotation Point", { "Object center", "Custom" }, 0, Qt::Vertical, this); connect (rb_rotpoint, SIGNAL (valueChanged (int)), this, SLOT (radioBoxChanged ())); @@ -471,4 +475,38 @@ void RotationPointDialog::radioBoxChanged () { setCustom (rb_rotpoint->value ()); +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +OpenFileDialog::OpenFileDialog (QWidget* parent, Qt::WindowFlags f) : QDialog (parent, f) { + progressBar = new QProgressBar; + progressText = new QLabel; + setNumLines (0); + m_progress = 0; + + QDialogButtonBox* dbb_buttons = new QDialogButtonBox; + dbb_buttons->addButton (QDialogButtonBox::Cancel); + connect (dbb_buttons, SIGNAL (rejected ()), this, SLOT (reject ())); + + QVBoxLayout* layout = new QVBoxLayout (this); + layout->addWidget (progressText); + layout->addWidget (progressBar); + layout->addWidget (dbb_buttons); +} + +void OpenFileDialog::callback_setNumLines () { + progressBar->setRange (0, numLines ()); + updateValues (); +} + +void OpenFileDialog::updateValues () { + progressBar->setValue (progress ()); + progressText->setText (fmt ("%s: %lu/%lu lines parsed", fileName ().c (), progress (), numLines ())); +} + +void OpenFileDialog::updateProgress (int progress) { + m_progress = progress; + updateValues (); } \ No newline at end of file
--- a/src/dialogs.h Thu May 23 17:37:25 2013 +0300 +++ b/src/dialogs.h Fri May 24 03:22:11 2013 +0300 @@ -22,6 +22,7 @@ #include <QDialog> #include "common.h" +class QProgressBar; class QGroupBox; class QDialogButtonBox; class QDoubleSpinBox; @@ -154,4 +155,24 @@ void radioBoxChanged (); }; +// ============================================================================= +class OpenFileDialog : public QDialog { + Q_OBJECT + READ_PROPERTY (ulong, progress) + CALLBACK_PROPERTY (ulong, numLines, setNumLines) + PROPERTY (str, fileName, setFileName) + +public: + explicit OpenFileDialog (QWidget* parent = null, Qt::WindowFlags f = 0); + +public slots: + void updateProgress (int progress); + +private: + QProgressBar* progressBar; + QLabel* progressText; + + void updateValues (); +}; + #endif // DIALOGS_H \ No newline at end of file
--- a/src/file.cpp Thu May 23 17:37:25 2013 +0300 +++ b/src/file.cpp Fri May 24 03:22:11 2013 +0300 @@ -19,6 +19,7 @@ #include <QMessageBox> #include <QFileDialog> #include <QDir> +#include <qthread.h> #include <stdlib.h> #include "common.h" @@ -34,6 +35,8 @@ cfg (str, io_ldpath, ""); cfg (str, io_recentfiles, ""); +static bool g_loadingMainFile = false; + // ============================================================================= namespace LDPaths { static str pathError; @@ -102,9 +105,9 @@ } // ============================================================================= -LDOpenFile* findLoadedFile (str zName) { +LDOpenFile* findLoadedFile (str name) { for (LDOpenFile* file : g_loadedFiles) - if (file->m_filename == zName) + if (file->m_filename == name) return file; return null; @@ -138,6 +141,7 @@ // ============================================================================= FILE* openLDrawFile (str relpath, bool subdirs) { printf ("%s: Try to open %s\n", __func__, relpath.c ()); + #ifndef WIN32 relpath.replace ("\\", "/"); #endif // WIN32 @@ -187,16 +191,12 @@ // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= -std::vector<LDObject*> loadFileContents (FILE* fp, ulong* numWarnings) { +void FileLoader::work () { char line[1024]; - vector<str> lines; - vector<LDObject*> objs; - ulong lnum = 0; + m_progress = 0; + abortflag = false; - if (numWarnings) - *numWarnings = 0; - - while (fgets (line, sizeof line, fp)) { + while (fgets (line, sizeof line, filePointer ())) { // Trim the trailing newline str data = line; while (data[~data - 1] == '\n' || data[~data - 1] == '\r') @@ -206,27 +206,99 @@ assert (obj != null); // Check for parse errors and warn about tthem - if (obj->getType() == LDObject::Gibberish) { + if (obj->getType () == LDObject::Gibberish) { logf (LOG_Warning, "Couldn't parse line #%lu: %s\n", - lnum, static_cast<LDGibberish*> (obj)->reason.chars()); + m_progress + 1, static_cast<LDGibberish*> (obj)->reason.chars()); logf (LOG_Warning, "- Line was: %s\n", data.chars()); - if (numWarnings) - (*numWarnings)++; + if (m_warningsPointer) + (*m_warningsPointer)++; } - objs.push_back (obj); - lnum++; + m_objs.push_back (obj); + m_progress++; + emit progressUpdate (m_progress); + + if (abortflag) { + // We were flagged for abortion, so abort. + for (LDObject* obj : m_objs) + delete obj; + + m_objs.clear (); + return; + } } + emit workDone (); + m_done = true; +} + +// ============================================================================= +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// ============================================================================= +std::vector<LDObject*> loadFileContents (FILE* fp, ulong* numWarnings, bool* ok) { + vector<str> lines; + vector<LDObject*> objs; + + if (numWarnings) + *numWarnings = 0; + + FileLoader* loader = new FileLoader; + loader->setFilePointer (fp); + loader->setWarningsPointer (numWarnings); + + // Calculate the amount of lines + ulong numLines = 0; + char line[1024]; + while (fgets (line, sizeof line, fp)) + numLines++; + + rewind (fp); + + if (g_loadingMainFile) { + // Show a progress dialog if we're loading the main file here and move + // the actual work to a separate thread as this can be a rather intensive + // operation and if we don't respond quickly enough, the program can be + // deemed inresponsive.. which is a bad thing. + + // Init the thread and move the loader into it + QThread* loaderThread = new QThread; + QObject::connect (loaderThread, SIGNAL (started ()), loader, SLOT (work ())); + QObject::connect (loaderThread, SIGNAL (finished ()), loader, SLOT (deleteLater ())); + loader->moveToThread (loaderThread); + loaderThread->start (); + + // Now create a progress dialog for the operation + OpenFileDialog* dlg = new OpenFileDialog (g_win); + dlg->setFileName ("???"); + dlg->setNumLines (numLines); + + // Connect the loader in so we can actually show updates + QObject::connect (loader, SIGNAL (progressUpdate (int)), dlg, SLOT (updateProgress (int))); + QObject::connect (loader, SIGNAL (workDone ()), dlg, SLOT (accept ())); + + // Show the dialog. If the user hits cancel, tell the loader to abort. + if (!dlg->exec ()) + loader->abortflag = true; + } else + loader->work (); + + // If we wanted the success value, supply that now + if (ok) + *ok = loader->done (); + + // If the loader was done, return the objects it generated + if (loader->done ()) + objs = loader->objs (); + return objs; } // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= -LDOpenFile* openDATFile (str path, bool search, bool mainfile) { +LDOpenFile* openDATFile (str path, bool search) { logf ("Opening %s...\n", path.chars()); // Convert the file name to lowercase since some parts contain uppercase @@ -243,14 +315,21 @@ return null; } + LDOpenFile* oldLoad = g_curfile; LDOpenFile* load = new LDOpenFile; load->m_filename = path; - if (mainfile) + if (g_loadingMainFile) g_curfile = load; ulong numWarnings; - std::vector<LDObject*> objs = loadFileContents (fp, &numWarnings); + bool ok; + std::vector<LDObject*> objs = loadFileContents (fp, &numWarnings, &ok); + + if (!ok) { + load = oldLoad; + return null; + } for (LDObject* obj : objs) load->m_objs.push_back (obj); @@ -392,14 +471,17 @@ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= void openMainFile (str path) { + g_loadingMainFile = true; closeAll (); - LDOpenFile* file = openDATFile (path, false, true); + LDOpenFile* file = openDATFile (path, false); if (!file) { // Tell the user loading failed. setlocale (LC_ALL, "C"); critical (fmt ("Failed to open %s: %s", path.chars(), strerror (errno))); + + g_loadingMainFile = false; return; } @@ -418,6 +500,7 @@ // Add it to the recent files list. addRecentFile (path); + g_loadingMainFile = false; } // ============================================================================= @@ -598,11 +681,15 @@ CHECK_TOKEN_COUNT (15) CHECK_TOKEN_NUMBERS (1, 13) - // Try open the file - LDOpenFile* pFile = loadSubfile (tokens[14]); + // Try open the file. Disable g_loadingMainFile temporarily since we're + // not loading the main file now, but the subfile + bool oldLoadingMainFile = g_loadingMainFile; + g_loadingMainFile = false; + LDOpenFile* load = loadSubfile (tokens[14]); + g_loadingMainFile = oldLoadingMainFile; // If we cannot open the file, mark it an error - if (!pFile) + if (!load) return new LDGibberish (line, "Could not open referred file"); LDSubfile* obj = new LDSubfile; @@ -613,7 +700,7 @@ obj->transform[i] = atof (tokens[i + 5]); // 5 - 13 obj->fileName = tokens[14]; - obj->fileInfo = pFile; + obj->fileInfo = load; return obj; } @@ -683,13 +770,15 @@ // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= -LDOpenFile* loadSubfile (str zFile) { - // Try open the file - LDOpenFile* pFile = findLoadedFile (zFile); - if (!pFile) - pFile = openDATFile (zFile, true, false); +LDOpenFile* loadSubfile (str fname) { + // Try find the subfile in the list of loaded files + LDOpenFile* load = findLoadedFile (fname); - return pFile; + // If it's not loaded, try open it + if (!load) + load = openDATFile (fname, true); + + return load; } // ============================================================================= @@ -710,12 +799,12 @@ // Go through all objects in the current file and reload the subfiles for (LDObject* obj : g_curfile->m_objs) { if (obj->getType() == LDObject::Subfile) { - // Note: ref->pFile is invalid right now since all subfiles were closed. + // Note: ref->fileInfo is invalid right now since all subfiles were closed. LDSubfile* ref = static_cast<LDSubfile*> (obj); - LDOpenFile* pFile = loadSubfile (ref->fileName); + LDOpenFile* fileInfo = loadSubfile (ref->fileName); - if (pFile) - ref->fileInfo = pFile; + if (fileInfo) + ref->fileInfo = fileInfo; else { // Couldn't load the file, mark it an error ref->replace (new LDGibberish (ref->getContents (), "Could not open referred file")); @@ -723,7 +812,7 @@ } // Reparse gibberish files. It could be that they are invalid because - // the file could not be opened. Circumstances may be different now. + // of loading errors. Circumstances may be different now. if (obj->getType() == LDObject::Gibberish) obj->replace (parseLine (static_cast<LDGibberish*> (obj)->contents)); }
--- a/src/file.h Thu May 23 17:37:25 2013 +0300 +++ b/src/file.h Fri May 24 03:22:11 2013 +0300 @@ -21,7 +21,9 @@ #include "common.h" #include "ldtypes.h" +#include <QObject> +class OpenFileDialog; namespace LDPaths { void initPaths (); bool tryConfigure (str path); @@ -94,7 +96,7 @@ // Opens the given file and parses the LDraw code within. Returns a pointer // to the opened file or null on error. -LDOpenFile* openDATFile (str path, bool search, bool mainfile); +LDOpenFile* openDATFile (str path, bool search); // Opens the given file and returns a pointer to it, potentially looking in /parts and /p FILE* openLDrawFile (str path, bool bSubDirectories); @@ -121,7 +123,7 @@ // Init and parse parts.lst void initPartList (); -std::vector< LDObject* > loadFileContents (FILE* fp, ulong* numWarnings); +std::vector<LDObject*> loadFileContents (FILE* fp, ulong* numWarnings, bool* ok = null); extern vector<LDOpenFile*> g_loadedFiles; extern vector<partListEntry> g_PartList; @@ -129,4 +131,24 @@ str basename (str path); str dirname (str path); +class FileLoader : public QObject { + Q_OBJECT + + READ_PROPERTY (std::vector<LDObject*>, objs) + READ_PROPERTY (bool, done) + READ_PROPERTY (ulong, progress) + PROPERTY (FILE*, filePointer, setFilePointer) + PROPERTY (ulong*, warningsPointer, setWarningsPointer) + +public: + bool abortflag; + +public slots: + void work (); + +signals: + void progressUpdate (int progress); + void workDone (); +}; + #endif // FILE_H \ No newline at end of file