From 17fe0460a9a59930b2f5ff7532cb0db4f937adac Mon Sep 17 00:00:00 2001 From: James Turner Date: Mon, 6 Feb 2017 14:09:07 +0000 Subject: [PATCH] Previews displayed in the launcher. --- CMakeLists.txt | 2 +- src/GUI/AircraftItemDelegate.cxx | 32 +- src/GUI/AircraftItemDelegate.hxx | 8 +- src/GUI/AircraftModel.cxx | 180 ++-- src/GUI/AircraftModel.hxx | 18 +- src/GUI/CMakeLists.txt | 4 +- src/GUI/Launcher.ui | 1380 +++++++++++++++--------------- src/GUI/QtLauncher.cxx | 11 + src/GUI/QtLauncher_private.hxx | 2 +- src/GUI/preview-close.png | Bin 0 -> 2395 bytes src/GUI/preview-icon.png | Bin 0 -> 771 bytes src/GUI/preview-left-arrow.png | Bin 0 -> 1016 bytes src/GUI/preview-right-arrow.png | Bin 0 -> 964 bytes src/GUI/previewwindow.cpp | 108 +++ src/GUI/previewwindow.h | 39 + src/GUI/resources.qrc | 6 + 16 files changed, 1004 insertions(+), 786 deletions(-) create mode 100644 src/GUI/preview-close.png create mode 100644 src/GUI/preview-icon.png create mode 100644 src/GUI/preview-left-arrow.png create mode 100644 src/GUI/preview-right-arrow.png create mode 100644 src/GUI/previewwindow.cpp create mode 100644 src/GUI/previewwindow.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dadae6e22..1bbab3ac7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,7 +305,7 @@ endif (USE_DBUS) ## Qt5 setup setup if (ENABLE_QT) message(STATUS "Qt launcher enabled, checking for Qt 5.1 / qmake") - find_package(Qt5 5.1 COMPONENTS Widgets) + find_package(Qt5 5.1 COMPONENTS Widgets Network) if (Qt5Widgets_FOUND) message(STATUS "Will enable Qt launcher GUI") message(STATUS " Qt5Widgets version: ${Qt5Widgets_VERSION_STRING}") diff --git a/src/GUI/AircraftItemDelegate.cxx b/src/GUI/AircraftItemDelegate.cxx index ab9578f9a..dbaa62533 100644 --- a/src/GUI/AircraftItemDelegate.cxx +++ b/src/GUI/AircraftItemDelegate.cxx @@ -46,6 +46,8 @@ AircraftItemDelegate::AircraftItemDelegate(QListView* view) : m_leftArrowIcon.load(":/left-arrow-icon"); m_rightArrowIcon.load(":/right-arrow-icon"); + m_openPreviewsIcon.load(":/preview-icon"); + m_openPreviewsIcon = m_openPreviewsIcon.scaled(32, 32, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } void AircraftItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, @@ -79,15 +81,22 @@ void AircraftItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem painter->drawLine(option.rect.topLeft(), option.rect.topRight()); } - + // thumbnail QPixmap thumbnail = index.data(Qt::DecorationRole).value(); quint32 yPos = contentRect.center().y() - (thumbnail.height() / 2); painter->drawPixmap(contentRect.left(), yPos, thumbnail); // draw 1px frame + QRect thumbFrame(contentRect.left(), yPos, thumbnail.width(), thumbnail.height()); painter->setPen(QColor(0x7f, 0x7f, 0x7f)); painter->setBrush(Qt::NoBrush); - painter->drawRect(contentRect.left(), yPos, thumbnail.width(), thumbnail.height()); + painter->drawRect(thumbFrame); + + if (!index.data(AircraftPreviewsRole).toList().empty()) { + QRect previewIconRect = m_openPreviewsIcon.rect(); + previewIconRect.moveBottomLeft(thumbFrame.bottomLeft()); + painter->drawPixmap(previewIconRect, m_openPreviewsIcon); + } // draw bottom dividing line painter->drawLine(contentRect.left(), contentRect.bottom() + MARGIN, @@ -337,6 +346,13 @@ bool AircraftItemDelegate::eventFilter( QObject*, QEvent* event ) return true; } + + if ((event->type() == QEvent::MouseButtonRelease) && + !index.data(AircraftPreviewsRole).toList().empty() && + showPreviewsRect(vr, index).contains(me->pos())) + { + emit showPreviews(index); + } } else if ( event->type() == QEvent::MouseMove ) { } @@ -381,6 +397,18 @@ QRect AircraftItemDelegate::packageButtonRect(const QRect& visualRect, const QMo BUTTON_WIDTH, BUTTON_HEIGHT); } +QRect AircraftItemDelegate::showPreviewsRect(const QRect& visualRect, const QModelIndex& index) const +{ + QRect contentRect = visualRect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN); + QPixmap thumbnail = index.data(Qt::DecorationRole).value(); + const quint32 yPos = contentRect.center().y() - (thumbnail.height() / 2); + QRect thumbFrame(contentRect.left(), yPos, thumbnail.width(), thumbnail.height()); + + QRect previewIconRect = m_openPreviewsIcon.rect(); + previewIconRect.moveBottomLeft(thumbFrame.bottomLeft()); + return previewIconRect; +} + void AircraftItemDelegate::drawRating(QPainter* painter, QString label, const QRect& box, int value) const { QRect dotBox = box; diff --git a/src/GUI/AircraftItemDelegate.hxx b/src/GUI/AircraftItemDelegate.hxx index 16e09fad6..4ddad9c2d 100644 --- a/src/GUI/AircraftItemDelegate.hxx +++ b/src/GUI/AircraftItemDelegate.hxx @@ -50,17 +50,23 @@ Q_SIGNALS: void requestUninstall(const QModelIndex& index); void cancelDownload(const QModelIndex& index); + + void showPreviews(const QModelIndex& index); + private: QRect leftCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const; QRect rightCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const; QRect packageButtonRect(const QRect& visualRect, const QModelIndex& index) const; + QRect showPreviewsRect(const QRect& visualRect, const QModelIndex& index) const; + void drawRating(QPainter* painter, QString label, const QRect& box, int value) const; QListView* m_view; QPixmap m_leftArrowIcon, - m_rightArrowIcon; + m_rightArrowIcon, + m_openPreviewsIcon; }; #endif diff --git a/src/GUI/AircraftModel.cxx b/src/GUI/AircraftModel.cxx index 21624c2b7..512d95ebe 100644 --- a/src/GUI/AircraftModel.cxx +++ b/src/GUI/AircraftModel.cxx @@ -40,28 +40,18 @@ // FlightGear #include
- const int STANDARD_THUMBNAIL_HEIGHT = 128; const int STANDARD_THUMBNAIL_WIDTH = 172; +static quint32 CACHE_VERSION = 6; using namespace simgear::pkg; -AircraftItem::AircraftItem() : - excluded(false), - usesHeliports(false), - usesSeaports(false) +AircraftItem::AircraftItem() { - // oh for C++11 initialisers - for (int i=0; i<4; ++i) ratings[i] = 0; } -AircraftItem::AircraftItem(QDir dir, QString filePath) : - excluded(false), - usesHeliports(false), - usesSeaports(false) +AircraftItem::AircraftItem(QDir dir, QString filePath) { - for (int i=0; i<4; ++i) ratings[i] = 0; - SGPropertyNode root; readProperties(filePath.toStdString(), &root); @@ -113,6 +103,16 @@ AircraftItem::AircraftItem(QDir dir, QString filePath) : } } // of tags iteration } // of set-xml has tags + + if (sim->hasChild("previews")) { + SGPropertyNode_ptr previewsNode = sim->getChild("previews"); + for (auto previewNode : previewsNode->getChildren("preview")) { + // add file path as url + QString pathInXml = QString::fromStdString(previewNode->getStringValue("path")); + QString previewPath = dir.absoluteFilePath(pathInXml); + previews.append(QUrl::fromLocalFile(previewPath)); + } + } } QString AircraftItem::baseName() const @@ -131,6 +131,7 @@ void AircraftItem::fromDataStream(QDataStream& ds) ds >> description >> longDescription >> authors >> variantOf; for (int i=0; i<4; ++i) ds >> ratings[i]; + ds >> previews; } void AircraftItem::toDataStream(QDataStream& ds) const @@ -142,6 +143,7 @@ void AircraftItem::toDataStream(QDataStream& ds) const ds << description << longDescription << authors << variantOf; for (int i=0; i<4; ++i) ds << ratings[i]; + ds << previews; } QPixmap AircraftItem::thumbnail(bool loadIfRequired) const @@ -161,9 +163,6 @@ QPixmap AircraftItem::thumbnail(bool loadIfRequired) const return m_thumbnail; } - -static quint32 CACHE_VERSION = 5; - class AircraftScanThread : public QThread { Q_OBJECT @@ -402,14 +401,22 @@ protected: if (pix.height() > STANDARD_THUMBNAIL_HEIGHT) { pix = pix.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT, Qt::SmoothTransformation); } - m_model->m_thumbnailPixmapCache.insert(QString::fromStdString(aThumbnailUrl), pix); - // notify any affected items. Linear scan here avoids another map/dict - // structure. + m_model->m_downloadedPixmapCache.insert(QString::fromStdString(aThumbnailUrl), pix); + + // notify any affected items. Linear scan here avoids another map/dict structure. for (auto pkg : m_model->m_packages) { - const string_list& urls(pkg->thumbnailUrls()); - auto cit = std::find(urls.begin(), urls.end(), aThumbnailUrl); - if (cit != urls.end()) { + const int variantCount = pkg->variants().size(); + bool notifyChanged = false; + + for (int v=0; v < variantCount; ++v) { + const Package::Thumbnail& thumb(pkg->thumbnailForVariant(v)); + if (thumb.url == aThumbnailUrl) { + notifyChanged = true; + } + } + + if (notifyChanged) { QModelIndex mi = indexForPackage(pkg); m_model->dataChanged(mi, mi); } @@ -432,7 +439,7 @@ private: AircraftItemModel* m_model; }; -AircraftItemModel::AircraftItemModel(QObject* pr ) : +AircraftItemModel::AircraftItemModel(QObject* pr) : QAbstractListModel(pr) { } @@ -593,10 +600,6 @@ QVariant AircraftItemModel::data(const QModelIndex& index, int role) const return m_delegateStates.at(row).variant; } - if (role == AircraftCurrentThumbnailRole) { - return m_delegateStates.at(row).thumbnail; - } - if (row >= m_items.size()) { quint32 packageIndex = row - m_items.size(); const PackageRef& pkg(m_packages[packageIndex]); @@ -623,20 +626,7 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta return item->variants.count(); } - if (role == AircraftThumbnailCountRole) { - QPixmap p = item->thumbnail(); - return p.isNull() ? 0 : 1; - } - - if (role == AircraftThumbnailSizeRole) { - QPixmap pm = item->thumbnail(false); - if (pm.isNull()) { - return QSize(STANDARD_THUMBNAIL_WIDTH, STANDARD_THUMBNAIL_HEIGHT); - } - return pm.size(); - } - - if ((role >= AircraftVariantDescriptionRole) && (role < AircraftThumbnailRole)) { + if (role >= AircraftVariantDescriptionRole) { int variantIndex = role - AircraftVariantDescriptionRole; return item->variants.at(variantIndex)->description; } @@ -648,6 +638,14 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta } } + if (role == AircraftThumbnailSizeRole) { + QPixmap pm = item->thumbnail(false); + if (pm.isNull()) { + return QSize(STANDARD_THUMBNAIL_WIDTH, STANDARD_THUMBNAIL_HEIGHT); + } + return pm.size(); + } + if (role == Qt::DisplayRole) { if (item->description.isEmpty()) { return tr("Missing description for: %1").arg(item->baseName()); @@ -662,7 +660,13 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta return item->authors; } else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) { return item->ratings[role - AircraftRatingRole]; - } else if (role >= AircraftThumbnailRole) { + } else if (role == AircraftPreviewsRole) { + QVariantList result; + Q_FOREACH(QUrl u, item->previews) { + result.append(u); + } + return result; + } else if (role == AircraftThumbnailRole) { return item->thumbnail(); } else if (role == AircraftPackageIdRole) { // can we fake an ID? otherwise fall through to a null variant @@ -692,10 +696,10 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const DelegateState& state, int role) const { if (role == Qt::DecorationRole) { - role = AircraftThumbnailRole; // use first thumbnail + role = AircraftThumbnailRole; } - if ((role >= AircraftVariantDescriptionRole) && (role < AircraftThumbnailRole)) { + if (role >= AircraftVariantDescriptionRole) { int variantIndex = role - AircraftVariantDescriptionRole; QString desc = QString::fromStdString(item->nameForVariant(variantIndex)); if (desc.isEmpty()) { @@ -743,11 +747,10 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const Delega if (pm.isNull()) return QSize(STANDARD_THUMBNAIL_WIDTH, STANDARD_THUMBNAIL_HEIGHT); return pm.size(); - } else if (role >= AircraftThumbnailRole) { - DelegateState changedState(state); - // override the current thumbnail as required - changedState.thumbnail = (role - AircraftThumbnailRole); - return packageThumbnail(item, changedState); + } else if (role == AircraftThumbnailRole) { + return packageThumbnail(item, state); + } else if (role == AircraftPreviewsRole) { + return packagePreviews(item, state); } else if (role == AircraftAuthorsRole) { std::string authors = item->getLocalisedProp("author", state.variant); if (!authors.empty()) { @@ -776,46 +779,69 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const Delega QVariant AircraftItemModel::packageThumbnail(PackageRef p, const DelegateState& ds, bool download) const { - const string_list& thumbnails(p->thumbnailUrls()); - if (ds.thumbnail >= static_cast(thumbnails.size())) { + const Package::Thumbnail& thumb(p->thumbnailForVariant(ds.variant)); + if (thumb.url.empty()) { return QVariant(); } - std::string thumbnailUrl = thumbnails.at(ds.thumbnail); - QString urlQString(QString::fromStdString(thumbnailUrl)); - if (m_thumbnailPixmapCache.contains(urlQString)) { + QString urlQString(QString::fromStdString(thumb.url)); + if (m_downloadedPixmapCache.contains(urlQString)) { // cache hit, easy - return m_thumbnailPixmapCache.value(urlQString); + return m_downloadedPixmapCache.value(urlQString); } -// check the on-disk store. This relies on the order of thumbnails in the -// results of thumbnailUrls and thumbnails corresponding +// check the on-disk store. InstallRef ex = p->existingInstall(); if (ex.valid()) { - const string_list& thumbNames(p->thumbnails()); - if (!thumbNames.empty()) { - SGPath path(ex->path()); - path.append(p->thumbnails()[ds.thumbnail]); - if (path.exists()) { - QPixmap pix; - pix.load(QString::fromStdString(path.utf8Str())); - // resize to the standard size - if (pix.height() > STANDARD_THUMBNAIL_HEIGHT) { - pix = pix.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT); - } - m_thumbnailPixmapCache[urlQString] = pix; - return pix; + SGPath thumbPath = ex->path() / thumb.path; + if (thumbPath.exists()) { + QPixmap pix; + pix.load(QString::fromStdString(thumbPath.utf8Str())); + // resize to the standard size + if (pix.height() > STANDARD_THUMBNAIL_HEIGHT) { + pix = pix.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT); } - } // of have thumbnail file names + m_downloadedPixmapCache[urlQString] = pix; + return pix; + } } // of have existing install if (download) { - m_packageRoot->requestThumbnailData(thumbnailUrl); + m_packageRoot->requestThumbnailData(thumb.url); } return QVariant(); } +QVariant AircraftItemModel::packagePreviews(PackageRef p, const DelegateState& ds) const +{ + const Package::PreviewVec& previews = p->previewsForVariant(ds.variant); + if (previews.empty()) { + return QVariant(); + } + + QVariantList result; + // if we have an install, return file URLs, not remote (http) ones + InstallRef ex = p->existingInstall(); + if (ex.valid()) { + for (auto p : previews) { + SGPath localPreviewPath = ex->path() / p.path; + if (!localPreviewPath.exists()) { + qWarning() << "missing local preview" << QString::fromStdString(localPreviewPath.utf8Str()); + continue; + } + result.append(QUrl::fromLocalFile(QString::fromStdString(localPreviewPath.utf8Str()))); + } + } + + // return remote urls + for (auto p : previews) { + result.append(QUrl(QString::fromStdString(p.url))); + } + + return result; +} + bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, int role) { int row = index.row(); @@ -831,16 +857,6 @@ bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, return true; } - if (role == AircraftCurrentThumbnailRole) { - if (m_delegateStates[row].thumbnail == newValue) { - return true; - } - - m_delegateStates[row].thumbnail = newValue; - emit dataChanged(index, index); - return true; - } - return false; } diff --git a/src/GUI/AircraftModel.hxx b/src/GUI/AircraftModel.hxx index 444425f38..613cb75e0 100644 --- a/src/GUI/AircraftModel.hxx +++ b/src/GUI/AircraftModel.hxx @@ -38,7 +38,6 @@ const int AircraftPathRole = Qt::UserRole + 1; const int AircraftAuthorsRole = Qt::UserRole + 2; const int AircraftVariantRole = Qt::UserRole + 3; const int AircraftVariantCountRole = Qt::UserRole + 4; -const int AircraftThumbnailCountRole = Qt::UserRole + 5; const int AircraftPackageIdRole = Qt::UserRole + 6; const int AircraftPackageStatusRole = Qt::UserRole + 7; const int AircraftPackageProgressRole = Qt::UserRole + 8; @@ -51,12 +50,12 @@ const int AircraftURIRole = Qt::UserRole + 14; const int AircraftThumbnailSizeRole = Qt::UserRole + 15; const int AircraftIsHelicopterRole = Qt::UserRole + 16; const int AircraftIsSeaplaneRole = Qt::UserRole + 17; -const int AircraftCurrentThumbnailRole = Qt::UserRole + 18; const int AircraftPackageRefRole = Qt::UserRole + 19; +const int AircraftThumbnailRole = Qt::UserRole + 20; +const int AircraftPreviewsRole = Qt::UserRole + 21; const int AircraftRatingRole = Qt::UserRole + 100; const int AircraftVariantDescriptionRole = Qt::UserRole + 200; -const int AircraftThumbnailRole = Qt::UserRole + 300; class AircraftScanThread; class QDataStream; @@ -81,17 +80,18 @@ struct AircraftItem QPixmap thumbnail(bool loadIfRequired = true) const; - bool excluded; + bool excluded = false; QString path; QString description; QString longDescription; QString authors; - int ratings[4]; + int ratings[4] = {0, 0, 0, 0}; QString variantOf; QDateTime pathModTime; QList variants; - bool usesHeliports; - bool usesSeaports; + bool usesHeliports = false; + bool usesSeaports = false; + QList previews; private: mutable QPixmap m_thumbnail; }; @@ -199,6 +199,8 @@ private: QVariant packageThumbnail(simgear::pkg::PackageRef p, const DelegateState& state, bool download = true) const; + QVariant packagePreviews(simgear::pkg::PackageRef p, const DelegateState &ds) const; + void abandonCurrentScan(); void refreshPackages(); @@ -217,7 +219,7 @@ private: simgear::pkg::RootRef m_packageRoot; simgear::pkg::PackageList m_packages; - mutable QHash m_thumbnailPixmapCache; + mutable QHash m_downloadedPixmapCache; }; #endif // of FG_GUI_AIRCRAFT_MODEL diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index aedf62068..a9c4d28f3 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -117,11 +117,13 @@ if (HAVE_QT) InstallSceneryDialog.cxx EditCustomMPServerDialog.cxx EditCustomMPServerDialog.hxx + PreviewWindow.cpp + PreviewWindow.h ${uic_sources} ${qrc_sources}) set_property(TARGET fglauncher PROPERTY AUTOMOC ON) - target_link_libraries(fglauncher Qt5::Core Qt5::Widgets SimGearCore) + target_link_libraries(fglauncher Qt5::Core Qt5::Widgets Qt5::Network SimGearCore) target_include_directories(fglauncher PRIVATE ${PROJECT_BINARY_DIR}/src/GUI) diff --git a/src/GUI/Launcher.ui b/src/GUI/Launcher.ui index 71e60daf8..e7da4408d 100644 --- a/src/GUI/Launcher.ui +++ b/src/GUI/Launcher.ui @@ -1,701 +1,701 @@ - Launcher - - - - 0 - 0 - 642 - 600 - + Launcher + + + + 0 + 0 + 642 + 600 + + + + Start FlightGear + + + + :/app-icon-large:/app-icon-large + + + + + 6 - - Start FlightGear + + 8 - - - :/app-icon-large:/app-icon-large + + 6 + + + 4 + + + + + 1 - - - - 6 + + + Summary + + + + + + + 16 + + + + Settings: + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + + 16 + + + + Location: + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 16 + + + + Aircraft: + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + Qt::Vertical + + + + 20 + 294 + + + + + + + + + 16 + + + + aircraft + + + true + + + + + + + + 11 + + + + TextLabel + + + true + + + + + + + false + + + + + + + + 16 + + + + settings + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + 16 + + + + location + + + true + + + + + + + + 0 + 0 + + + + + 171 + 128 + + + + TextLabel + + + + + + + + Aircraft + + + + 4 + + + 4 + + + 8 + + + 4 + + + 4 + + + + + 4 - 8 + 4 - - 6 - - - 4 - - - - - 0 - - - - Summary - - - - - - - 16 - - - - Settings: - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - - - - - - 16 - - - - Location: - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - - - - - false - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 16 - - - - Aircraft: - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - - - - - Qt::Vertical - - - - 20 - 294 - - - - - - - - - 16 - - - - aircraft - - - true - - - - - - - - 11 - - - - TextLabel - - - true - - - - - - - false - - - - - - - - 16 - - - - settings - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - - - - - - 16 - - - - location - - - true - - - - - - - - 0 - 0 - - - - - 171 - 128 - - - - TextLabel - - - - - - - - Aircraft - - - - 4 - - - 4 - - - 8 - - - 4 - - - 4 - - - - - 4 - - - 4 - - - - - Search: - - - - - - - Search aircraft (press Enter to search) - - - - - - - - - - - Only show installed aircraft - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - Hide aircraft based on completeness (rating) - - - true - - - - - - - Edit... - - - false - - - - - - - - - QAbstractItemView::ScrollPerPixel - - - QAbstractItemView::ScrollPerPixel - - - - - - - - Location - - - - 4 - - - 4 - - - 4 - - - - - - - - - Settings - - - - 8 - - - 8 - - - 8 - - - - - - 0 - 100 - - - - Multi-player - - - true - - - false - - - - - - Callsign: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Server: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - 10 - - - G-FGFS - - - - - - - (Ten characters maximum) - - - - - - - - - - - - - Additional options - - - true - - - - 4 - - - 4 - - - 4 - - - 4 - - - - - - - - - - - Start full-screen - - - true - - - - - - - Enable Multi-sample anti-aliasing - - - true - - - - - - - Fetch real weather online - - - - - - - Start paused - - - true - - - - - - - Restore Defaults... - - - false - - - - - - - - - Time of day: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - Current time - - - - - Dawn - - - - - Morning - - - - - Noon - - - - - Afternoon - - - - - Dusk - - - - - Evening - - - - - Midnight - - - - - - - - - - Enable deferred rendering (Rembrandt) - - - true - - - - - - - Enable automatic scenery downloading (TerraSync) - - - - - - - <html><head/><body><p>For information on additional options, please <a href="http://flightgear.sourceforge.net/getstart-en/getstart-enpa2.html#x5-450004.5"><span style=" text-decoration: underline; color:#0000ff;">see here</span></a>.</p></body></html> - - - Qt::RichText - - - true - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 40 - 20 - - - - - - - - <html><head/><body><p>If scenery download is disabled, you may need to download additional files from <a href="http://www.flightgear.org/download/scenery/"><span style=" text-decoration: underline; color:#0000ff;">this page</span></a> and install them in a scenery location; otherwise some objects may be missing from the world.</p></body></html> - - - true - - - true - - - - - - - - - - - Season: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - Summer - - - - - Winter - - - - - - - - - + + + + Search: + + - - - - 0 - - - - - Quit - - - false - - - - - - - Qt::Horizontal - - - - 412 - 20 - - - - - - - - Run - - - false - - - false - - - false - - - - + + + + Search aircraft (press Enter to search) + + - + + + + + + + + Only show installed aircraft + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Hide aircraft based on completeness (rating) + + + true + + + + + + + Edit... + + + false + + + + + + + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + + - - - - LocationWidget - QWidget -
GUI/LocationWidget.hxx
- 1 -
-
- - - - -
+ + + Location + + + + 4 + + + 4 + + + 4 + + + + + + + + + Settings + + + + 8 + + + 8 + + + 8 + + + + + + 0 + 100 + + + + Multi-player + + + true + + + false + + + + + + Callsign: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Server: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 10 + + + G-FGFS + + + + + + + (Ten characters maximum) + + + + + + + + + + + + + Additional options + + + true + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + + + + + + Start full-screen + + + true + + + + + + + Enable Multi-sample anti-aliasing + + + true + + + + + + + Fetch real weather online + + + + + + + Start paused + + + true + + + + + + + Restore Defaults... + + + false + + + + + + + + + Time of day: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Current time + + + + + Dawn + + + + + Morning + + + + + Noon + + + + + Afternoon + + + + + Dusk + + + + + Evening + + + + + Midnight + + + + + + + + + + Enable deferred rendering (Rembrandt) + + + true + + + + + + + Enable automatic scenery downloading (TerraSync) + + + + + + + <html><head/><body><p>For information on additional options, please <a href="http://flightgear.sourceforge.net/getstart-en/getstart-enpa2.html#x5-450004.5"><span style=" text-decoration: underline; color:#0000ff;">see here</span></a>.</p></body></html> + + + Qt::RichText + + + true + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + <html><head/><body><p>If scenery download is disabled, you may need to download additional files from <a href="http://www.flightgear.org/download/scenery/"><span style=" text-decoration: underline; color:#0000ff;">this page</span></a> and install them in a scenery location; otherwise some objects may be missing from the world.</p></body></html> + + + true + + + true + + + + + + + + + + + Season: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Summer + + + + + Winter + + + + + + + + + + + + + + 0 + + + + + Quit + + + false + + + + + + + Qt::Horizontal + + + + 412 + 20 + + + + + + + + Run + + + false + + + false + + + false + + + + + + + + + + + LocationWidget + QWidget +
GUI/LocationWidget.hxx
+ 1 +
+
+ + + + + diff --git a/src/GUI/QtLauncher.cxx b/src/GUI/QtLauncher.cxx index 08efa2c79..77abd10e2 100644 --- a/src/GUI/QtLauncher.cxx +++ b/src/GUI/QtLauncher.cxx @@ -69,6 +69,7 @@ #include "AircraftModel.hxx" #include "PathsDialog.hxx" #include "EditCustomMPServerDialog.hxx" +#include "previewwindow.h" #include
#include
@@ -854,6 +855,8 @@ QtLauncher::QtLauncher() : this, &QtLauncher::onRequestPackageUninstall); connect(delegate, &AircraftItemDelegate::cancelDownload, this, &QtLauncher::onCancelDownload); + connect(delegate, &AircraftItemDelegate::showPreviews, + this, &QtLauncher::onShowPreviews); connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted, this, &QtLauncher::onAircraftInstalledCompleted); @@ -1401,6 +1404,14 @@ void QtLauncher::onRequestPackageUninstall(const QModelIndex& index) } } +void QtLauncher::onShowPreviews(const QModelIndex &index) +{ + QVariant urls = index.data(AircraftPreviewsRole); + + PreviewWindow* previewWindow = new PreviewWindow; + previewWindow->setUrls(urls.toList()); +} + void QtLauncher::onCancelDownload(const QModelIndex& index) { QString pkg = index.data(AircraftPackageIdRole).toString(); diff --git a/src/GUI/QtLauncher_private.hxx b/src/GUI/QtLauncher_private.hxx index 451b4c9e3..ed6430a53 100644 --- a/src/GUI/QtLauncher_private.hxx +++ b/src/GUI/QtLauncher_private.hxx @@ -75,7 +75,7 @@ private slots: void onAircraftSelected(const QModelIndex& index); void onRequestPackageInstall(const QModelIndex& index); void onRequestPackageUninstall(const QModelIndex& index); - + void onShowPreviews(const QModelIndex& index); void onCancelDownload(const QModelIndex& index); void onPopupAircraftHistory(); diff --git a/src/GUI/preview-close.png b/src/GUI/preview-close.png new file mode 100644 index 0000000000000000000000000000000000000000..94309ec98016741207f5d869a53126096ea4e0d7 GIT binary patch literal 2395 zcmY*bdpy(YAO99vxujf58(M2f%v=i1Osl1hS?nlVh)t%MvCU+KlBiGzZf7Db}kn5(fQ3;j(ZL0V2k@92J~J#7x~fZ~hehxmVX#%B@9k@zd?w?+PNCu7%2E`FTs=Wr zSy&>!Yb!$0t0?LSj~S?NUe(7~p}#Qy5BuhWMy{IwPcmOC{emh=#i*i@->(g$N^CZv z0stt4fWvy8S~q!%dg`c`&ZW+}Td2cDx~d4uW*9bm(ES<$e@JS#^FH)$$}}Nq*KTDH zry2ABdtankzf1LbQ=$8=D~Hnknd6=b7<*i?DDj7qn^R+-Cm(fB%5HTHgCR;5l==6x z($9XZe_~|4@8BM@q=%=KwQ}&9$LP@+2M5!39{#o8uReO;%hUahYox@n zDe{idVd(?b4TEdVOa1f5FaBICR`VGy#r7-P977$Dr}0y(a&?%gT?E#>210SZX(2&V z_x_`8;$LMx;e^=zEk>5d=YA10p2_c4RB%QYriYyLZ5Tvef8zxY77NvI(t4ZYxn=vJ zvNDoy4QC*w*sJ`lJ#YAN%E#Bpm7__U)WF8hQ9{+7f#iG5WpfkVPM8=u|IMpcIzqCk zkgIP;fQYM}$VF`+Kg)amH{L zNT#5VaR@#C3uB^)CmkvypVMXLJH)i!Xk*j98g9Ug;U_opYHQJ5c$cOT$1@+{s3=(o ze?>mm00x7{C&tGO-!zP6hPK(ZI8(nisMSmn>175GN zi`8A$HuJ8tVMl8MS)3eKXFyKhj4Qpp($N^XgW?~j0rKG#mG4hFY5&KcUZ16}M9QS# z*Gk!&gX?%Bs(B}j5bFLYBDC*!P|5C!fmk)tgPe3NCAw8Ut9)Va&`<3t+d=6OQr<*Y zsbH`*F>~1A)snWdwDE>8#LKNxEi+=<}cS z8yS=4C2qG1+6?oB4Gg8M^WGa=gIU^CPX ze0ie*eb1ICO{Gd!*n=Z4HvZ8*fBCl-yZp?n)i>`tM{jDWn4s6dL%R~Ow&?f7X1!8G z_})~mBEhJ&N%sm13RE_A1>CMyZjIGiS(@*wKRXv3Qm-LxftOtE+!!*wA><(1p47C# z&Z+gTgiCIv6T6%H`aP-VJ}sY#-WDTU45}2DTKi1Y&CJYH=h|~&T`jxHN=o2~NeVUZ z#?*ZW`i@=DkSr{~LyYmw%k^>Ep#(M2{c@jR2ZOfER%d>H?U|W#OeR-kHy*NmtFCTL zZXg?X<5=nOk+oWBkkXdRnvdX~cD0W)%|>Vbz-cY0MqY2XC3+<&bask1-9QQSVB6mF z_pY!28wvZh%-w|0{vfOmgWp2a>PS`Eg+PpKm~j3R^mk*MGeX81S7k_s``G(lu_!rV zDBhYqOB-TulS(pDZ{@)Ija6{_ecteuMGOY?wnJo@(adAW;j_s}moy+Ukv{wKQ&K!| z{FlTL@kJo$c{URl+S|j6%f_%p`8{$-u2FhUJa;x;k;Zmy2l{Y843`S>e(5c~R++%6 zO7jZKbLns%OaS#N?&L|;K~3w|Y&`^WU~2fb_9v_)@1@~MSY_eRE|1CF)^fB9$8v}Q z9s8Jv6L_AQr~RnZ`fIgvon-KRUZg4b44M>ERP;jumZ z`dP9QtvTLqj_j14dG@@FCUlv`VnzIgd<-#G$f;f~IiMx;(R*Rw6p{we=Pxc*Jr?RF zF8$a`&E2wyUM#>M3Vv94cTQmDG$nl&+$4$E3kkj7Y19|eQ*p;Pw|kC!Le1an{0S44 z>lHYa;#vZ-FGmL2SkagscP&18EQ2I}0D;t04;~@f78yKAfn<%u!}9WdWryp1w4kS! z@$hJ2K1T8CJk6z2HxA@d7k_iTHhU{Q#d9pvR~O!49s$vje%i`SRNkH^bsLYtqq?zt rpYoAxT?FN?DL;)$p1^DJ!}?_n$0PpFk}}F^t6vzxArh|afPdV-+QLM; literal 0 HcmV?d00001 diff --git a/src/GUI/preview-icon.png b/src/GUI/preview-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..db64d8491a4f2da37e6fce2b439a1577bbae9769 GIT binary patch literal 771 zcmV+e1N{7nP)Px%y-7qtRA>e5nZZgNK@f&_V~{K!6g=(4AYKF^r$El$lA}+sj}i2u5bzB`PF_6v z0wGTz$xUy;fOw1{5Rc}N&G*k#jcxZ%*a0`Yvv$F6byxjWUDe&YJA<80mb?Rb2OifB z42Q!-FaTe`tcz+KfW_pLFdl-o$>FF*uC0rM#5rRI>fE7TNp97qCyWQXBdpH&U zz%?(K%etMoUw77~{ZL*egJ-^Q@UF)bA>42CX*abHpZ(2njf!E-fL1WqCW)-U( zy>x|o3;aS7My(@ZmInCS;G4&p{)7GimTJN}@)<|mP?T%CE-tovU~Ja{g(Qy10BHf} zN!bgzVEhY6jw5>QRNNw>j8zVaaw_*iZJ;?smL$B=Ji-w$;VgrX#?_+l!5f=@N3S@Y z@fvsw&J7x`1^yN0ijgRXa{1@6`O4=<0eXnE9>ws@G2DzN;I)0JaU| z-2^thUQ2G>Ce2*e@)Af+L!XaJ*IgrX^N8XNK8~xUZaK<6&<9YWtJA&@yB{0HxBu%m zXr}!?h~A7er_=67l&F~=4ebK|x@A~Mdmb92hNk`)-}QpDj~er`Q`y`%t|rBQd$Fpm zR}RUzMNgYoR(n_P-2AfVMy7qW;`Q^1#5*$Wc|MV8pFq5Rcp^XK3heMCwT|*4$R#S`~{3KXsi=e?7jd1002ovPDHLkV1fXs BcYy!^ literal 0 HcmV?d00001 diff --git a/src/GUI/preview-left-arrow.png b/src/GUI/preview-left-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..14281dbacfb3b42e4894e8417fe610f376666e79 GIT binary patch literal 1016 zcmeAS@N?(olHy`uVBq!ia0vp^PC%T(!3HFa7Ct)+q*&4&eH|GXHuiJ>Nn{1`6_P!I zd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4FdyXWM)W2NrbPDRdRl= zUSdjqQmS4>ZUImS1A|S46_A;mT9T+xk(-lOY*k^a1Xf`MWP^nDl@!2AO0sR0B76fB zob!uP6-@O^^bC~jxD*r=Y>HCStb$zJpxS{vTcwPWk^(Dz{qpj1y>er{{GxPyLrY6b zeFGzXBO_g)3f4M^0=zbG>mXfw!sJ3||=N)$1uJvRCv%aI%d z@hMmo=v6x|8+~}N*>U+CQjcU{V3PB6aSW+oe0#^xi#br>*vB>A8#Z_-Hl@Bx@R;nz zagy^=d&xGlDt76O0;eZviJxH1VPfRw))xFClC|{w%r%jAOnPnxD*6Ap7YDD|@%!`a z=@N3$D+?4F7??N|8V+|=Y;=P)6RZ%awO~Ylz*|ddWt1S<0zFN0NV83gHVS^QiLWoCGFC&LS%6dMPMwJOI zf6fX$K44+M*W)~)enEsV4E`~86LS-=VOL>n*~#E}LLx%KlgaZ0iwBdZ0_RQ!ppGYr z4{Dhn2dpc+vGqLv^WE2{ndezLM;<%Kqm>?(Xd#nUU-vUgjir4@zKK-P??`QqgXdQM z?z~<1Cg(Trn%BQ1d-F5JygOxX|2`nVnCZzTp`@UDlEIl}iK=8mN5d8sW*)%_(^47~ znJ!J@F>rB+n#9<~;o)sWlHz|2f?t_;ipjN}oZ!ACR<};!+51+BxqipDTiYGwS@8Xv zvfJjHB^~Uxmf!4-zxe(;(P(B%-1_GnanCp2|MTgu<+0wYj{?`{Uz;B_wM=-e{h#kQ nUR~&7W@Hg?U|?jqrv8C-VUpy_ literal 0 HcmV?d00001 diff --git a/src/GUI/preview-right-arrow.png b/src/GUI/preview-right-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..b5702a9a2e272fe86897bd4995e50273bcd2f0d4 GIT binary patch literal 964 zcmeAS@N?(olHy`uVBq!ia0vp^_CTD>!3HFCgl_}`DVB6cUq=Rpjs4tz5?O(Kg=CK) zUj~LMH3o);76yi2K%s^g3=E|P3=FRl7#OT(FffQ0%-I!a!@$58nHdsM65;D(m7Jfe zmza{Dl&V*eTL9F-z+h8h1!U%?mLw`vE(HYzo1&C7s~{IQsCFRFRw<*Tq`*pFzr4I$uiRKKzbIYb(9+UU z-@r)U$VeBcLbtdwuOzWTH?LS3VhGF}m(=3qqRfJl%=|nBkhzIT`K2YcN=hJ$-~i&z zlFT%OO?kyoZvj2150cS0)HBeBn+?=z0}{3JFUm{>+6*$^&d>&|5=9JZkBvUaawNw< zd-gh3`eeAl_{omhX*NYrn-j-355tIEUV@7^@SHL5S zX)jB(D(86%u=KT9_r|_{@&5SZja#AvMeYfFn4#h+{rk-wai+XO22)n8+N1Iy;PWrb zE~S;PZ+SEIO>GG0c)+Q^dV#@ZLSRGU1gnIJjPhQIT#8zbUWt1c(m2dJq>u1EVk>Ok z;dtl4B5|QJ6WEQNfoz350c5F!?b3U$HfdflxNha0xIM&AaQ6zn;&w$`Jz9g2A!(vP<7lzekuf5EF2R;&6}Eq?X);*+!M zSwh{f29}20U9w7krQJTAXoi%^cV~LJh39W?ukyOQMK$|#@aytF+oe{tA6{tU``Nug z|9~u`xYVT7nNw0<-c4(W=K2@&`0T_554SacNt5|sGa)bZ? literal 0 HcmV?d00001 diff --git a/src/GUI/previewwindow.cpp b/src/GUI/previewwindow.cpp new file mode 100644 index 000000000..5a4ce47df --- /dev/null +++ b/src/GUI/previewwindow.cpp @@ -0,0 +1,108 @@ +#include "previewwindow.h" + +#include +#include +#include + +const int BORDER_SIZE = 16; + +PreviewWindow::PreviewWindow(QWidget *parent) + : QDialog(parent) + , m_netAccess(new QNetworkAccessManager(this)) +{ + setWindowFlags(Qt::Popup); + setModal(true); + + m_closeIcon.load(":/preview/close-icon"); + m_leftIcon.load(":/preview/left-arrow-icon"); + m_rightIcon.load(":/preview/right-arrow-icon"); +} + +void PreviewWindow::setUrls(QVariantList urls) +{ + m_cache.clear(); + + Q_FOREACH (QVariant v, urls) { + QUrl url = v.toUrl(); + qWarning() << v; + m_urls.append(url); + QNetworkReply* reply = m_netAccess->get(QNetworkRequest(url)); + connect(reply, &QNetworkReply::finished, this, &PreviewWindow::onDownloadFinished); + } +} + +void PreviewWindow::paintEvent(QPaintEvent *pe) +{ + QUrl key = m_urls.at(m_currentPreview); + QPixmap pm = m_cache.value(key.toString()); + if (pm.isNull()) { + qWarning() << "null pixmap"; + } + + QPainter painter(this); + painter.fillRect(rect(), Qt::black); + + QRect imgRect = rect().adjusted(BORDER_SIZE, BORDER_SIZE, -BORDER_SIZE, -BORDER_SIZE); + painter.drawPixmap(imgRect, pm); + + QRect closeIconRect = m_closeIcon.rect(); + closeIconRect.moveTopRight(rect().topRight()); + painter.drawPixmap(closeIconRect, m_closeIcon); + + QRect leftArrowRect = m_leftIcon.rect(); + unsigned int iconTop = rect().center().y() - (m_leftIcon.size().height() / 2); + leftArrowRect.moveTopLeft(QPoint(0, iconTop)); + painter.drawPixmap(leftArrowRect, m_leftIcon); + + QRect rightArrowRect = m_rightIcon.rect(); + rightArrowRect.moveTopRight(QPoint(width(), iconTop)); + painter.drawPixmap(rightArrowRect, m_rightIcon); +} + +void PreviewWindow::mouseReleaseEvent(QMouseEvent *event) +{ + QRect closeIconRect = m_closeIcon.rect(); + closeIconRect.moveTopRight(rect().topRight()); + + QRect leftArrowRect = m_leftIcon.rect(); + unsigned int iconTop = rect().center().y() - (m_leftIcon.size().height() / 2); + leftArrowRect.moveTopLeft(QPoint(0, iconTop)); + + QRect rightArrowRect = m_rightIcon.rect(); + rightArrowRect.moveTopRight(QPoint(width(), iconTop)); + + if (closeIconRect.contains(event->pos())) { + close(); + deleteLater(); + } + + if (leftArrowRect.contains(event->pos())) { + m_currentPreview = (m_currentPreview - 1) % m_urls.size(); + update(); + } + + if (rightArrowRect.contains(event->pos())) { + m_currentPreview = (m_currentPreview + 1) % m_urls.size(); + update(); + } +} + +void PreviewWindow::onDownloadFinished() +{ + QNetworkReply* reply = qobject_cast(sender()); + + QImage img; + if (!img.load(reply, nullptr)) { + qWarning() << "failed to read image data from" << reply->url(); + return; + } + + m_cache.insert(reply->url().toString(), QPixmap::fromImage(img)); + + if (!isVisible()) { + QSize winSize(img.width() + BORDER_SIZE * 2, img.height() + BORDER_SIZE * 2); + resize(winSize); + + show(); + } +} diff --git a/src/GUI/previewwindow.h b/src/GUI/previewwindow.h new file mode 100644 index 000000000..d673f1504 --- /dev/null +++ b/src/GUI/previewwindow.h @@ -0,0 +1,39 @@ +#ifndef PREVIEWWINDOW_H +#define PREVIEWWINDOW_H + +#include + +#include +#include +#include + +class PreviewWindow : public QDialog +{ + Q_OBJECT +public: + explicit PreviewWindow(QWidget *parent = 0); + + void setUrls(QVariantList urls); + +signals: + +public slots: + +protected: + virtual void paintEvent(QPaintEvent *pe) override; + + virtual void mouseReleaseEvent(QMouseEvent *event) override; + +private: + void onDownloadFinished(); + + + unsigned int m_currentPreview = 0; + QNetworkAccessManager* m_netAccess; + QList m_urls; + QMap m_cache; + + QPixmap m_leftIcon, m_rightIcon, m_closeIcon; +}; + +#endif // PREVIEWWINDOW_H diff --git a/src/GUI/resources.qrc b/src/GUI/resources.qrc index 7dc2ebd03..45be6a206 100644 --- a/src/GUI/resources.qrc +++ b/src/GUI/resources.qrc @@ -19,5 +19,11 @@ ndb-large-icon.png airplane-icon.png airport-closed-icon.png + preview-icon.png + + + preview-close.png + preview-left-arrow.png + preview-right-arrow.png