1
0
Fork 0

Remove use of QQ.Window / PopupTracker

Overlay menus work better than native popups for most use-cases, and
simplify integration (no window focus changes). Switch the remaining
menus to always use the overlay system

As part of this, fix how Overlay positions are adjusted, to avoid
the ugly zero-interval timer.
This commit is contained in:
James Turner 2020-06-09 16:34:23 +01:00
parent aedaf798c0
commit bce6b56c8d
11 changed files with 153 additions and 353 deletions

View file

@ -161,8 +161,6 @@ if (HAVE_QT)
PreviewImageItem.hxx
ThumbnailImageItem.cxx
ThumbnailImageItem.hxx
PopupWindowTracker.cxx
PopupWindowTracker.hxx
QmlPropertyModel.hxx
QmlPropertyModel.cxx
QmlPositioned.hxx

View file

@ -44,7 +44,6 @@
#include "NavaidSearchModel.hxx"
#include "PathUrlHelper.hxx"
#include "PixmapImageItem.hxx"
#include "PopupWindowTracker.hxx"
#include "PreviewImageItem.hxx"
#include "QmlAircraftInfo.hxx"
#include "QmlPositioned.hxx"
@ -157,7 +156,6 @@ void LauncherController::initQML()
qmlRegisterType<FileDialogWrapper>("FlightGear", 1, 0, "FileDialog");
qmlRegisterType<QmlAircraftInfo>("FlightGear.Launcher", 1, 0, "AircraftInfo");
qmlRegisterType<PopupWindowTracker>("FlightGear.Launcher", 1, 0, "PopupWindowTracker");
qmlRegisterUncreatableType<LocalAircraftCache>("FlightGear.Launcher", 1, 0, "LocalAircraftCache", "Aircraft cache");
qmlRegisterUncreatableType<AircraftItemModel>("FlightGear.Launcher", 1, 0, "AircraftModel", "Built-in model");

View file

@ -1,67 +0,0 @@
#include "PopupWindowTracker.hxx"
#include <QGuiApplication>
#include <QMouseEvent>
#include <QWindow>
#include <QDebug>
PopupWindowTracker::PopupWindowTracker(QObject *parent) : QObject(parent)
{
connect(qGuiApp, &QGuiApplication::applicationStateChanged,
this, &PopupWindowTracker::onApplicationStateChanged);
}
PopupWindowTracker::~PopupWindowTracker()
{
if (m_window) {
qApp->removeEventFilter(this);
}
}
void PopupWindowTracker::setWindow(QWindow *window)
{
if (m_window == window)
return;
if (m_window) {
qApp->removeEventFilter(this);
}
m_window = window;
if (m_window) {
qApp->installEventFilter(this);
}
emit windowChanged(m_window);
}
void PopupWindowTracker::onApplicationStateChanged(Qt::ApplicationState as)
{
if (m_window && (as != Qt::ApplicationActive)) {
m_window->close();
setWindow(nullptr);
}
}
bool PopupWindowTracker::eventFilter(QObject *watched, QEvent *event)
{
if (!m_window)
return false;
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
QRect globalGeometry(m_window->mapToGlobal(QPoint(0,0)), m_window->size());
if (globalGeometry.contains(me->globalPos())) {
// click inside the window, process as normal fall through
} else {
m_window->close();
setWindow(nullptr);
// still fall through
}
}
return false;
}

View file

@ -1,38 +0,0 @@
#ifndef POPUPWINDOWTRACKER_HXX
#define POPUPWINDOWTRACKER_HXX
#include <QObject>
class QWindow;
class PopupWindowTracker : public QObject
{
Q_OBJECT
Q_PROPERTY(QWindow* window READ window WRITE setWindow NOTIFY windowChanged)
QWindow* m_window = nullptr;
public:
explicit PopupWindowTracker(QObject *parent = nullptr);
~PopupWindowTracker();
QWindow* window() const
{
return m_window;
}
signals:
void windowChanged(QWindow* window);
public slots:
void setWindow(QWindow* window);
void onApplicationStateChanged(Qt::ApplicationState as);
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
};
#endif // POPUPWINDOWTRACKER_HXX

View file

@ -1,7 +1,6 @@
import QtQuick 2.4
import QtQuick.Window 2.0
import "." // -> forces the qmldir to be loaded
import FlightGear 1.0
import FlightGear.Launcher 1.0
@ -22,6 +21,9 @@ Rectangle {
property alias aircraft: aircraftInfo.uri
property alias currentIndex: aircraftInfo.variant
property alias fontPixelSize: title.font.pixelSize
// allow users to control the font sizing
// (aircraft variants can have excessively long names)
property int popupFontPixelSize: 0
// expose this to avoid a second AircraftInfo in the compact delegate
@ -30,17 +32,6 @@ Rectangle {
signal selected(var index)
// Image {
// id: upDownIcon
// source: "qrc:///up-down-arrow"
// x: root.centerX + Math.min(title.implicitWidth * 0.5, title.width * 0.5)
// anchors.verticalCenter: parent.verticalCenter
// visible: __enabled
// }
Row {
anchors.centerIn: parent
height: title.implicitHeight
@ -85,16 +76,9 @@ Rectangle {
anchors.fill: parent
hoverEnabled: true
onClicked: {
var screenPos = _launcher.mapToGlobal(title, Qt.point(0, -title.height * currentIndex))
if (screenPos.y < 0) {
// if the popup would appear off the screen, use the first item
// position instead
screenPos = _launcher.mapToGlobal(title, Qt.point(0, 0))
}
var pop = popup.createObject(root, {x:screenPos.x, y:screenPos.y })
tracker.window = pop;
pop.show();
var hCenter = root.width / 2;
OverlayShared.globalOverlay.showOverlayAtItemOffset(popup, root,
Qt.point(hCenter, root.height))
}
}
@ -103,67 +87,17 @@ Rectangle {
id: aircraftInfo
}
PopupWindowTracker {
id: tracker
}
Component {
id: popup
Window {
id: popupFrame
width: root.width
flags: Qt.Popup
height: choicesColumn.childrenRect.height
color: "white"
Rectangle {
border.width: 1
border.color: Style.minorFrameColor
anchors.fill: parent
VariantMenu {
id: menu
aircraftUri: aircraftInfo.uri
popupFontPixelSize: root.popupFontPixelSize
onSelect: {
aircraftInfo.variant = index;
root.selected(index)
}
Column {
id: choicesColumn
Repeater {
// would prefer the model to be conditional on visiblity,
// but this trips up the Window sizing on Linux (Ubuntu at
// least) and we get a mis-aligned origin
model: aircraftInfo.variantNames
delegate: Item {
width: popupFrame.width
height: choiceText.implicitHeight
Text {
id: choiceText
text: modelData
// allow override the size in case the title size is enormous
font.pixelSize: (root.popupFontPixelSize > 0) ? root.popupFontPixelSize : title.font.pixelSize
color: choiceArea.containsMouse ? Style.themeColor : Style.baseTextColor
anchors {
left: parent.left
right: parent.right
margins: 4
}
}
MouseArea {
id: choiceArea
hoverEnabled: true
anchors.fill: parent
onClicked: {
popupFrame.close()
root.selected(model.index)
}
}
} // of delegate Item
} // of Repeater
}
} // of popup frame
}
} // of popup component
}

View file

@ -1,6 +1,6 @@
import QtQuick 2.4
import QtQuick.Window 2.0
import FlightGear.Launcher 1.0
import FlightGear 1.0
import "."
@ -37,68 +37,18 @@ Item {
hoverEnabled: root.enabled
enabled: root.enabled
onClicked: {
var pop = popup.createObject(root)
var screenPos = _launcher.mapToGlobal(button, Qt.point(-pop.width, 0))
pop.y = screenPos.y;
pop.x = screenPos.x;
tracker.window = pop;
pop.show();
OverlayShared.globalOverlay.showOverlayAtItemOffset(menu, root,
Qt.point(root.width, root.height))
}
}
}
PopupWindowTracker {
id: tracker
}
Component {
id: popup
Window {
id: popupFrame
flags: Qt.Popup
height: choicesColumn.childrenRect.height + Style.margin * 2
width: choicesColumn.childrenRect.width + Style.margin * 2
color: "white"
Rectangle {
border.width: 1
border.color: Style.minorFrameColor
anchors.fill: parent
}
// text repeater
Column {
id: choicesColumn
spacing: Style.margin
x: Style.margin
y: Style.margin
Repeater {
id: choicesRepeater
model: root.model
delegate:
StyledText {
id: choiceText
// Taken from TableViewItemDelegateLoader.qml to follow QML role conventions
text: model && model.hasOwnProperty(displayRole) ? model[displayRole] // Qml ListModel and QAbstractItemModel
: modelData && modelData.hasOwnProperty(displayRole) ? modelData[displayRole] // QObjectList / QObject
: modelData != undefined ? modelData : "" // Models without role
height: implicitHeight + Style.margin
MouseArea {
width: popupFrame.width // full width of the popup
height: parent.height
onClicked: {
root.selected(model.index);
popupFrame.close()
}
}
} // of Text delegate
} // text repeater
} // text column
} // of popup Window
id: menu
OverlayMenu {
model: root.model
onSelect: root.selected(index)
alignRightEdge: true
}
}
}

View file

@ -1,6 +1,5 @@
import QtQuick 2.4
import FlightGear 1.0
import QtQuick.Window 2.0
import FlightGear.Launcher 1.0
import "."
@ -89,10 +88,7 @@ FocusScope {
units.selectedIndex = unitIndex;
var newQuantity = q.convertToUnit(units.selectedUnit)
console.info("Changing text to:" + newQuantity.value.toFixed(units.numDecimals));
edit.text = newQuantity.value.toFixed(units.numDecimals)
console.info("Change units commit:" + newQuantity.value)
commit(newQuantity);
} else {
// not focused, easy
@ -103,11 +99,8 @@ FocusScope {
function showUnitsMenu()
{
var screenPos = _launcher.mapToGlobal(editFrame, Qt.point(0, editFrame.height))
var pop = popup.createObject(root, {x:screenPos.x, y:screenPos.y })
tracker.window = pop;
pop.show();
OverlayShared.globalOverlay.showOverlayAtItemOffset(menu, root,
Qt.point(editFrame.x, editFrame.height))
}
Component.onCompleted: {
@ -329,73 +322,14 @@ FocusScope {
}
} // of frame rectangle
PopupWindowTracker {
id: tracker
}
Component {
id: popup
Window {
id: unitSelectionPopup
flags: Qt.Popup
color: "white"
height: choicesColumn.childrenRect.height + Style.margin * 2
width: choicesColumn.width + Style.margin * 2
id: menu
Rectangle {
border.width: 1
border.color: Style.minorFrameColor
anchors.fill: parent
}
// choice layout column
Column {
id: choicesColumn
spacing: Style.margin
x: Style.margin
y: Style.margin
width: menuWidth
function calculateMenuWidth()
{
var minWidth = 0;
for (var i = 0; i < choicesRepeater.count; i++) {
minWidth = Math.max(minWidth, choicesRepeater.itemAt(i).implicitWidth);
}
return minWidth;
}
readonly property int menuWidth: calculateMenuWidth()
// main item repeater
Repeater {
id: choicesRepeater
model: units
delegate:
Text {
id: choiceText
readonly property bool selected: units.selectedIndex === model.index
text: model.longName
height: implicitHeight + Style.margin
font.pixelSize: Style.baseFontPixelSize
color: choiceArea.containsMouse ? Style.themeColor : Style.baseTextColor
MouseArea {
id: choiceArea
width: unitSelectionPopup.width // full width of the popup
height: parent.height
hoverEnabled: true
onClicked: {
root.changeToUnits(model.index);
unitSelectionPopup.close();
}
}
} // of Text delegate
} // text repeater
} // text column
OverlayMenu {
model: units
displayRole: "longName"
currentIndex: units.selectedIndex
onSelect: root.changeToUnits(index);
}
} // of popup component
}
}

View file

@ -6,19 +6,25 @@ Item {
readonly property bool dismissOnClickOutside: activeOverlayLoader.item && activeOverlayLoader.item.hasOwnProperty("dismissOnClickOutside") ?
activeOverlayLoader.item.dismissOnClickOutside : false
property var __showPos: Qt.point(0,0)
function showOverlay(comp)
{
__showPos = Qt.point(0,0)
activeOverlayLoader.sourceComponent = comp;
activeOverlayLoader.visible = true;
activeOverlayLoader.x = __showPos.x;
activeOverlayLoader.y = __showPos.y;
}
function showOverlayAtItemOffset(comp, item, offset)
{
var pt = mapFromItem(item, offset.x, offset.y)
__showPos = mapFromItem(item, offset.x, offset.y)
activeOverlayLoader.sourceComponent = comp;
activeOverlayLoader.visible = true;
activeOverlayLoader.x = pt.x;
activeOverlayLoader.y = pt.y;
activeOverlayLoader.x = __showPos.x;
activeOverlayLoader.y = __showPos.y;
}
function dismissOverlay()
@ -27,6 +33,14 @@ Item {
activeOverlayLoader.visible = false;
}
function verticalOverflowFor(itemHeight)
{
// assumes we fill the whole window, so anything
// extending past our bottom is overflowing
var ov = (itemHeight + activeOverlayLoader.y) - root.height;
return Math.max(ov, 0);
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
@ -38,29 +52,5 @@ Item {
{
id: activeOverlayLoader
// no size, size to the component
onStatusChanged: {
if (status == Loader.Ready) {
if (item.hasOwnProperty("adjustYPosition")) {
// setting position here doesn't work, so we use a 0-interval
// timer to do it shortly afterwards
adjustPosition.start();
}
}
}
function adjustY()
{
var overflowHeight = (y + item.height) - root.height;
if (overflowHeight > 0) {
activeOverlayLoader.y = Math.max(0, y - overflowHeight)
}
}
Timer {
id: adjustPosition
interval: 0
onTriggered: activeOverlayLoader.adjustY();
}
}
}

View file

@ -9,6 +9,7 @@ Rectangle {
property int currentIndex: 0
property string headerText: ""
property int maximumPermittedHeight: 450
property string displayRole: "display"
color: "white"
border.width: 1
@ -19,7 +20,7 @@ Rectangle {
// Overlay control properties
property bool dismissOnClickOutside: true
property bool adjustYPosition: true
property bool alignRightEdge: false
signal select(var index);
@ -27,6 +28,9 @@ Rectangle {
computeMenuWidth();
}
y: -OverlayShared.globalOverlay.verticalOverflowFor(height)
x: alignRightEdge ? -width : 0
function close()
{
OverlayShared.globalOverlay.dismissOverlay()

View file

@ -0,0 +1,96 @@
import QtQuick 2.4
import FlightGear 1.0
import FlightGear.Launcher 1.0
import "."
Rectangle {
id: root
property alias currentIndex: aircraft.variant
property alias aircraftUri: aircraft.uri
AircraftInfo {
id: aircraft
}
color: "white"
border.width: 1
border.color: Style.minorFrameColor
width: itemsColumn.width
height: itemsColumn.height
clip: true
property int popupFontPixelSize: 0
// Overlay control properties
property bool dismissOnClickOutside: true
signal select(var index);
y: -OverlayShared.globalOverlay.verticalOverflowFor(height)
x: -width / 2 // align horizontal center
Component.onCompleted: {
computeMenuWidth();
}
function close()
{
OverlayShared.globalOverlay.dismissOverlay()
}
function doSelect(index)
{
select(index);
close();
}
function computeMenuWidth()
{
var minWidth = 0;
for (var i = 0; i < choicesRepeater.count; i++) {
minWidth = Math.max(minWidth, choicesRepeater.itemAt(i).implicitWidth);
}
itemsColumn.width = minWidth
}
Column {
id: itemsColumn
spacing: Style.margin
Repeater {
id: choicesRepeater
model: aircraft.variantNames
delegate: Item {
width: itemsColumn.width
height: choiceText.implicitHeight
implicitWidth: choiceText.implicitWidth + (Style.margin * 2)
Text {
id: choiceText
text: modelData
x: Style.margin
width: parent.width - (Style.margin * 2)
// allow override the size in case the title size is enormous
font.pixelSize: (root.popupFontPixelSize > 0) ? root.popupFontPixelSize
: Style.baseFontPixelSize * 2
color: choiceArea.containsMouse ? Style.themeColor : Style.baseTextColor
}
MouseArea {
id: choiceArea
hoverEnabled: true
anchors.fill: parent
onClicked: {
root.doSelect(model.index)
}
}
} // of delegate Item
} // menu item repeater
} // of menu contents column
}

View file

@ -132,6 +132,7 @@
<file>qml/EnableDisableButton.qml</file>
<file>qml/IconButton.qml</file>
<file>qml/FavouriteToggleButton.qml</file>
<file>qml/VariantMenu.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>