1
0
Fork 0

Launcher: favourite aircraft support

This commit is contained in:
James Turner 2020-04-01 10:47:29 +01:00
parent 3060cf10c6
commit 2904321959
16 changed files with 248 additions and 15 deletions

View file

@ -23,6 +23,7 @@
#include <QSettings>
#include <QDebug>
#include <QSharedPointer>
#include <QSettings>
// Simgear
#include <simgear/props/props_io.hxx>
@ -157,6 +158,8 @@ AircraftItemModel::AircraftItemModel(QObject* pr) :
this, &AircraftItemModel::onScanAddedItems);
connect(cache, &LocalAircraftCache::cleared,
this, &AircraftItemModel::onLocalCacheCleared);
loadFavourites();
}
AircraftItemModel::~AircraftItemModel()
@ -238,6 +241,12 @@ QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
return m_delegateStates.at(row).variant;
}
if (role == AircraftIsFavouriteRole) {
// recursive call here, hope that's okay
const auto uri = data(index, AircraftURIRole).toUrl();
return m_favourites.contains(uri);
}
if (row >= m_cachedLocalAircraftCount) {
quint32 packageIndex = static_cast<quint32>(row - m_cachedLocalAircraftCount);
const PackageRef& pkg(m_packages[packageIndex]);
@ -421,6 +430,18 @@ bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value,
m_delegateStates[row].variant = newValue;
emit dataChanged(index, index);
return true;
} else if (role == AircraftIsFavouriteRole) {
bool f = value.toBool();
const auto uri = data(index, AircraftURIRole).toUrl();
const auto cur = m_favourites.contains(uri);
if (f && !cur) {
m_favourites.append(uri);
} else if (!f && cur) {
m_favourites.removeOne(uri);
}
saveFavourites();
emit dataChanged(index, index);
}
return false;
@ -441,6 +462,7 @@ QHash<int, QByteArray> AircraftItemModel::roleNames() const
result[AircraftInstallDownloadedSizeRole] = "downloadedBytes";
result[AircraftVariantRole] = "activeVariant";
result[AircraftIsFavouriteRole] = "favourite";
result[AircraftStatusRole] = "aircraftStatus";
result[AircraftMinVersionRole] = "requiredFGVersion";
@ -560,7 +582,7 @@ QString AircraftItemModel::nameForAircraftURI(QUrl uri) const
QString ident = uri.path();
PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString());
if (pkg) {
int variantIndex = pkg->indexOfVariant(ident.toStdString());
const auto variantIndex = pkg->indexOfVariant(ident.toStdString());
return QString::fromStdString(pkg->nameForVariant(variantIndex));
}
} else {
@ -572,7 +594,7 @@ QString AircraftItemModel::nameForAircraftURI(QUrl uri) const
void AircraftItemModel::onScanAddedItems(int addedCount)
{
Q_UNUSED(addedCount);
Q_UNUSED(addedCount)
const auto items = LocalAircraftCache::instance()->allItems();
const int newItemCount = items.size() - m_cachedLocalAircraftCount;
const int firstRow = m_cachedLocalAircraftCount;
@ -631,7 +653,7 @@ bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
return true; // local file, always runnable
}
quint32 packageIndex = index.row() - m_cachedLocalAircraftCount;
quint32 packageIndex = static_cast<quint32>(index.row() - m_cachedLocalAircraftCount);
const PackageRef& pkg(m_packages[packageIndex]);
InstallRef ex = pkg->existingInstall();
if (!ex.valid()) {
@ -641,4 +663,21 @@ bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
return !ex->isDownloading();
}
void AircraftItemModel::loadFavourites()
{
m_favourites.clear();
QSettings settings;
Q_FOREACH(auto v, settings.value("favourite-aircraft").toList()) {
m_favourites.append(v.toUrl());
}
}
void AircraftItemModel::saveFavourites()
{
QVariantList favs;
Q_FOREACH(auto u, m_favourites) {
favs.append(u);
}
QSettings settings;
settings.setValue("favourite-aircraft", favs);
}

View file

@ -50,6 +50,7 @@ const int AircraftURIRole = Qt::UserRole + 14;
const int AircraftIsHelicopterRole = Qt::UserRole + 16;
const int AircraftIsSeaplaneRole = Qt::UserRole + 17;
const int AircraftPackageRefRole = Qt::UserRole + 19;
const int AircraftIsFavouriteRole = Qt::UserRole + 20;
const int AircraftStatusRole = Qt::UserRole + 22;
const int AircraftMinVersionRole = Qt::UserRole + 23;
@ -145,14 +146,20 @@ private:
void installSucceeded(QModelIndex index);
void installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason);
void loadFavourites();
void saveFavourites();
private:
PackageDelegate* m_delegate = nullptr;
QVector<DelegateState> m_delegateStates;
simgear::pkg::RootRef m_packageRoot;
simgear::pkg::PackageList m_packages;
QVector<QUrl> m_favourites;
int m_cachedLocalAircraftCount = 0;
};
#endif // of FG_GUI_AIRCRAFT_MODEL

View file

@ -14,6 +14,8 @@ AircraftProxyModel::AircraftProxyModel(QObject *pr, QAbstractItemModel * source)
setSortCaseSensitivity(Qt::CaseInsensitive);
setFilterCaseSensitivity(Qt::CaseInsensitive);
// important we sort on the primary name role and not Qt::DisplayRole
// otherwise the aircraft jump when switching variant
setSortRole(AircraftVariantDescriptionRole);
@ -113,6 +115,15 @@ void AircraftProxyModel::setHaveUpdateFilterEnabled(bool e)
invalidate();
}
void AircraftProxyModel::setShowFavourites(bool e)
{
if (e == m_onlyShowFavourites)
return;
m_onlyShowFavourites = e;
invalidate();
}
bool AircraftProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
@ -145,6 +156,11 @@ bool AircraftProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sour
}
}
if (m_onlyShowFavourites) {
if (!index.data(AircraftIsFavouriteRole).toBool())
return false;
}
// if there is no search active, i.e we are browsing, we might apply the
// ratings filter.
if (m_filterString.isEmpty() && !m_onlyShowInstalled && m_ratingsFilter) {

View file

@ -60,6 +60,8 @@ public slots:
void setInstalledFilterEnabled(bool e);
void setHaveUpdateFilterEnabled(bool e);
void setShowFavourites(bool e);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
@ -69,6 +71,7 @@ private:
bool m_ratingsFilter = true;
bool m_onlyShowInstalled = false;
bool m_onlyShowWithUpdate = false;
bool m_onlyShowFavourites = false;
QList<int> m_ratings;
QString m_filterString;

View file

@ -172,6 +172,8 @@ if (HAVE_QT)
RouteDiagram.hxx
ModelDataExtractor.cxx
ModelDataExtractor.hxx
HoverArea.cxx
HoverArea.hxx
)
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)

33
src/GUI/HoverArea.cxx Normal file
View file

@ -0,0 +1,33 @@
#include "HoverArea.hxx"
#include <QDebug>
#include <QQuickWindow>
HoverArea::HoverArea()
{
connect(this, &QQuickItem::windowChanged, [this](QQuickWindow* win) {
if (win) {
win->installEventFilter(this);
}
});
}
bool HoverArea::eventFilter(QObject *sender, QEvent *event)
{
Q_UNUSED(sender)
if (event->type() == QEvent::MouseMove) {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
const auto local = mapFromScene(me->pos());
const bool con = contains(local);
if (con != m_containsMouse) {
m_containsMouse = con;
emit containsMouseChanged(con);
}
}
return false;
}

31
src/GUI/HoverArea.hxx Normal file
View file

@ -0,0 +1,31 @@
#ifndef HOVERAREA_HXX
#define HOVERAREA_HXX
#include <QQuickItem>
class HoverArea : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(bool containsMouse READ containsMouse NOTIFY containsMouseChanged);
public:
HoverArea();
bool containsMouse() const
{
return m_containsMouse;
}
signals:
void containsMouseChanged(bool containsMouse);
protected:
bool eventFilter(QObject* sender, QEvent* event) override;
private:
bool m_containsMouse = false;
};
#endif // HOVERAREA_HXX

View file

@ -54,6 +54,7 @@
#include "ModelDataExtractor.hxx"
#include "CarriersLocationModel.hxx"
#include "SetupRootDialog.hxx"
#include "HoverArea.hxx"
using namespace simgear::pkg;
@ -92,6 +93,9 @@ LauncherController::LauncherController(QObject *parent, QWindow* window) :
m_aircraftSearchModel = new AircraftProxyModel(this, m_aircraftModel);
m_favouriteAircraftModel = new AircraftProxyModel(this, m_aircraftModel);
m_favouriteAircraftModel->setShowFavourites(true);
m_aircraftHistory = new RecentAircraftModel(m_aircraftModel, this);
connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted,
@ -167,6 +171,7 @@ void LauncherController::initQML()
qmlRegisterType<NavaidDiagram>("FlightGear", 1, 0, "NavaidDiagram");
qmlRegisterType<RouteDiagram>("FlightGear", 1, 0, "RouteDiagram");
qmlRegisterType<QmlRadioButtonGroup>("FlightGear", 1, 0, "RadioButtonGroup");
qmlRegisterType<HoverArea>("FlightGear", 1, 0, "HoverArea");
qmlRegisterType<ModelDataExtractor>("FlightGear", 1, 0, "ModelDataExtractor");

View file

@ -51,6 +51,7 @@ class LauncherController : public QObject
Q_PROPERTY(AircraftProxyModel* aircraftWithUpdatesModel MEMBER m_aircraftWithUpdatesModel CONSTANT)
Q_PROPERTY(AircraftProxyModel* browseAircraftModel MEMBER m_browseAircraftModel CONSTANT)
Q_PROPERTY(AircraftProxyModel* searchAircraftModel MEMBER m_aircraftSearchModel CONSTANT)
Q_PROPERTY(AircraftProxyModel* favouriteAircraftModel MEMBER m_favouriteAircraftModel CONSTANT)
Q_PROPERTY(AircraftItemModel* baseAircraftModel MEMBER m_aircraftModel CONSTANT)
@ -269,6 +270,8 @@ private:
AircraftProxyModel* m_aircraftSearchModel;
AircraftProxyModel* m_browseAircraftModel;
AircraftProxyModel* m_aircraftWithUpdatesModel;
AircraftProxyModel* m_favouriteAircraftModel;
MPServersModel* m_serversModel = nullptr;
LocationController* m_location = nullptr;
FlightPlanController* m_flightPlan = nullptr;

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

View file

@ -1,5 +1,7 @@
import QtQuick 2.4
import FlightGear.Launcher 1.0
import FlightGear 1.0
import "."
Item {
@ -77,19 +79,39 @@ Item {
spacing: Style.margin
AircraftVariantChoice {
id: titleBox
Item {
height: titleBox.height
width: parent.width
aircraft: model.uri;
currentIndex: model.activeVariant
onSelected: {
model.activeVariant = index
root.select(model.uri)
FavouriteToggleButton {
id: favourite
checked: model.favourite
anchors.verticalCenter: parent.verticalCenter
onToggle: {
model.favourite = on;
}
}
AircraftVariantChoice {
id: titleBox
anchors {
left: favourite.right
leftMargin: Style.margin
right: parent.right
}
aircraft: model.uri;
currentIndex: model.activeVariant
onSelected: {
model.activeVariant = index
root.select(model.uri)
}
}
}
StyledText {
id: description
width: parent.width

View file

@ -1,5 +1,6 @@
import QtQuick 2.4
import FlightGear.Launcher 1.0
import FlightGear 1.0
import "."
Item {
@ -87,10 +88,21 @@ Item {
}
}
MouseArea {
FavouriteToggleButton {
id: favourite
anchors {
left: parent.left
top: parent.top
margins: Style.margin
}
visible: hover.containsMouse || model.favourite
checked: model.favourite
onToggle: { model.favourite = on; }
}
HoverArea {
id: hover
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
}
} // of root item

View file

@ -50,6 +50,16 @@ FocusScope
active: root.state == "installed"
}
TabButton {
id: favouritesButton
text: qsTr("Favourites")
onClicked: {
root.state = "favourites"
root.updateSelectionFromLauncher();
}
active: root.state == "favourites"
}
TabButton {
id: browseButton
text: qsTr("Browse")
@ -231,7 +241,22 @@ FocusScope
PropertyChanges {
target: gridModeToggle; visible: false
}
},
State {
name: "favourites"
PropertyChanges {
target: root
__model: _launcher.favouriteAircraftModel
__header: emptyHeader
}
PropertyChanges {
target: gridModeToggle; visible: true
}
}
]
function showDetails(uri)

View file

@ -0,0 +1,32 @@
import QtQuick 2.4
import "."
Item {
id: root
property bool checked: false
implicitWidth: icon.width + Style.margin
implicitHeight: icon.height + Style.margin
signal toggle(var on);
Image {
id: icon
source: {
var b = mouse.containsMouse ? !root.checked : root.checked;
return b ? "qrc:///favourite-icon-filled" : "qrc:///favourite-icon-outline";
}
anchors.centerIn: parent
}
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
onClicked: root.toggle(!root.checked);
cursorShape: Qt.PointingHandCursor
}
}

View file

@ -132,6 +132,9 @@
<file>qml/GridToggleButton.qml</file>
<file>qml/EnableDisableButton.qml</file>
<file>qml/IconButton.qml</file>
<file>qml/FavouriteToggleButton.qml</file>
<file alias="favourite-icon-filled">assets/icons8-christmas-star-filled.png</file>
<file alias="favourite-icon-outline">assets/icons8-christmas-star-outline.png</file>
</qresource>
<qresource prefix="/preview">
<file alias="close-icon">preview-close.png</file>