| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the tools applications of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
| 21 | ** included in the packaging of this file. Please review the following | 
| 22 | ** information to ensure the GNU General Public License requirements will | 
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
| 24 | ** | 
| 25 | ** $QT_END_LICENSE$ | 
| 26 | ** | 
| 27 | ****************************************************************************/ | 
| 28 |  | 
| 29 | #include <QtCore/qabstractanimation.h> | 
| 30 | #include <QtCore/qdir.h> | 
| 31 | #include <QtCore/qmath.h> | 
| 32 | #include <QtCore/qelapsedtimer.h> | 
| 33 | #include <QtCore/qpointer.h> | 
| 34 | #include <QtCore/qscopedpointer.h> | 
| 35 | #include <QtCore/qtextstream.h> | 
| 36 | #include <QtCore/qregularexpression.h> | 
| 37 |  | 
| 38 | #include <QtGui/QGuiApplication> | 
| 39 | #include <QtGui/QOpenGLFunctions> | 
| 40 |  | 
| 41 | #include <QtQml/qqml.h> | 
| 42 | #include <QtQml/qqmlengine.h> | 
| 43 | #include <QtQml/qqmlcomponent.h> | 
| 44 | #include <QtQml/qqmlcontext.h> | 
| 45 | #include <QtQml/qqmlfileselector.h> | 
| 46 |  | 
| 47 | #include <QtQuick/qquickitem.h> | 
| 48 | #include <QtQuick/qquickview.h> | 
| 49 |  | 
| 50 | #include <private/qabstractanimation_p.h> | 
| 51 | #include <private/qopenglcontext_p.h> | 
| 52 |  | 
| 53 | #ifdef QT_WIDGETS_LIB | 
| 54 | #include <QtWidgets/QApplication> | 
| 55 | #if QT_CONFIG(filedialog) | 
| 56 | #include <QtWidgets/QFileDialog> | 
| 57 | #endif // QT_CONFIG(filedialog) | 
| 58 | #endif // QT_WIDGETS_LIB | 
| 59 |  | 
| 60 | #include <QtCore/QTranslator> | 
| 61 | #include <QtCore/QLibraryInfo> | 
| 62 |  | 
| 63 | #ifdef QML_RUNTIME_TESTING | 
| 64 | class RenderStatistics | 
| 65 | { | 
| 66 | public: | 
| 67 |     static void updateStats(); | 
| 68 |     static void printTotalStats(); | 
| 69 | private: | 
| 70 |     static QVector<qreal> timePerFrame; | 
| 71 |     static QVector<int> timesPerFrames; | 
| 72 | }; | 
| 73 |  | 
| 74 | QVector<qreal> RenderStatistics::timePerFrame; | 
| 75 | QVector<int> RenderStatistics::timesPerFrames; | 
| 76 |  | 
| 77 | void RenderStatistics::updateStats() | 
| 78 | { | 
| 79 |     static QElapsedTimer time; | 
| 80 |     static int frames; | 
| 81 |     static int lastTime; | 
| 82 |  | 
| 83 |     if (frames == 0) { | 
| 84 |         time.start(); | 
| 85 |     } else { | 
| 86 |         int elapsed = time.elapsed(); | 
| 87 |         timesPerFrames.append(t: elapsed - lastTime); | 
| 88 |         lastTime = elapsed; | 
| 89 |  | 
| 90 |         if (elapsed > 5000) { | 
| 91 |             qreal avgtime = elapsed / (qreal) frames; | 
| 92 |             qreal var = 0; | 
| 93 |             for (int i = 0; i < timesPerFrames.size(); ++i) { | 
| 94 |                 qreal diff = timesPerFrames.at(i) - avgtime; | 
| 95 |                 var += diff * diff; | 
| 96 |             } | 
| 97 |             var /= timesPerFrames.size(); | 
| 98 |  | 
| 99 |             printf(format: "Average time per frame: %f ms (%i fps), std.dev: %f ms\n" , avgtime, qRound(d: 1000. / avgtime), qSqrt(v: var)); | 
| 100 |  | 
| 101 |             timePerFrame.append(t: avgtime); | 
| 102 |             timesPerFrames.clear(); | 
| 103 |             time.start(); | 
| 104 |             lastTime = 0; | 
| 105 |             frames = 0; | 
| 106 |         } | 
| 107 |     } | 
| 108 |     ++frames; | 
| 109 | } | 
| 110 |  | 
| 111 | void RenderStatistics::printTotalStats() | 
| 112 | { | 
| 113 |     int count = timePerFrame.count(); | 
| 114 |     if (count == 0) | 
| 115 |         return; | 
| 116 |  | 
| 117 |     qreal minTime = 0; | 
| 118 |     qreal maxTime = 0; | 
| 119 |     qreal avg = 0; | 
| 120 |     for (int i = 0; i < count; ++i) { | 
| 121 |         minTime = minTime == 0 ? timePerFrame.at(i) : qMin(a: minTime, b: timePerFrame.at(i)); | 
| 122 |         maxTime = qMax(a: maxTime, b: timePerFrame.at(i)); | 
| 123 |         avg += timePerFrame.at(i); | 
| 124 |     } | 
| 125 |     avg /= count; | 
| 126 |  | 
| 127 |     puts(s: " " ); | 
| 128 |     puts(s: "----- Statistics -----" ); | 
| 129 |     printf(format: "Average time per frame: %f ms (%i fps)\n" , avg, qRound(d: 1000. / avg)); | 
| 130 |     printf(format: "Best time per frame: %f ms (%i fps)\n" , minTime, int(1000 / minTime)); | 
| 131 |     printf(format: "Worst time per frame: %f ms (%i fps)\n" , maxTime, int(1000 / maxTime)); | 
| 132 |     puts(s: "----------------------" ); | 
| 133 |     puts(s: " " ); | 
| 134 | } | 
| 135 | #endif | 
| 136 |  | 
| 137 | struct Options | 
| 138 | { | 
| 139 |     enum QmlApplicationType | 
| 140 |     { | 
| 141 |         QmlApplicationTypeGui, | 
| 142 |         QmlApplicationTypeWidget, | 
| 143 | #ifdef QT_WIDGETS_LIB | 
| 144 |         DefaultQmlApplicationType = QmlApplicationTypeWidget | 
| 145 | #else | 
| 146 |         DefaultQmlApplicationType = QmlApplicationTypeGui | 
| 147 | #endif | 
| 148 |     }; | 
| 149 |  | 
| 150 |     Options() | 
| 151 |         : textRenderType(QQuickWindow::textRenderType()) | 
| 152 |     { | 
| 153 |         // QtWebEngine needs a shared context in order for the GPU thread to | 
| 154 |         // upload textures. | 
| 155 |         applicationAttributes.append(t: Qt::AA_ShareOpenGLContexts); | 
| 156 |     } | 
| 157 |  | 
| 158 |     QUrl url; | 
| 159 |     bool originalQml = false; | 
| 160 |     bool originalQmlRaster = false; | 
| 161 |     bool maximized = false; | 
| 162 |     bool fullscreen = false; | 
| 163 |     bool transparent = false; | 
| 164 |     bool clip = false; | 
| 165 |     bool versionDetection = true; | 
| 166 |     bool slowAnimations = false; | 
| 167 |     bool quitImmediately = false; | 
| 168 |     bool resizeViewToRootItem = false; | 
| 169 |     bool multisample = false; | 
| 170 |     bool coreProfile = false; | 
| 171 |     bool verbose = false; | 
| 172 |     bool rhi = false; | 
| 173 |     bool rhiBackendSet = false; | 
| 174 |     QVector<Qt::ApplicationAttribute> applicationAttributes; | 
| 175 |     QString translationFile; | 
| 176 |     QmlApplicationType applicationType = DefaultQmlApplicationType; | 
| 177 |     QQuickWindow::TextRenderType textRenderType; | 
| 178 |     QString rhiBackend; | 
| 179 | }; | 
| 180 |  | 
| 181 | #if defined(QMLSCENE_BUNDLE) | 
| 182 | QFileInfoList findQmlFiles(const QString &dirName) | 
| 183 | { | 
| 184 |     QDir dir(dirName); | 
| 185 |  | 
| 186 |     QFileInfoList ret; | 
| 187 |     if (dir.exists()) { | 
| 188 |         const QFileInfoList fileInfos = dir.entryInfoList(QStringList() << "*.qml" , | 
| 189 |                                                           QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot); | 
| 190 |  | 
| 191 |         for (const QFileInfo &fileInfo : fileInfos) { | 
| 192 |             if (fileInfo.isDir()) | 
| 193 |                 ret += findQmlFiles(fileInfo.filePath()); | 
| 194 |             else if (fileInfo.fileName().length() > 0 && fileInfo.fileName().at(0).isLower()) | 
| 195 |                 ret.append(fileInfo); | 
| 196 |         } | 
| 197 |     } | 
| 198 |  | 
| 199 |     return ret; | 
| 200 | } | 
| 201 |  | 
| 202 | static int displayOptionsDialog(Options *options) | 
| 203 | { | 
| 204 |     QDialog dialog; | 
| 205 |  | 
| 206 |     QFormLayout *layout = new QFormLayout(&dialog); | 
| 207 |  | 
| 208 |     QComboBox *qmlFileComboBox = new QComboBox(&dialog); | 
| 209 |     const QFileInfoList fileInfos = findQmlFiles(":/bundle" ) + findQmlFiles("./qmlscene-resources" ); | 
| 210 |  | 
| 211 |     for (const QFileInfo &fileInfo : fileInfos) | 
| 212 |         qmlFileComboBox->addItem(fileInfo.dir().dirName() + QLatin1Char('/') + fileInfo.fileName(), QVariant::fromValue(fileInfo)); | 
| 213 |  | 
| 214 |     QCheckBox *originalCheckBox = new QCheckBox(&dialog); | 
| 215 |     originalCheckBox->setText("Use original QML viewer" ); | 
| 216 |     originalCheckBox->setChecked(options->originalQml); | 
| 217 |  | 
| 218 |     QCheckBox *fullscreenCheckBox = new QCheckBox(&dialog); | 
| 219 |     fullscreenCheckBox->setText("Start fullscreen" ); | 
| 220 |     fullscreenCheckBox->setChecked(options->fullscreen); | 
| 221 |  | 
| 222 |     QCheckBox *maximizedCheckBox = new QCheckBox(&dialog); | 
| 223 |     maximizedCheckBox->setText("Start maximized" ); | 
| 224 |     maximizedCheckBox->setChecked(options->maximized); | 
| 225 |  | 
| 226 |     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, | 
| 227 |                                                        Qt::Horizontal, | 
| 228 |                                                        &dialog); | 
| 229 |     QObject::connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept())); | 
| 230 |     QObject::connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject())); | 
| 231 |  | 
| 232 |     layout->addRow("Qml file:" , qmlFileComboBox); | 
| 233 |     layout->addWidget(originalCheckBox); | 
| 234 |     layout->addWidget(maximizedCheckBox); | 
| 235 |     layout->addWidget(fullscreenCheckBox); | 
| 236 |     layout->addWidget(buttonBox); | 
| 237 |  | 
| 238 |     int result = dialog.exec(); | 
| 239 |     if (result == QDialog::Accepted) { | 
| 240 |         QVariant variant = qmlFileComboBox->itemData(qmlFileComboBox->currentIndex()); | 
| 241 |         QFileInfo fileInfo = variant.value<QFileInfo>(); | 
| 242 |  | 
| 243 |         if (fileInfo.canonicalFilePath().startsWith(QLatin1Char(':'))) | 
| 244 |             options->file = QUrl("qrc"  + fileInfo.canonicalFilePath()); | 
| 245 |         else | 
| 246 |             options->file = QUrl::fromLocalFile(fileInfo.canonicalFilePath()); | 
| 247 |         options->originalQml = originalCheckBox->isChecked(); | 
| 248 |         options->maximized = maximizedCheckBox->isChecked(); | 
| 249 |         options->fullscreen = fullscreenCheckBox->isChecked(); | 
| 250 |     } | 
| 251 |     return result; | 
| 252 | } | 
| 253 | #endif | 
| 254 |  | 
| 255 | static bool checkVersion(const QUrl &url) | 
| 256 | { | 
| 257 |     if (!qgetenv(varName: "QMLSCENE_IMPORT_NAME" ).isEmpty()) | 
| 258 |         fprintf(stderr, format: "QMLSCENE_IMPORT_NAME is no longer supported.\n" ); | 
| 259 |  | 
| 260 |     if (!url.isLocalFile()) | 
| 261 |         return true; | 
| 262 |  | 
| 263 |     const QString fileName = url.toLocalFile(); | 
| 264 |     QFile f(fileName); | 
| 265 |     if (!f.open(flags: QFile::ReadOnly | QFile::Text)) { | 
| 266 |         fprintf(stderr, format: "qmlscene: failed to check version of file '%s', could not open...\n" , | 
| 267 |                  qPrintable(fileName)); | 
| 268 |         return false; | 
| 269 |     } | 
| 270 |  | 
| 271 |     QRegularExpression quick1("^\\s*import +QtQuick +1\\.\\w*" ); | 
| 272 |     QRegularExpression qt47("^\\s*import +Qt +4\\.7" ); | 
| 273 |  | 
| 274 |     QTextStream stream(&f); | 
| 275 |     bool codeFound= false; | 
| 276 |     while (!codeFound) { | 
| 277 |         if (stream.atEnd()) { | 
| 278 |             fprintf(stderr, format: "qmlscene: no code found in file '%s'.\n" , qPrintable(fileName)); | 
| 279 |             return false; | 
| 280 |         } | 
| 281 |         QString line = stream.readLine(); | 
| 282 |         if (line.contains(c: QLatin1Char('{'))) { | 
| 283 |             codeFound = true; | 
| 284 |         } else { | 
| 285 |             QString import; | 
| 286 |             QRegularExpressionMatch match = quick1.match(subject: line); | 
| 287 |             if (match.hasMatch()) | 
| 288 |                 import = match.captured(nth: 0).trimmed(); | 
| 289 |             else if ((match = qt47.match(subject: line)).hasMatch()) | 
| 290 |                 import = match.captured(nth: 0).trimmed(); | 
| 291 |  | 
| 292 |             if (!import.isNull()) { | 
| 293 |                 fprintf(stderr, format: "qmlscene: '%s' is no longer supported.\n"  | 
| 294 |                          "Use qmlviewer to load file '%s'.\n" , | 
| 295 |                          qPrintable(import), | 
| 296 |                          qPrintable(fileName)); | 
| 297 |                 return false; | 
| 298 |             } | 
| 299 |         } | 
| 300 |     } | 
| 301 |  | 
| 302 |     return true; | 
| 303 | } | 
| 304 |  | 
| 305 | static void displayFileDialog(Options *options) | 
| 306 | { | 
| 307 | #if defined(QT_WIDGETS_LIB) && QT_CONFIG(filedialog) | 
| 308 |     if (options->applicationType == Options::QmlApplicationTypeWidget) { | 
| 309 |         QString fileName = QFileDialog::getOpenFileName(parent: nullptr, caption: "Open QML file" , dir: QString(), filter: "QML Files (*.qml)" ); | 
| 310 |         if (!fileName.isEmpty()) { | 
| 311 |             QFileInfo fi(fileName); | 
| 312 |             options->url = QUrl::fromLocalFile(localfile: fi.canonicalFilePath()); | 
| 313 |         } | 
| 314 |         return; | 
| 315 |     } | 
| 316 | #endif // QT_WIDGETS_LIB && QT_CONFIG(filedialog) | 
| 317 |     Q_UNUSED(options); | 
| 318 |     puts(s: "No filename specified..." ); | 
| 319 | } | 
| 320 |  | 
| 321 | static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory) | 
| 322 | { | 
| 323 |     QDir dir(directory+"/dummydata" , "*.qml" ); | 
| 324 |     QStringList list = dir.entryList(); | 
| 325 |     for (int i = 0; i < list.size(); ++i) { | 
| 326 |         QString qml = list.at(i); | 
| 327 |         QQmlComponent comp(&engine, dir.filePath(fileName: qml)); | 
| 328 |         QObject *dummyData = comp.create(); | 
| 329 |  | 
| 330 |         if(comp.isError()) { | 
| 331 |             const QList<QQmlError> errors = comp.errors(); | 
| 332 |             for (const QQmlError &error : errors) | 
| 333 |                 fprintf(stderr, format: "%s\n" , qPrintable(error.toString())); | 
| 334 |         } | 
| 335 |  | 
| 336 |         if (dummyData) { | 
| 337 |             fprintf(stderr, format: "Loaded dummy data: %s\n" , qPrintable(dir.filePath(qml))); | 
| 338 |             qml.truncate(pos: qml.length()-4); | 
| 339 |             engine.rootContext()->setContextProperty(qml, dummyData); | 
| 340 |             dummyData->setParent(&engine); | 
| 341 |         } | 
| 342 |     } | 
| 343 | } | 
| 344 |  | 
| 345 | static void usage() | 
| 346 | { | 
| 347 |     puts(s: "Usage: qmlscene [options] <filename>" ); | 
| 348 |     puts(s: " " ); | 
| 349 |     puts(s: " Options:" ); | 
| 350 |     puts(s: "  --maximized ...................... Run maximized" ); | 
| 351 |     puts(s: "  --fullscreen ..................... Run fullscreen" ); | 
| 352 |     puts(s: "  --transparent .................... Make the window transparent" ); | 
| 353 |     puts(s: "  --multisample .................... Enable multisampling (OpenGL anti-aliasing)" ); | 
| 354 |     puts(s: "  --core-profile ................... Request a core profile OpenGL context" ); | 
| 355 |     puts(s: "  --rhi [vulkan|metal|d3d11|gl] .... Use the Qt graphics abstraction (RHI) instead of OpenGL directly.\n"  | 
| 356 |          "                                .... Backend has platform specific defaults. Specify to override." ); | 
| 357 |     puts(s: "  --no-version-detection ........... Do not try to detect the version of the .qml file" ); | 
| 358 |     puts(s: "  --slow-animations ................ Run all animations in slow motion" ); | 
| 359 |     puts(s: "  --resize-to-root ................. Resize the window to the size of the root item" ); | 
| 360 |     puts(s: "  --quit ........................... Quit immediately after starting" ); | 
| 361 |     puts(s: "  --disable-context-sharing ........ Disable the use of a shared GL context for QtQuick Windows\n"  | 
| 362 |          "                            ........ (remove AA_ShareOpenGLContexts)" ); | 
| 363 |     puts(s: "  --desktop......................... Force use of desktop GL (AA_UseDesktopOpenGL)" ); | 
| 364 |     puts(s: "  --gles............................ Force use of GLES (AA_UseOpenGLES)" ); | 
| 365 |     puts(s: "  --software........................ Force use of software rendering (AA_UseOpenGLES)" ); | 
| 366 |     puts(s: "  --scaling......................... Enable High DPI scaling (AA_EnableHighDpiScaling)" ); | 
| 367 |     puts(s: "  --no-scaling...................... Disable High DPI scaling (AA_DisableHighDpiScaling)" ); | 
| 368 |     puts(s: "  --verbose......................... Print version and graphical diagnostics for the run-time" ); | 
| 369 | #ifdef QT_WIDGETS_LIB | 
| 370 |     puts(s: "  --apptype [gui|widgets] .......... Select which application class to use. Default is widgets." ); | 
| 371 | #endif | 
| 372 |     puts(s: "  --textrendertype [qt|native]...... Select the default render type for text-like elements." ); | 
| 373 |     puts(s: "  -I <path> ........................ Add <path> to the list of import paths" ); | 
| 374 |     puts(s: "  -S <selector> .................... Add <selector> to the list of QQmlFileSelector selectors" ); | 
| 375 |     puts(s: "  -P <path> ........................ Add <path> to the list of plugin paths" ); | 
| 376 |     puts(s: "  -translation <translationfile> ... Set the language to run in" ); | 
| 377 |  | 
| 378 |     puts(s: " " ); | 
| 379 |     exit(status: 1); | 
| 380 | } | 
| 381 | #if QT_CONFIG(opengl) | 
| 382 | // Listen on GL context creation of the QQuickWindow in order to print diagnostic output. | 
| 383 | class DiagnosticGlContextCreationListener : public QObject { | 
| 384 |     Q_OBJECT | 
| 385 | public: | 
| 386 |     explicit DiagnosticGlContextCreationListener(QQuickWindow *window) : QObject(window) | 
| 387 |     { | 
| 388 |         connect(sender: window, signal: &QQuickWindow::openglContextCreated, | 
| 389 |                 receiver: this, slot: &DiagnosticGlContextCreationListener::onOpenGlContextCreated); | 
| 390 |     } | 
| 391 |  | 
| 392 | private slots: | 
| 393 |     void onOpenGlContextCreated(QOpenGLContext *context) | 
| 394 |     { | 
| 395 |         context->makeCurrent(surface: qobject_cast<QQuickWindow *>(object: parent())); | 
| 396 |         QOpenGLFunctions functions(context); | 
| 397 |         QByteArray output = "Vendor  : " ; | 
| 398 |         output += reinterpret_cast<const char *>(functions.glGetString(GL_VENDOR)); | 
| 399 |         output += "\nRenderer: " ; | 
| 400 |         output += reinterpret_cast<const char *>(functions.glGetString(GL_RENDERER)); | 
| 401 |         output += "\nVersion : " ; | 
| 402 |         output += reinterpret_cast<const char *>(functions.glGetString(GL_VERSION)); | 
| 403 |         output += "\nLanguage: " ; | 
| 404 |         output += reinterpret_cast<const char *>(functions.glGetString(GL_SHADING_LANGUAGE_VERSION)); | 
| 405 |         puts(s: output.constData()); | 
| 406 |         context->doneCurrent(); | 
| 407 |         deleteLater(); | 
| 408 |     } | 
| 409 |  | 
| 410 | }; | 
| 411 | #endif | 
| 412 |  | 
| 413 | static void setWindowTitle(bool verbose, const QObject *topLevel, QWindow *window) | 
| 414 | { | 
| 415 |     const QString oldTitle = window->title(); | 
| 416 |     QString newTitle = oldTitle; | 
| 417 |     if (newTitle.isEmpty()) { | 
| 418 |         newTitle = QLatin1String("qmlscene" ); | 
| 419 |         if (!qobject_cast<const QWindow *>(o: topLevel) && !topLevel->objectName().isEmpty()) | 
| 420 |             newTitle += QLatin1String(": " ) + topLevel->objectName(); | 
| 421 |     } | 
| 422 |     if (verbose) { | 
| 423 |         newTitle += QLatin1String(" [Qt " ) + QLatin1String(QT_VERSION_STR) + QLatin1Char(' ') | 
| 424 |             + QGuiApplication::platformName() + QLatin1Char(' '); | 
| 425 | #if QT_CONFIG(opengl) | 
| 426 |         newTitle += QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL | 
| 427 |             ? QLatin1String("GL" ) : QLatin1String("GLES" ); | 
| 428 | #endif | 
| 429 |         newTitle += QLatin1Char(']'); | 
| 430 |     } | 
| 431 |     if (oldTitle != newTitle) | 
| 432 |         window->setTitle(newTitle); | 
| 433 | } | 
| 434 |  | 
| 435 | static QUrl parseUrlArgument(const QString &arg) | 
| 436 | { | 
| 437 |     const QUrl url = QUrl::fromUserInput(userInput: arg, workingDirectory: QDir::currentPath(), options: QUrl::AssumeLocalFile); | 
| 438 |     if (!url.isValid()) { | 
| 439 |         fprintf(stderr, format: "Invalid URL: \"%s\"\n" , qPrintable(arg)); | 
| 440 |         return QUrl(); | 
| 441 |     } | 
| 442 |     if (url.isLocalFile()) { | 
| 443 |         const QFileInfo fi(url.toLocalFile()); | 
| 444 |         if (!fi.exists()) { | 
| 445 |             fprintf(stderr, format: "\"%s\" does not exist.\n" , | 
| 446 |                     qPrintable(QDir::toNativeSeparators(fi.absoluteFilePath()))); | 
| 447 |             return QUrl(); | 
| 448 |         } | 
| 449 |     } | 
| 450 |     return url; | 
| 451 | } | 
| 452 |  | 
| 453 | static QQuickWindow::TextRenderType parseTextRenderType(const QString &renderType) | 
| 454 | { | 
| 455 |     if (renderType == QLatin1String("qt" )) | 
| 456 |         return QQuickWindow::QtTextRendering; | 
| 457 |     else if (renderType == QLatin1String("native" )) | 
| 458 |         return QQuickWindow::NativeTextRendering; | 
| 459 |  | 
| 460 |     usage(); | 
| 461 |  | 
| 462 |     Q_UNREACHABLE(); | 
| 463 |     return QQuickWindow::QtTextRendering; | 
| 464 | } | 
| 465 |  | 
| 466 | int main(int argc, char ** argv) | 
| 467 | { | 
| 468 |     Options options; | 
| 469 |  | 
| 470 |     QStringList imports; | 
| 471 |     QStringList customSelectors; | 
| 472 |     QStringList pluginPaths; | 
| 473 |  | 
| 474 |     // Parse arguments for application attributes to be applied before Q[Gui]Application creation. | 
| 475 |     for (int i = 1; i < argc; ++i) { | 
| 476 |         const char *arg = argv[i]; | 
| 477 |         if (!qstrcmp(str1: arg, str2: "--disable-context-sharing" )) { | 
| 478 |             options.applicationAttributes.removeAll(t: Qt::AA_ShareOpenGLContexts); | 
| 479 |         } else if (!qstrcmp(str1: arg, str2: "--gles" )) { | 
| 480 |             options.applicationAttributes.append(t: Qt::AA_UseOpenGLES); | 
| 481 |         } else if (!qstrcmp(str1: arg, str2: "--software" )) { | 
| 482 |             options.applicationAttributes.append(t: Qt::AA_UseSoftwareOpenGL); | 
| 483 |         } else if (!qstrcmp(str1: arg, str2: "--desktop" )) { | 
| 484 |             options.applicationAttributes.append(t: Qt::AA_UseDesktopOpenGL); | 
| 485 |         } else if (!qstrcmp(str1: arg, str2: "--scaling" )) { | 
| 486 |             options.applicationAttributes.append(t: Qt::AA_EnableHighDpiScaling); | 
| 487 |         } else if (!qstrcmp(str1: arg, str2: "--no-scaling" )) { | 
| 488 |             options.applicationAttributes.append(t: Qt::AA_DisableHighDpiScaling); | 
| 489 |         } else if (!qstrcmp(str1: arg, str2: "--transparent" )) { | 
| 490 |             options.transparent = true; | 
| 491 |         } else if (!qstrcmp(str1: arg, str2: "--multisample" )) { | 
| 492 |             options.multisample = true; | 
| 493 |         } else if (!qstrcmp(str1: arg, str2: "--core-profile" )) { | 
| 494 |             options.coreProfile = true; | 
| 495 |         } else if (!qstrcmp(str1: arg, str2: "--apptype" )) { | 
| 496 |             if (++i >= argc) | 
| 497 |                 usage(); | 
| 498 |             if (!qstrcmp(str1: argv[i], str2: "gui" )) | 
| 499 |                 options.applicationType = Options::QmlApplicationTypeGui; | 
| 500 |         } | 
| 501 |     } | 
| 502 |  | 
| 503 |     if (qEnvironmentVariableIsSet(varName: "QMLSCENE_CORE_PROFILE" ) | 
| 504 |         || qEnvironmentVariableIsSet(varName: "QSG_CORE_PROFILE" )) | 
| 505 |         options.coreProfile = true; | 
| 506 |  | 
| 507 |     // Set default surface format before creating the window | 
| 508 |     QSurfaceFormat surfaceFormat; | 
| 509 |     surfaceFormat.setStencilBufferSize(8); | 
| 510 |     surfaceFormat.setDepthBufferSize(24); | 
| 511 |     if (options.multisample) | 
| 512 |         surfaceFormat.setSamples(16); | 
| 513 |     if (options.transparent) | 
| 514 |         surfaceFormat.setAlphaBufferSize(8); | 
| 515 |     if (options.coreProfile) { | 
| 516 |         surfaceFormat.setVersion(major: 4, minor: 1); | 
| 517 |         surfaceFormat.setProfile(QSurfaceFormat::CoreProfile); | 
| 518 |     } | 
| 519 |     QSurfaceFormat::setDefaultFormat(surfaceFormat); | 
| 520 |  | 
| 521 |     for (Qt::ApplicationAttribute a : qAsConst(t&: options.applicationAttributes)) | 
| 522 |         QCoreApplication::setAttribute(attribute: a); | 
| 523 |     QScopedPointer<QGuiApplication> app; | 
| 524 | #ifdef QT_WIDGETS_LIB | 
| 525 |     if (options.applicationType == Options::QmlApplicationTypeWidget) | 
| 526 |         app.reset(other: new QApplication(argc, argv)); | 
| 527 | #endif | 
| 528 |     if (app.isNull()) | 
| 529 |         app.reset(other: new QGuiApplication(argc, argv)); | 
| 530 |     QCoreApplication::setApplicationName(QStringLiteral("QtQmlViewer" )); | 
| 531 |     QCoreApplication::setOrganizationName(QStringLiteral("QtProject" )); | 
| 532 |     QCoreApplication::setOrganizationDomain(QStringLiteral("qt-project.org" )); | 
| 533 |     QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); | 
| 534 |  | 
| 535 |     const QStringList arguments = QCoreApplication::arguments(); | 
| 536 |     for (int i = 1, size = arguments.size(); i < size; ++i) { | 
| 537 |         if (!arguments.at(i).startsWith(c: QLatin1Char('-'))) { | 
| 538 |             options.url = parseUrlArgument(arg: arguments.at(i)); | 
| 539 |         } else { | 
| 540 |             const QString lowerArgument = arguments.at(i).toLower(); | 
| 541 |             if (lowerArgument == QLatin1String("--maximized" )) | 
| 542 |                 options.maximized = true; | 
| 543 |             else if (lowerArgument == QLatin1String("--fullscreen" )) | 
| 544 |                 options.fullscreen = true; | 
| 545 |             else if (lowerArgument == QLatin1String("--clip" )) | 
| 546 |                 options.clip = true; | 
| 547 |             else if (lowerArgument == QLatin1String("--no-version-detection" )) | 
| 548 |                 options.versionDetection = false; | 
| 549 |             else if (lowerArgument == QLatin1String("--slow-animations" )) | 
| 550 |                 options.slowAnimations = true; | 
| 551 |             else if (lowerArgument == QLatin1String("--quit" )) | 
| 552 |                 options.quitImmediately = true; | 
| 553 |            else if (lowerArgument == QLatin1String("-translation" )) | 
| 554 |                 options.translationFile = QLatin1String(argv[++i]); | 
| 555 |             else if (lowerArgument == QLatin1String("--resize-to-root" )) | 
| 556 |                 options.resizeViewToRootItem = true; | 
| 557 |             else if (lowerArgument == QLatin1String("--verbose" )) | 
| 558 |                 options.verbose = true; | 
| 559 |             else if (lowerArgument == QLatin1String("--rhi" )) { | 
| 560 |                 options.rhi = true; | 
| 561 |                 if (i + 1 < size && !arguments.at(i: i + 1).startsWith(c: QLatin1Char('-'))) { | 
| 562 |                     options.rhiBackendSet = true; | 
| 563 |                     options.rhiBackend = arguments.at(i: ++i); | 
| 564 |                 } | 
| 565 |             } else if (lowerArgument == QLatin1String("-i" ) && i + 1 < size) | 
| 566 |                 imports.append(t: arguments.at(i: ++i)); | 
| 567 |             else if (lowerArgument == QLatin1String("-s" ) && i + 1 < size) | 
| 568 |                 customSelectors.append(t: arguments.at(i: ++i)); | 
| 569 |             else if (lowerArgument == QLatin1String("-p" ) && i + 1 < size) | 
| 570 |                 pluginPaths.append(t: arguments.at(i: ++i)); | 
| 571 |             else if (lowerArgument == QLatin1String("--apptype" )) | 
| 572 |                 ++i; // Consume previously parsed argument | 
| 573 |             else if (lowerArgument == QLatin1String("--textrendertype" ) && i + 1 < size) | 
| 574 |                 options.textRenderType = parseTextRenderType(renderType: arguments.at(i: ++i)); | 
| 575 |             else if (lowerArgument == QLatin1String("--help" ) | 
| 576 |                      || lowerArgument == QLatin1String("-help" ) | 
| 577 |                      || lowerArgument == QLatin1String("--h" ) | 
| 578 |                      || lowerArgument == QLatin1String("-h" )) | 
| 579 |                 usage(); | 
| 580 |         } | 
| 581 |     } | 
| 582 |  | 
| 583 | #if QT_CONFIG(translation) | 
| 584 |     QLocale locale; | 
| 585 |     QTranslator qtTranslator; | 
| 586 |     if (qtTranslator.load(locale, filename: QLatin1String("qt" ), prefix: QLatin1String("_" ), directory: QLibraryInfo::location(QLibraryInfo::TranslationsPath))) | 
| 587 |         QCoreApplication::installTranslator(messageFile: &qtTranslator); | 
| 588 |     QTranslator translator; | 
| 589 |     if (translator.load(locale, filename: QLatin1String("qmlscene" ), prefix: QLatin1String("_" ), directory: QLibraryInfo::location(QLibraryInfo::TranslationsPath))) | 
| 590 |         QCoreApplication::installTranslator(messageFile: &translator); | 
| 591 |  | 
| 592 |     QTranslator qmlTranslator; | 
| 593 |     if (!options.translationFile.isEmpty()) { | 
| 594 |         if (qmlTranslator.load(filename: options.translationFile)) { | 
| 595 |             QCoreApplication::installTranslator(messageFile: &qmlTranslator); | 
| 596 |         } else { | 
| 597 |             fprintf(stderr, format: "Could not load the translation file \"%s\"\n" , | 
| 598 |                     qPrintable(options.translationFile)); | 
| 599 |         } | 
| 600 |     } | 
| 601 | #endif | 
| 602 |  | 
| 603 |     QQuickWindow::setTextRenderType(options.textRenderType); | 
| 604 |  | 
| 605 |     QUnifiedTimer::instance()->setSlowModeEnabled(options.slowAnimations); | 
| 606 |  | 
| 607 |     if (options.rhi) { | 
| 608 |         qputenv(varName: "QSG_RHI" , value: "1" ); | 
| 609 |         if (options.rhiBackendSet) | 
| 610 |             qputenv(varName: "QSG_RHI_BACKEND" , value: options.rhiBackend.toLatin1()); | 
| 611 |         else | 
| 612 |             qunsetenv(varName: "QSG_RHI_BACKEND" ); | 
| 613 |     } | 
| 614 |  | 
| 615 |     if (options.url.isEmpty()) | 
| 616 | #if defined(QMLSCENE_BUNDLE) | 
| 617 |         displayOptionsDialog(&options); | 
| 618 | #else | 
| 619 |         displayFileDialog(options: &options); | 
| 620 | #endif | 
| 621 |  | 
| 622 |     int exitCode = 0; | 
| 623 |  | 
| 624 |     if (options.verbose) | 
| 625 |         puts(s: QLibraryInfo::build()); | 
| 626 |  | 
| 627 |     if (!options.url.isEmpty()) { | 
| 628 |         if (!options.versionDetection || checkVersion(url: options.url)) { | 
| 629 |             // TODO: as soon as the engine construction completes, the debug service is | 
| 630 |             // listening for connections.  But actually we aren't ready to debug anything. | 
| 631 |             QQmlEngine engine; | 
| 632 |             QQmlFileSelector* selector = new QQmlFileSelector(&engine, &engine); | 
| 633 |             selector->setExtraSelectors(customSelectors); | 
| 634 |             QPointer<QQmlComponent> component = new QQmlComponent(&engine); | 
| 635 |             for (int i = 0; i < imports.size(); ++i) | 
| 636 |                 engine.addImportPath(dir: imports.at(i)); | 
| 637 |             for (int i = 0; i < pluginPaths.size(); ++i) | 
| 638 |                 engine.addPluginPath(dir: pluginPaths.at(i)); | 
| 639 |             if (options.url.isLocalFile()) { | 
| 640 |                 QFileInfo fi(options.url.toLocalFile()); | 
| 641 | #if QT_CONFIG(translation) | 
| 642 |                 QTranslator *translator = new QTranslator(app.get()); | 
| 643 |                 if (translator->load(locale: QLocale(), filename: QLatin1String("qml" ), prefix: QLatin1String("_" ), directory: fi.path() + QLatin1String("/i18n" ))) | 
| 644 |                     QCoreApplication::installTranslator(messageFile: translator); | 
| 645 | #endif | 
| 646 |                 loadDummyDataFiles(engine, directory: fi.path()); | 
| 647 |             } | 
| 648 |             QObject::connect(sender: &engine, SIGNAL(quit()), receiver: QCoreApplication::instance(), SLOT(quit())); | 
| 649 |             QObject::connect(sender: &engine, signal: &QQmlEngine::exit, context: QCoreApplication::instance(), slot: &QCoreApplication::exit); | 
| 650 |             component->loadUrl(url: options.url); | 
| 651 |             while (component->isLoading()) | 
| 652 |                 QCoreApplication::processEvents(); | 
| 653 |             if ( !component->isReady() ) { | 
| 654 |                 fprintf(stderr, format: "%s\n" , qPrintable(component->errorString())); | 
| 655 |                 return -1; | 
| 656 |             } | 
| 657 |  | 
| 658 |             QObject *topLevel = component->create(); | 
| 659 |             if (!topLevel && component->isError()) { | 
| 660 |                 fprintf(stderr, format: "%s\n" , qPrintable(component->errorString())); | 
| 661 |                 return -1; | 
| 662 |             } | 
| 663 |  | 
| 664 |             QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(object: topLevel)); | 
| 665 |             if (window) { | 
| 666 |                 engine.setIncubationController(window->incubationController()); | 
| 667 |             } else { | 
| 668 |                 QQuickItem *contentItem = qobject_cast<QQuickItem *>(object: topLevel); | 
| 669 |                 if (contentItem) { | 
| 670 |                     QQuickView* qxView = new QQuickView(&engine, nullptr); | 
| 671 |                     window.reset(other: qxView); | 
| 672 |                     // Set window default properties; the qml can still override them | 
| 673 |                     if (options.resizeViewToRootItem) | 
| 674 |                         qxView->setResizeMode(QQuickView::SizeViewToRootObject); | 
| 675 |                     else | 
| 676 |                         qxView->setResizeMode(QQuickView::SizeRootObjectToView); | 
| 677 |                     qxView->setContent(url: options.url, component, item: contentItem); | 
| 678 |                 } | 
| 679 |             } | 
| 680 |  | 
| 681 |             if (window) { | 
| 682 |                 setWindowTitle(verbose: options.verbose, topLevel, window: window.data()); | 
| 683 | #if QT_CONFIG(opengl) | 
| 684 |                 if (options.verbose) | 
| 685 |                     new DiagnosticGlContextCreationListener(window.data()); | 
| 686 | #endif | 
| 687 |                 if (options.transparent) { | 
| 688 |                     window->setClearBeforeRendering(true); | 
| 689 |                     window->setColor(QColor(Qt::transparent)); | 
| 690 |                     window->setFlags(Qt::FramelessWindowHint); | 
| 691 |                 } | 
| 692 |                 window->setFormat(surfaceFormat); | 
| 693 |  | 
| 694 |                 if (window->flags() == Qt::Window) // Fix window flags unless set by QML. | 
| 695 |                     window->setFlags(Qt::Window | Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint | Qt::WindowFullscreenButtonHint); | 
| 696 |  | 
| 697 |                 if (options.fullscreen) | 
| 698 |                     window->showFullScreen(); | 
| 699 |                 else if (options.maximized) | 
| 700 |                     window->showMaximized(); | 
| 701 |                 else if (!window->isVisible()) | 
| 702 |                     window->show(); | 
| 703 |             } | 
| 704 |  | 
| 705 |             if (options.quitImmediately) | 
| 706 |                 QMetaObject::invokeMethod(obj: QCoreApplication::instance(), member: "quit" , type: Qt::QueuedConnection); | 
| 707 |  | 
| 708 |             // Now would be a good time to inform the debug service to start listening. | 
| 709 |  | 
| 710 |             exitCode = app->exec(); | 
| 711 |  | 
| 712 | #ifdef QML_RUNTIME_TESTING | 
| 713 |             RenderStatistics::printTotalStats(); | 
| 714 | #endif | 
| 715 |             // Ready to exit. Notice that the component might be owned by | 
| 716 |             // QQuickView if one was created. That case is tracked by | 
| 717 |             // QPointer, so it is safe to delete the component here. | 
| 718 |             delete component; | 
| 719 |         } else { | 
| 720 |             exitCode = 1; | 
| 721 |         } | 
| 722 |     } | 
| 723 |  | 
| 724 |     return exitCode; | 
| 725 | } | 
| 726 |  | 
| 727 | #include "main.moc" | 
| 728 |  |