From 03958f2fa07d0ab8346790630b51c30d257a4f5a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 1 Apr 2014 12:02:47 -0700 Subject: [PATCH] work on metadata in the model browser + real time updates --- interface/src/Menu.cpp | 23 +- interface/src/ui/ModelBrowser.cpp | 150 ------------ interface/src/ui/ModelBrowser.h | 62 ----- interface/src/ui/ModelsBrowser.cpp | 305 ++++++++++++++++++++++++ interface/src/ui/ModelsBrowser.h | 79 ++++++ libraries/shared/src/FileDownloader.cpp | 54 ++--- libraries/shared/src/FileDownloader.h | 13 +- 7 files changed, 421 insertions(+), 265 deletions(-) delete mode 100644 interface/src/ui/ModelBrowser.cpp delete mode 100644 interface/src/ui/ModelBrowser.h create mode 100644 interface/src/ui/ModelsBrowser.cpp create mode 100644 interface/src/ui/ModelsBrowser.h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 79b0a23ce5..3e097008c9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include "Application.h" #include "Menu.h" @@ -37,7 +36,7 @@ #include "Util.h" #include "ui/InfoView.h" #include "ui/MetavoxelEditor.h" -#include "ui/ModelBrowser.h" +#include "ui/ModelsBrowser.h" Menu* Menu::_instance = NULL; @@ -714,8 +713,8 @@ void Menu::loginForCurrentDomain() { void Menu::editPreferences() { Application* applicationInstance = Application::getInstance(); - ModelBrowser headBrowser(Head); - ModelBrowser skeletonBrowser(Skeleton); + ModelsBrowser headBrowser(Head); + ModelsBrowser skeletonBrowser(Skeleton); const QString BROWSE_BUTTON_TEXT = "Browse"; @@ -816,15 +815,23 @@ void Menu::editPreferences() { if (ret == QDialog::Accepted) { bool shouldDispatchIdentityPacket = false; - if (headURLEdit.text() != faceURLString && !headURLEdit.text().isEmpty()) { + if (headURLEdit.text() != faceURLString) { // change the faceModelURL in the profile, it will also update this user's BlendFace - applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.text())); + if (headURLEdit.text().isEmpty()) { + applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.placeholderText())); + } else { + applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.text())); + } shouldDispatchIdentityPacket = true; } - if (skeletonURLEdit.text() != skeletonURLString && !skeletonURLEdit.text().isEmpty()) { + if (skeletonURLEdit.text() != skeletonURLString) { // change the skeletonModelURL in the profile, it will also update this user's Body - applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.text())); + if (skeletonURLEdit.text().isEmpty()) { + applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.placeholderText())); + } else { + applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.text())); + } shouldDispatchIdentityPacket = true; } diff --git a/interface/src/ui/ModelBrowser.cpp b/interface/src/ui/ModelBrowser.cpp deleted file mode 100644 index e03081273f..0000000000 --- a/interface/src/ui/ModelBrowser.cpp +++ /dev/null @@ -1,150 +0,0 @@ -// -// ModelBrowser.cpp -// hifi -// -// Created by Clement on 3/17/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. -// - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ModelBrowser.h" - -static const QString PREFIX_PARAMETER_NAME = "prefix"; -static const QString MARKER_PARAMETER_NAME = "marker"; -static const QString IS_TRUNCATED_NAME = "IsTruncated"; -static const QString CONTAINER_NAME = "Contents"; -static const QString KEY_NAME = "Key"; - -ModelBrowser::ModelBrowser(ModelType modelType, QWidget* parent) : QWidget(parent), _type(modelType) { - QUrl url(S3_URL); - QUrlQuery query; - - if (_type == Head) { - query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION); - } else if (_type == Skeleton) { - query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION); - } - url.setQuery(query); - - _downloader = new FileDownloader(url); - connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished())); -} - -ModelBrowser::~ModelBrowser() { - delete _downloader; -} - -void ModelBrowser::downloadFinished() { - parseXML(_downloader->getData()); -} - -void ModelBrowser::browse() { - QDialog dialog(this); - dialog.setWindowTitle("Browse models"); - - QGridLayout* layout = new QGridLayout(&dialog); - dialog.setLayout(layout); - - QLineEdit* searchBar = new QLineEdit(&dialog); - layout->addWidget(searchBar, 0, 0); - - ListView* listView = new ListView(&dialog); - listView->setEditTriggers(QAbstractItemView::NoEditTriggers); - layout->addWidget(listView, 1, 0); - listView->connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(keyboardSearch(const QString&))); - dialog.connect(listView, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept())); - - QStringListModel* model = new QStringListModel(_models.keys(), listView); - model->sort(0); - listView->setModel(model); - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - layout->addWidget(buttons, 2, 0); - dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); - dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); - - if (dialog.exec() == QDialog::Rejected) { - return; - } - - QString selectedKey = model->data(listView->currentIndex(), Qt::DisplayRole).toString(); - - emit selected(_models[selectedKey]); -} - -bool ModelBrowser::parseXML(QByteArray xmlFile) { - QXmlStreamReader xml(xmlFile); - QRegExp rx(".*fst"); - bool truncated = false; - QString lastKey; - - // Read xml until the end or an error is detected - while(!xml.atEnd() && !xml.hasError()) { - if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) { - while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) { - // Let's check if there is more - xml.readNext(); - if (xml.text().toString() == "True") { - truncated = true; - } - } - } - - if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) { - while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) { - // If a file is find, process it - if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) { - xml.readNext(); - lastKey = xml.text().toString(); - if (rx.exactMatch(xml.text().toString())) { - // Add the found file to the list - _models.insert(QFileInfo(xml.text().toString()).baseName(), - S3_URL + "/" + xml.text().toString()); - } - } - xml.readNext(); - } - } - xml.readNext(); - } - - // Error handling - if(xml.hasError()) { - _models.clear(); - QMessageBox::critical(this, - "ModelBrowser::ModelBrowser()", - xml.errorString(), - QMessageBox::Ok); - return false; - } - - // If we didn't all the files, download the next ones - if (truncated) { - QUrl url(S3_URL); - QUrlQuery query; - - if (_type == Head) { - query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION); - } else if (_type == Skeleton) { - query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION); - } - query.addQueryItem(MARKER_PARAMETER_NAME, lastKey); - url.setQuery(query); - - delete _downloader; - _downloader = new FileDownloader(url); - connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished())); - } - - return true; -} \ No newline at end of file diff --git a/interface/src/ui/ModelBrowser.h b/interface/src/ui/ModelBrowser.h deleted file mode 100644 index 4628642e89..0000000000 --- a/interface/src/ui/ModelBrowser.h +++ /dev/null @@ -1,62 +0,0 @@ -// -// ModelBrowser.h -// hifi -// -// Created by Clement on 3/17/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. -// - -#ifndef __hifi__ModelBrowser__ -#define __hifi__ModelBrowser__ - -#include - -#include -#include - -static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com"; -static const QString HEAD_MODELS_LOCATION = "models/heads/"; -static const QString SKELETON_MODELS_LOCATION = "models/skeletons/"; - -enum ModelType { - Head, - Skeleton -}; - -class ModelBrowser : public QWidget { - Q_OBJECT - -public: - ModelBrowser(ModelType modelType, QWidget* parent = NULL); - ~ModelBrowser(); - -signals: - void selected(QString filename); - -public slots: - void browse(); - -private slots: - void downloadFinished(); - -private: - ModelType _type; - FileDownloader* _downloader; - QHash _models; - - bool parseXML(QByteArray xmlFile); -}; - - - -class ListView : public QListView { - Q_OBJECT -public: - ListView(QWidget* parent) : QListView(parent) {} - public slots: - void keyboardSearch(const QString& text) { - QAbstractItemView::keyboardSearch(text); - } -}; - -#endif /* defined(__hifi__ModelBrowser__) */ diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp new file mode 100644 index 0000000000..3bdc4ca9ed --- /dev/null +++ b/interface/src/ui/ModelsBrowser.cpp @@ -0,0 +1,305 @@ +// +// ModelsBrowser.cpp +// hifi +// +// Created by Clement on 3/17/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ModelsBrowser.h" + +static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com"; +static const QString PUBLIC_URL = "http://public.highfidelity.io"; +static const QString HEAD_MODELS_LOCATION = "models/heads"; +static const QString SKELETON_MODELS_LOCATION = "models/skeletons/"; + +static const QString PREFIX_PARAMETER_NAME = "prefix"; +static const QString MARKER_PARAMETER_NAME = "marker"; +static const QString IS_TRUNCATED_NAME = "IsTruncated"; +static const QString CONTAINER_NAME = "Contents"; +static const QString KEY_NAME = "Key"; +static const QString LOADING_MSG = "Loading..."; + +enum ModelMetaData { + Name = 0, + Creator, + UploadeDate, + Type, + Gender, + + ModelMetaDataCount +}; +static const QString propertiesNames[ModelMetaDataCount] = { + "Name", + "Creator", + "Upload Date", + "Type", + "Gender" +}; + +ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) : + QWidget(parent), + _handler(new ModelHandler(modelsType)) +{ + // Connect handler + _handler->connect(this, SIGNAL(startDownloading()), SLOT(download())); + _handler->connect(this, SIGNAL(startUpdating()), SLOT(update())); + _handler->connect(this, SIGNAL(destroyed()), SLOT(exit())); + + // Setup and launch update thread + QThread* thread = new QThread(); + thread->connect(_handler, SIGNAL(destroyed()), SLOT(quit())); + thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater())); + _handler->moveToThread(thread); + thread->start(); + emit startDownloading(); + + // Initialize the view + _view.setEditTriggers(QAbstractItemView::NoEditTriggers); + _view.setRootIsDecorated(false); + _view.setModel(_handler->getModel()); +} + +ModelsBrowser::~ModelsBrowser() { +} + +void ModelsBrowser::applyFilter(const QString &filter) { + QStringList filters = filter.split(" "); + + // Try and match every filter with each rows + for (int i = 0; i < _handler->getModel()->rowCount(); ++i) { + bool match = false; + for (int k = 0; k < filters.count(); ++k) { + match = false; + for (int j = 0; j < ModelMetaDataCount; ++j) { + if (_handler->getModel()->item(i, j)->text().contains(filters.at(k))) { + match = true; + break; + } + } + if (!match) { + break; + } + } + + // Hid the row if it doesn't match (Make sure it's not it it does) + if (match) { + _view.setRowHidden(i, QModelIndex(), false); + } else { + _view.setRowHidden(i, QModelIndex(), true); + } + } +} + +void ModelsBrowser::browse() { + QDialog dialog; + dialog.setWindowTitle("Browse models"); + + QGridLayout* layout = new QGridLayout(&dialog); + dialog.setLayout(layout); + + QLineEdit* searchBar = new QLineEdit(&dialog); + layout->addWidget(searchBar, 0, 0); + + layout->addWidget(&_view, 1, 0); + dialog.connect(&_view, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept())); + connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(applyFilter(const QString&))); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + layout->addWidget(buttons, 2, 0); + dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); + dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); + + if (dialog.exec() == QDialog::Accepted) { + QVariant selectedFile = _handler->getModel()->data(_view.currentIndex(), Qt::UserRole); + if (selectedFile.isValid()) { + emit selected(selectedFile.toString()); + } + } + + // So that we don't have to reconstruct the view + _view.setParent(NULL); +} + + +ModelHandler::ModelHandler(ModelType modelsType, QWidget* parent) : + QObject(parent), + _initiateExit(false), + _type(modelsType) +{ + connect(&_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished())); + + // set headers data + QStringList headerData; + for (int i = 0; i < ModelMetaDataCount; ++i) { + headerData << propertiesNames[i]; + } + _model.setHorizontalHeaderLabels(headerData); +} + +void ModelHandler::download() { + // Query models list + queryNewFiles(); + + QMutexLocker lockerModel(&_modelMutex); + if (_initiateExit) { + return; + } + // Show loading message + QStandardItem* loadingItem = new QStandardItem(LOADING_MSG); + loadingItem->setEnabled(false); + _model.appendRow(loadingItem); +} + +void ModelHandler::update() { + // Will be implemented in my next PR +} + +void ModelHandler::exit() { + QMutexLocker lockerDownload(&_downloadMutex); + QMutexLocker lockerModel(&_modelMutex); + _initiateExit = true; + + // Disconnect everything + _downloader.disconnect(); + disconnect(); + thread()->disconnect(); + + // Make sure the thread will exit correctly + thread()->connect(this, SIGNAL(destroyed()), SLOT(quit())); + thread()->connect(thread(), SIGNAL(finished()), SLOT(deleteLater())); + deleteLater(); +} + +void ModelHandler::downloadFinished() { + QMutexLocker lockerDownload(&_downloadMutex); + if (_initiateExit) { + return; + } + parseXML(_downloader.getData()); +} + +void ModelHandler::queryNewFiles(QString marker) { + QMutexLocker lockerDownload(&_downloadMutex); + if (_initiateExit) { + return; + } + + // Build query + QUrl url(S3_URL); + QUrlQuery query; + if (_type == Head) { + query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION); + } else if (_type == Skeleton) { + query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION); + } + + if (!marker.isEmpty()) { + query.addQueryItem(MARKER_PARAMETER_NAME, marker); + } + + // Download + url.setQuery(query); + _downloader.download(url); +} + +bool ModelHandler::parseXML(QByteArray xmlFile) { + QMutexLocker lockerModel(&_modelMutex); + if (_initiateExit) { + return false; + } + + QXmlStreamReader xml(xmlFile); + QRegExp rx(".*fst"); + bool truncated = false; + QString lastKey; + + // Remove loading indication + int oldLastRow = _model.rowCount() - 1; + delete _model.takeRow(oldLastRow).first(); + + // Read xml until the end or an error is detected + while(!xml.atEnd() && !xml.hasError()) { + if (_initiateExit) { + return false; + } + + if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) { + while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) { + // Let's check if there is more + xml.readNext(); + if (xml.text().toString() == "True") { + truncated = true; + } + } + } + + if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) { + while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) { + // If a file is find, process it + if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) { + xml.readNext(); + lastKey = xml.text().toString(); + if (rx.exactMatch(xml.text().toString())) { + // Add the found file to the list + QList model; + model << new QStandardItem(QFileInfo(xml.text().toString()).baseName()); + model.first()->setData(PUBLIC_URL + "/" + xml.text().toString(), Qt::UserRole); + + // Rand properties for now (Will be taken out in the next PR) + static QString creator[] = {"Ryan", "Philip", "Andzrej"}; + static QString type[] = {"human", "beast", "pet", "elfe"}; + static QString gender[] = {"male", "female", "none"}; + model << new QStandardItem(creator[randIntInRange(0, 2)]); + model << new QStandardItem(QDate(randIntInRange(2013, 2014), + randIntInRange(1, 12), + randIntInRange(1, 30)).toString()); + model << new QStandardItem(type[randIntInRange(0, 3)]); + model << new QStandardItem(gender[randIntInRange(0, 2)]); + //////////////////////////////////////////////////////////// + + _model.appendRow(model); + } + } + xml.readNext(); + } + } + xml.readNext(); + } + + static const QString ERROR_MSG = "Error loading files"; + // Error handling + if(xml.hasError()) { + _model.clear(); + QStandardItem* errorItem = new QStandardItem(ERROR_MSG); + errorItem->setEnabled(false); + _model.appendRow(errorItem); + + return false; + } + + // If we didn't all the files, download the next ones + if (truncated) { + // Indicate more files are being loaded + QStandardItem* loadingItem = new QStandardItem(LOADING_MSG); + loadingItem->setEnabled(false); + _model.appendRow(loadingItem); + + // query those files + queryNewFiles(lastKey); + } + + return true; +} \ No newline at end of file diff --git a/interface/src/ui/ModelsBrowser.h b/interface/src/ui/ModelsBrowser.h new file mode 100644 index 0000000000..7343c0c14f --- /dev/null +++ b/interface/src/ui/ModelsBrowser.h @@ -0,0 +1,79 @@ +// +// ModelsBrowser.h +// hifi +// +// Created by Clement on 3/17/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__ModelsBrowser__ +#define __hifi__ModelsBrowser__ + +#include +#include +#include +#include + +#include + +typedef +enum { + Head, + Skeleton +} ModelType; + +class ModelHandler : public QObject { + Q_OBJECT +public: + ModelHandler(ModelType modelsType, QWidget* parent = NULL); + QStandardItemModel* getModel() { QMutexLocker locker(&_modelMutex); return &_model; } + +signals: + void doneDownloading(); + void doneUpdating(); + +public slots: + void download(); + void update(); + void exit(); + +private slots: + void downloadFinished(); + +private: + bool _initiateExit; + ModelType _type; + QMutex _downloadMutex; + FileDownloader _downloader; + QMutex _modelMutex; + QStandardItemModel _model; + + void queryNewFiles(QString marker = QString()); + bool parseXML(QByteArray xmlFile); +}; + + +class ModelsBrowser : public QWidget { + Q_OBJECT +public: + + ModelsBrowser(ModelType modelsType, QWidget* parent = NULL); + ~ModelsBrowser(); + +signals: + void startDownloading(); + void startUpdating(); + void selected(QString filename); + +public slots: + void browse(); + +private slots: + void applyFilter(const QString& filter); + +private: + ModelHandler* _handler; + QTreeView _view; +}; + +#endif /* defined(__hifi__ModelBrowser__) */ diff --git a/libraries/shared/src/FileDownloader.cpp b/libraries/shared/src/FileDownloader.cpp index 2b65bbd8df..69cf599fae 100644 --- a/libraries/shared/src/FileDownloader.cpp +++ b/libraries/shared/src/FileDownloader.cpp @@ -14,14 +14,25 @@ #include "FileDownloader.h" -FileDownloader::FileDownloader(const QUrl dataURL, QObject* parent) : - QObject(parent), - _done(false) -{ - connect(&_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*))); +FileDownloader::FileDownloader(QObject* parent) : QObject(parent) { + connect(&_networkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(processReply(QNetworkReply*))); +} +void FileDownloader::download(const QUrl dataURL, QNetworkAccessManager::Operation operation) { QNetworkRequest request(dataURL); - _networkAccessManager.get(request); + + _downloadedData.clear(); + switch (operation) { + case QNetworkAccessManager::GetOperation: + _networkAccessManager.get(request); + break; + case QNetworkAccessManager::HeadOperation: + _networkAccessManager.head(request); + break; + default: + emit done(QNetworkReply::ProtocolInvalidOperationError); + break; + } } void FileDownloader::processReply(QNetworkReply *reply) { @@ -30,36 +41,5 @@ void FileDownloader::processReply(QNetworkReply *reply) { } reply->deleteLater(); - _done = true; emit done(reply->error()); -} - -void FileDownloader::waitForFile(int timeout) { - QTimer timer; - QEventLoop loop; - connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); - connect(this, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit())); - - if (!_done) { - if (timeout > 0) { - timer.start(timeout); - } - loop.exec(); - } -} - -QByteArray FileDownloader::download(const QUrl dataURL, int timeout) { - QTimer timer; - QEventLoop loop; - connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit)); - - FileDownloader downloader(dataURL); - connect(&downloader, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit())); - - if (timeout > 0) { - timer.start(timeout); - } - loop.exec(); - - return downloader.getData(); } \ No newline at end of file diff --git a/libraries/shared/src/FileDownloader.h b/libraries/shared/src/FileDownloader.h index 593b39b013..7c5a416600 100644 --- a/libraries/shared/src/FileDownloader.h +++ b/libraries/shared/src/FileDownloader.h @@ -13,31 +13,28 @@ #include #include #include +#include class FileDownloader : public QObject { Q_OBJECT public: - FileDownloader(const QUrl dataURL, QObject* parent = NULL); - - void waitForFile(int timeout = 0); - + FileDownloader(QObject* parent = NULL); QByteArray getData() const { return _downloadedData; } - bool done() { return _done; } - static QByteArray download(const QUrl dataURL, int timeout = 0); signals: void done(QNetworkReply::NetworkError error); +public slots: + void download(const QUrl dataURL, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation); + private slots: void processReply(QNetworkReply* reply); private: QNetworkAccessManager _networkAccessManager; QByteArray _downloadedData; - - bool _done; };