3881 lines · 155.5 KB · C++
RawDownload
////  Application.cpp//  interface/src////  Created by Andrzej Kapolka on 5/10/13.//  Copyright 2013 High Fidelity, Inc.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include <sstream> #include <stdlib.h>#include <cmath>#include <math.h> #include <glm/glm.hpp>#include <glm/gtx/component_wise.hpp>#include <glm/gtx/quaternion.hpp>#include <glm/gtx/vector_angle.hpp> // include this before QGLWidget, which includes an earlier version of OpenGL#include "InterfaceConfig.h" #include <QActionGroup>#include <QColorDialog>#include <QDesktopWidget>#include <QCheckBox>#include <QImage>#include <QInputDialog>#include <QKeyEvent>#include <QMenuBar>#include <QMouseEvent>#include <QNetworkAccessManager>#include <QNetworkReply>#include <QNetworkDiskCache>#include <QOpenGLFramebufferObject>#include <QObject>#include <QWheelEvent>#include <QSettings>#include <QShortcut>#include <QTimer>#include <QUrl>#include <QtDebug>#include <QFileDialog>#include <QDesktopServices>#include <QXmlStreamReader>#include <QXmlStreamAttributes>#include <QMediaPlayer>#include <QMimeData>#include <QMessageBox> #include <AccountManager.h>#include <AudioInjector.h>#include <Logging.h>#include <ModelsScriptingInterface.h>#include <OctalCode.h>#include <PacketHeaders.h>#include <ParticlesScriptingInterface.h>#include <PerfStat.h>#include <ResourceCache.h>#include <UUID.h>#include <OctreeSceneStats.h>#include <LocalVoxelsList.h> #include "Application.h"#include "InterfaceVersion.h"#include "Menu.h"#include "ModelUploader.h"#include "Util.h"#include "devices/OculusManager.h"#include "devices/TV3DManager.h"#include "renderer/ProgramObject.h" #include "scripting/AccountScriptingInterface.h"#include "scripting/AudioDeviceScriptingInterface.h"#include "scripting/ClipboardScriptingInterface.h"#include "scripting/MenuScriptingInterface.h"#include "scripting/SettingsScriptingInterface.h"#include "scripting/WindowScriptingInterface.h"#include "scripting/LocationScriptingInterface.h" #include "ui/InfoView.h"#include "ui/OAuthWebViewHandler.h"#include "ui/Snapshot.h"#include "ui/Stats.h"#include "ui/TextRenderer.h" using namespace std; //  Starfield informationstatic unsigned STARFIELD_NUM_STARS = 50000;static unsigned STARFIELD_SEED = 1; static const int BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH = 6; // farther dragged clicks are ignored const int IDLE_SIMULATE_MSECS = 16;              //  How often should call simulate and other stuff                                                 //  in the idle loop?  (60 FPS is default)static QTimer* idleTimer = NULL; const int STARTUP_JITTER_SAMPLES = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL / 2;                                                 //  Startup optimistically with small jitter buffer that                                                 //  will start playback on the second received audio packet. const QString CHECK_VERSION_URL = "https://highfidelity.io/latestVersion.xml";const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion"; const QString DEFAULT_SCRIPTS_JS_URL = "http://public.highfidelity.io/scripts/defaultScripts.js"; void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {    if (message.size() > 0) {        QString dateString = QDateTime::currentDateTime().toTimeSpec(Qt::LocalTime).toString(Qt::ISODate);        QString formattedMessage = QString("[%1] %2\n").arg(dateString).arg(message);         fprintf(stdout, "%s", qPrintable(formattedMessage));        Application::getInstance()->getLogger()->addMessage(qPrintable(formattedMessage));    }} QString& Application::resourcesPath() {#ifdef Q_OS_MAC    static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/";#else    static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/";#endif    return staticResourcePath;} Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :        QApplication(argc, argv),        _window(new MainWindow(desktop())),        _glWidget(new GLCanvas()),        _nodeThread(new QThread(this)),        _datagramProcessor(),        _frameCount(0),        _fps(120.0f),        _justStarted(true),        _voxelImporter(NULL),        _importSucceded(false),        _sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),        _wantToKillLocalVoxels(false),        _viewFrustum(),        _lastQueriedViewFrustum(),        _lastQueriedTime(usecTimestampNow()),        _mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)),        _cameraPushback(0.0f),        _scaleMirror(1.0f),        _rotateMirror(0.0f),        _raiseMirror(0.0f),        _mouseX(0),        _mouseY(0),        _lastMouseMove(usecTimestampNow()),        _lastMouseMoveType(QEvent::MouseMove),        _mouseHidden(false),        _seenMouseMove(false),        _touchAvgX(0.0f),        _touchAvgY(0.0f),        _isTouchPressed(false),        _mousePressed(false),        _audio(STARTUP_JITTER_SAMPLES),        _enableProcessVoxelsThread(true),        _octreeProcessor(),        _voxelHideShowThread(&_voxels),        _packetsPerSecond(0),        _bytesPerSecond(0),        _nodeBoundsDisplay(this),        _previousScriptLocation(),        _applicationOverlay(),        _runningScriptsWidget(new RunningScriptsWidget(_window)),        _runningScriptsWidgetWasVisible(false),        _trayIcon(new QSystemTrayIcon(_window)),        _lastNackTime(usecTimestampNow()){    // read the ApplicationInfo.ini file for Name/Version/Domain information    QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat);     // set the associated application properties    applicationInfo.beginGroup("INFO");     qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion());     setApplicationName(applicationInfo.value("name").toString());    setApplicationVersion(BUILD_VERSION);    setOrganizationName(applicationInfo.value("organizationName").toString());    setOrganizationDomain(applicationInfo.value("organizationDomain").toString());     _logger = new FileLogger(this);  // After setting organization name in order to get correct directory     QSettings::setDefaultFormat(QSettings::IniFormat);     _myAvatar = _avatarManager.getMyAvatar();     _applicationStartupTime = startup_time;     QFontDatabase::addApplicationFont(Application::resourcesPath() + "styles/Inconsolata.otf");    _window->setWindowTitle("Interface");     qInstallMessageHandler(messageHandler);     // call Menu getInstance static method to set up the menu    _window->setMenuBar(Menu::getInstance());     unsigned int listenPort = 0; // bind to an ephemeral port by default    const char** constArgv = const_cast<const char**>(argv);    const char* portStr = getCmdOption(argc, constArgv, "--listenPort");    if (portStr) {        listenPort = atoi(portStr);    }     // start the nodeThread so its event loop is running    _nodeThread->start();     // make sure the node thread is given highest priority    _nodeThread->setPriority(QThread::TimeCriticalPriority);     // put the NodeList and datagram processing on the node thread    NodeList* nodeList = NodeList::createInstance(NodeType::Agent, listenPort);     nodeList->moveToThread(_nodeThread);    _datagramProcessor.moveToThread(_nodeThread);     // connect the DataProcessor processDatagrams slot to the QUDPSocket readyRead() signal    connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), &_datagramProcessor, SLOT(processDatagrams()));     // put the audio processing on a separate thread    QThread* audioThread = new QThread(this);     _audio.moveToThread(audioThread);    connect(audioThread, SIGNAL(started()), &_audio, SLOT(start()));     audioThread->start();     connect(&nodeList->getDomainHandler(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));    connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&)));     // update our location every 5 seconds in the data-server, assuming that we are authenticated with one    const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;     QTimer* locationUpdateTimer = new QTimer(this);    connect(locationUpdateTimer, &QTimer::timeout, this, &Application::updateLocationInServer);    locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);     connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded);    connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled);    connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));    connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), &_voxels, SLOT(nodeAdded(SharedNodePointer)));    connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer)));    connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle);    connect(nodeList, SIGNAL(uuidChanged(const QUuid&)), _myAvatar, SLOT(setSessionUUID(const QUuid&)));    connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset);     // connect to appropriate slots on AccountManager    AccountManager& accountManager = AccountManager::getInstance();     const qint64 BALANCE_UPDATE_INTERVAL_MSECS = 5 * 1000;     QTimer* balanceUpdateTimer = new QTimer(this);    connect(balanceUpdateTimer, &QTimer::timeout, &accountManager, &AccountManager::updateBalance);    balanceUpdateTimer->start(BALANCE_UPDATE_INTERVAL_MSECS);     connect(&accountManager, &AccountManager::balanceChanged, this, &Application::updateWindowTitle);     connect(&accountManager, &AccountManager::authRequired, Menu::getInstance(), &Menu::loginForCurrentDomain);    connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle);     // set the account manager's root URL and trigger a login request if we don't have the access token    accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL);     // once the event loop has started, check and signal for an access token    QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);     _settings = new QSettings(this);    _numChangedSettings = 0;     // Check to see if the user passed in a command line option for loading a local    // Voxel File.    _voxelsFilename = getCmdOption(argc, constArgv, "-i");     #ifdef _WIN32    WSADATA WsaData;    int wsaresult = WSAStartup(MAKEWORD(2,2), &WsaData);    #endif     // tell the NodeList instance who to tell the domain server we care about    nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer                                                 << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer                                                 << NodeType::MetavoxelServer);     // connect to the packet sent signal of the _voxelEditSender and the _particleEditSender    connect(&_voxelEditSender, &VoxelEditPacketSender::packetSent, this, &Application::packetSent);    connect(&_particleEditSender, &ParticleEditPacketSender::packetSent, this, &Application::packetSent);    connect(&_modelEditSender, &ModelEditPacketSender::packetSent, this, &Application::packetSent);     // move the silentNodeTimer to the _nodeThread    QTimer* silentNodeTimer = new QTimer();    connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));    silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS);    silentNodeTimer->moveToThread(_nodeThread);     // send the identity packet for our avatar each second to our avatar mixer    QTimer* identityPacketTimer = new QTimer();    connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket);    identityPacketTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);     // send the billboard packet for our avatar every few seconds    QTimer* billboardPacketTimer = new QTimer();    connect(billboardPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendBillboardPacket);    billboardPacketTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS);     QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);     _networkAccessManager = new QNetworkAccessManager(this);    QNetworkDiskCache* cache = new QNetworkDiskCache(_networkAccessManager);    cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache");    _networkAccessManager->setCache(cache);     ResourceCache::setNetworkAccessManager(_networkAccessManager);    ResourceCache::setRequestLimit(3);     _window->setCentralWidget(_glWidget);     restoreSizeAndPosition();     _window->setVisible(true);    _glWidget->setFocusPolicy(Qt::StrongFocus);    _glWidget->setFocus();     // enable mouse tracking; otherwise, we only get drag events    _glWidget->setMouseTracking(true);     // initialization continues in initializeGL when OpenGL context is ready     // Tell our voxel edit sender about our known jurisdictions    _voxelEditSender.setVoxelServerJurisdictions(&_voxelServerJurisdictions);    _particleEditSender.setServerJurisdictions(&_particleServerJurisdictions);    _modelEditSender.setServerJurisdictions(&_modelServerJurisdictions);     Particle::setVoxelEditPacketSender(&_voxelEditSender);    Particle::setParticleEditPacketSender(&_particleEditSender);     // when -url in command line, teleport to location    urlGoTo(argc, constArgv);     // For now we're going to set the PPS for outbound packets to be super high, this is    // probably not the right long term solution. But for now, we're going to do this to    // allow you to move a particle around in your hand    _particleEditSender.setPacketsPerSecond(3000); // super high!!    _modelEditSender.setPacketsPerSecond(3000); // super high!!     // Set the sixense filtering    _sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense));     checkVersion();     _overlays.init(_glWidget); // do this before scripts load     LocalVoxelsList::getInstance()->addPersistantTree(DOMAIN_TREE_NAME, _voxels.getTree());    LocalVoxelsList::getInstance()->addPersistantTree(CLIPBOARD_TREE_NAME, &_clipboard);     _runningScriptsWidget->setRunningScripts(getRunningScripts());    connect(_runningScriptsWidget, &RunningScriptsWidget::stopScriptName, this, &Application::stopScript);     // check first run...    QVariant firstRunValue = _settings->value("firstRun",QVariant(true));    if (firstRunValue.isValid() && firstRunValue.toBool()) {        qDebug() << "This is a first run...";        // clear the scripts, and set out script to our default scripts        clearScriptsBeforeRunning();        loadScript(DEFAULT_SCRIPTS_JS_URL);         QMutexLocker locker(&_settingsMutex);        _settings->setValue("firstRun",QVariant(false));    } else {        // do this as late as possible so that all required subsystems are inialized        loadScripts();         QMutexLocker locker(&_settingsMutex);        _previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString();    }     connect(_window, &MainWindow::windowGeometryChanged,            _runningScriptsWidget, &RunningScriptsWidget::setBoundary);     //When -url in command line, teleport to location    urlGoTo(argc, constArgv);     // call the OAuthWebviewHandler static getter so that its instance lives in our thread    OAuthWebViewHandler::getInstance();    // make sure the High Fidelity root CA is in our list of trusted certs    OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig();     _trayIcon->show();} Application::~Application() {     qInstallMessageHandler(NULL);     // make sure we don't call the idle timer any more    delete idleTimer;     _sharedVoxelSystem.changeTree(new VoxelTree);     saveSettings();     delete _voxelImporter;     // let the avatar mixer know we're out    MyAvatar::sendKillAvatar();     // ask the datagram processing thread to quit and wait until it is done    _nodeThread->quit();    _nodeThread->wait();     // stop the audio process    QMetaObject::invokeMethod(&_audio, "stop");     // ask the audio thread to quit and wait until it is done    _audio.thread()->quit();    _audio.thread()->wait();     _octreeProcessor.terminate();    _voxelHideShowThread.terminate();    _voxelEditSender.terminate();    _particleEditSender.terminate();    _modelEditSender.terminate();     storeSizeAndPosition();    saveScripts();     VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown    Menu::getInstance()->deleteLater();     _myAvatar = NULL;     delete _glWidget;     AccountManager::getInstance().destroy();} void Application::saveSettings() {    Menu::getInstance()->saveSettings();    _rearMirrorTools->saveSettings(_settings);     if (_voxelImporter) {        _voxelImporter->saveSettings(_settings);    }    _settings->sync();    _numChangedSettings = 0;}  void Application::restoreSizeAndPosition() {    QRect available = desktop()->availableGeometry();     QMutexLocker locker(&_settingsMutex);    _settings->beginGroup("Window");     int x = (int)loadSetting(_settings, "x", 0);    int y = (int)loadSetting(_settings, "y", 0);    _window->move(x, y);     int width = (int)loadSetting(_settings, "width", available.width());    int height = (int)loadSetting(_settings, "height", available.height());    _window->resize(width, height);     _settings->endGroup();} void Application::storeSizeAndPosition() {    QMutexLocker locker(&_settingsMutex);    _settings->beginGroup("Window");     _settings->setValue("width", _window->rect().width());    _settings->setValue("height", _window->rect().height());     _settings->setValue("x", _window->pos().x());    _settings->setValue("y", _window->pos().y());     _settings->endGroup();} void Application::initializeGL() {    qDebug( "Created Display Window.");     // initialize glut for shape drawing; Qt apparently initializes it on OS X    #ifndef __APPLE__    static bool isInitialized = false;    if (isInitialized) {        return;    } else {        isInitialized = true;    }    int argc = 0;    glutInit(&argc, 0);    #endif     #ifdef WIN32    GLenum err = glewInit();

Lines 1–500 of 3881.