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 PreviewImageItem.hxx
ThumbnailImageItem.cxx ThumbnailImageItem.cxx
ThumbnailImageItem.hxx ThumbnailImageItem.hxx
PopupWindowTracker.cxx
PopupWindowTracker.hxx
QmlPropertyModel.hxx QmlPropertyModel.hxx
QmlPropertyModel.cxx QmlPropertyModel.cxx
QmlPositioned.hxx QmlPositioned.hxx

View file

@ -44,7 +44,6 @@
#include "NavaidSearchModel.hxx" #include "NavaidSearchModel.hxx"
#include "PathUrlHelper.hxx" #include "PathUrlHelper.hxx"
#include "PixmapImageItem.hxx" #include "PixmapImageItem.hxx"
#include "PopupWindowTracker.hxx"
#include "PreviewImageItem.hxx" #include "PreviewImageItem.hxx"
#include "QmlAircraftInfo.hxx" #include "QmlAircraftInfo.hxx"
#include "QmlPositioned.hxx" #include "QmlPositioned.hxx"
@ -157,7 +156,6 @@ void LauncherController::initQML()
qmlRegisterType<FileDialogWrapper>("FlightGear", 1, 0, "FileDialog"); qmlRegisterType<FileDialogWrapper>("FlightGear", 1, 0, "FileDialog");
qmlRegisterType<QmlAircraftInfo>("FlightGear.Launcher", 1, 0, "AircraftInfo"); qmlRegisterType<QmlAircraftInfo>("FlightGear.Launcher", 1, 0, "AircraftInfo");
qmlRegisterType<PopupWindowTracker>("FlightGear.Launcher", 1, 0, "PopupWindowTracker");
qmlRegisterUncreatableType<LocalAircraftCache>("FlightGear.Launcher", 1, 0, "LocalAircraftCache", "Aircraft cache"); qmlRegisterUncreatableType<LocalAircraftCache>("FlightGear.Launcher", 1, 0, "LocalAircraftCache", "Aircraft cache");
qmlRegisterUncreatableType<AircraftItemModel>("FlightGear.Launcher", 1, 0, "AircraftModel", "Built-in model"); 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 2.4
import QtQuick.Window 2.0
import "." // -> forces the qmldir to be loaded import "." // -> forces the qmldir to be loaded
import FlightGear 1.0
import FlightGear.Launcher 1.0 import FlightGear.Launcher 1.0
@ -22,6 +21,9 @@ Rectangle {
property alias aircraft: aircraftInfo.uri property alias aircraft: aircraftInfo.uri
property alias currentIndex: aircraftInfo.variant property alias currentIndex: aircraftInfo.variant
property alias fontPixelSize: title.font.pixelSize property alias fontPixelSize: title.font.pixelSize
// allow users to control the font sizing
// (aircraft variants can have excessively long names)
property int popupFontPixelSize: 0 property int popupFontPixelSize: 0
// expose this to avoid a second AircraftInfo in the compact delegate // expose this to avoid a second AircraftInfo in the compact delegate
@ -30,17 +32,6 @@ Rectangle {
signal selected(var index) 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 { Row {
anchors.centerIn: parent anchors.centerIn: parent
height: title.implicitHeight height: title.implicitHeight
@ -85,16 +76,9 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onClicked: { onClicked: {
var screenPos = _launcher.mapToGlobal(title, Qt.point(0, -title.height * currentIndex)) var hCenter = root.width / 2;
if (screenPos.y < 0) { OverlayShared.globalOverlay.showOverlayAtItemOffset(popup, root,
// if the popup would appear off the screen, use the first item Qt.point(hCenter, root.height))
// 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();
} }
} }
@ -103,67 +87,17 @@ Rectangle {
id: aircraftInfo id: aircraftInfo
} }
PopupWindowTracker {
id: tracker
}
Component { Component {
id: popup id: popup
VariantMenu {
id: menu
aircraftUri: aircraftInfo.uri
popupFontPixelSize: root.popupFontPixelSize
Window { onSelect: {
id: popupFrame aircraftInfo.variant = index;
root.selected(index)
width: root.width
flags: Qt.Popup
height: choicesColumn.childrenRect.height
color: "white"
Rectangle {
border.width: 1
border.color: Style.minorFrameColor
anchors.fill: parent
}
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 } // of popup component
} }

View file

@ -1,6 +1,6 @@
import QtQuick 2.4 import QtQuick 2.4
import QtQuick.Window 2.0
import FlightGear.Launcher 1.0 import FlightGear.Launcher 1.0
import FlightGear 1.0
import "." import "."
@ -37,68 +37,18 @@ Item {
hoverEnabled: root.enabled hoverEnabled: root.enabled
enabled: root.enabled enabled: root.enabled
onClicked: { onClicked: {
var pop = popup.createObject(root) OverlayShared.globalOverlay.showOverlayAtItemOffset(menu, root,
var screenPos = _launcher.mapToGlobal(button, Qt.point(-pop.width, 0)) Qt.point(root.width, root.height))
pop.y = screenPos.y;
pop.x = screenPos.x;
tracker.window = pop;
pop.show();
} }
} }
} }
PopupWindowTracker {
id: tracker
}
Component { Component {
id: popup id: menu
OverlayMenu {
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 model: root.model
delegate: onSelect: root.selected(index)
StyledText { alignRightEdge: true
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
}
} }

View file

@ -1,6 +1,5 @@
import QtQuick 2.4 import QtQuick 2.4
import FlightGear 1.0 import FlightGear 1.0
import QtQuick.Window 2.0
import FlightGear.Launcher 1.0 import FlightGear.Launcher 1.0
import "." import "."
@ -89,10 +88,7 @@ FocusScope {
units.selectedIndex = unitIndex; units.selectedIndex = unitIndex;
var newQuantity = q.convertToUnit(units.selectedUnit) var newQuantity = q.convertToUnit(units.selectedUnit)
console.info("Changing text to:" + newQuantity.value.toFixed(units.numDecimals));
edit.text = newQuantity.value.toFixed(units.numDecimals) edit.text = newQuantity.value.toFixed(units.numDecimals)
console.info("Change units commit:" + newQuantity.value)
commit(newQuantity); commit(newQuantity);
} else { } else {
// not focused, easy // not focused, easy
@ -103,11 +99,8 @@ FocusScope {
function showUnitsMenu() function showUnitsMenu()
{ {
OverlayShared.globalOverlay.showOverlayAtItemOffset(menu, root,
var screenPos = _launcher.mapToGlobal(editFrame, Qt.point(0, editFrame.height)) Qt.point(editFrame.x, editFrame.height))
var pop = popup.createObject(root, {x:screenPos.x, y:screenPos.y })
tracker.window = pop;
pop.show();
} }
Component.onCompleted: { Component.onCompleted: {
@ -329,73 +322,14 @@ FocusScope {
} }
} // of frame rectangle } // of frame rectangle
PopupWindowTracker {
id: tracker
}
Component { Component {
id: popup id: menu
Window {
id: unitSelectionPopup
flags: Qt.Popup
color: "white"
height: choicesColumn.childrenRect.height + Style.margin * 2
width: choicesColumn.width + Style.margin * 2
Rectangle { OverlayMenu {
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 model: units
delegate: displayRole: "longName"
Text { currentIndex: units.selectedIndex
id: choiceText onSelect: root.changeToUnits(index);
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
}
} // of popup component
} }

View file

@ -6,19 +6,25 @@ Item {
readonly property bool dismissOnClickOutside: activeOverlayLoader.item && activeOverlayLoader.item.hasOwnProperty("dismissOnClickOutside") ? readonly property bool dismissOnClickOutside: activeOverlayLoader.item && activeOverlayLoader.item.hasOwnProperty("dismissOnClickOutside") ?
activeOverlayLoader.item.dismissOnClickOutside : false activeOverlayLoader.item.dismissOnClickOutside : false
property var __showPos: Qt.point(0,0)
function showOverlay(comp) function showOverlay(comp)
{ {
__showPos = Qt.point(0,0)
activeOverlayLoader.sourceComponent = comp; activeOverlayLoader.sourceComponent = comp;
activeOverlayLoader.visible = true; activeOverlayLoader.visible = true;
activeOverlayLoader.x = __showPos.x;
activeOverlayLoader.y = __showPos.y;
} }
function showOverlayAtItemOffset(comp, item, offset) function showOverlayAtItemOffset(comp, item, offset)
{ {
var pt = mapFromItem(item, offset.x, offset.y) __showPos = mapFromItem(item, offset.x, offset.y)
activeOverlayLoader.sourceComponent = comp; activeOverlayLoader.sourceComponent = comp;
activeOverlayLoader.visible = true; activeOverlayLoader.visible = true;
activeOverlayLoader.x = pt.x; activeOverlayLoader.x = __showPos.x;
activeOverlayLoader.y = pt.y; activeOverlayLoader.y = __showPos.y;
} }
function dismissOverlay() function dismissOverlay()
@ -27,6 +33,14 @@ Item {
activeOverlayLoader.visible = false; 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 { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
@ -38,29 +52,5 @@ Item {
{ {
id: activeOverlayLoader id: activeOverlayLoader
// no size, size to the component // 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 int currentIndex: 0
property string headerText: "" property string headerText: ""
property int maximumPermittedHeight: 450 property int maximumPermittedHeight: 450
property string displayRole: "display"
color: "white" color: "white"
border.width: 1 border.width: 1
@ -19,7 +20,7 @@ Rectangle {
// Overlay control properties // Overlay control properties
property bool dismissOnClickOutside: true property bool dismissOnClickOutside: true
property bool adjustYPosition: true property bool alignRightEdge: false
signal select(var index); signal select(var index);
@ -27,6 +28,9 @@ Rectangle {
computeMenuWidth(); computeMenuWidth();
} }
y: -OverlayShared.globalOverlay.verticalOverflowFor(height)
x: alignRightEdge ? -width : 0
function close() function close()
{ {
OverlayShared.globalOverlay.dismissOverlay() 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/EnableDisableButton.qml</file>
<file>qml/IconButton.qml</file> <file>qml/IconButton.qml</file>
<file>qml/FavouriteToggleButton.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-filled">assets/icons8-christmas-star-filled.png</file>
<file alias="favourite-icon-outline">assets/icons8-christmas-star-outline.png</file> <file alias="favourite-icon-outline">assets/icons8-christmas-star-outline.png</file>
</qresource> </qresource>