Show notifications on all screens

- Fix windows flags for DesktopPopup
- Merge notification creation and show macros
- Store height offset for each screens (key: screen name)
- Create a Visual Root Window for each notifications
- Put each Visual Root Windows behind its notification to get visually only one entity
- Bind each Visual Root Window to a Screen
- Propagate events between visual objects in order to use only one
- Add open/close events
- Add missing anchors
- Avoid using window as a keyword
This commit is contained in:
Julien Wadel 2020-05-08 23:34:42 +02:00
parent e412a0466b
commit ca500317ca
5 changed files with 97 additions and 109 deletions

View file

@ -21,6 +21,8 @@
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include <QQuickWindow>
#include <QQuickItem>
#include <QQuickView>
#include <QScreen>
#include <QTimer>
@ -115,49 +117,61 @@ Notifier::~Notifier () {
// -----------------------------------------------------------------------------
QObject *Notifier::createNotification (Notifier::NotificationType type) {
mMutex->lock();
QObject *Notifier::createNotification (Notifier::NotificationType type, QVariantMap data) {
QQuickItem *wrapperItem = nullptr;
mMutex->lock();
Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber);
if (mInstancesNumber == MaxNotificationsNumber) { // Check existing instances.
qWarning() << QStringLiteral("Unable to create another notification.");
mMutex->unlock();
return nullptr;
}
QList<QScreen *> allScreens = QGuiApplication::screens();
if(allScreens.size() > 0){ // Ensure to have a screen to avoid errors
Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber);
QQuickItem * previousWrapper = nullptr;
++mInstancesNumber;
for(int i = 0 ; i < allScreens.size() ; ++i){
QQuickView *view = new QQuickView(App::getInstance()->getEngine(), nullptr); // Use QQuickView to create a visual root object that is independant from current application Window
QScreen *screen = allScreens[i];
// Check existing instances.
if (mInstancesNumber == MaxNotificationsNumber) {
qWarning() << QStringLiteral("Unable to create another notification.");
mMutex->unlock();
return nullptr;
}
view->setScreen(screen); // Bind the visual root object to the screen
view->setProperty("flags", QVariant(Qt::BypassWindowManagerHint | Qt::WindowStaysOnBottomHint | Qt::CustomizeWindowHint | Qt::X11BypassWindowManagerHint)); // Set the visual ghost window
view->setSource(QString(NotificationsPath)+Notifier::Notifications[type].filename);
QQuickWindow *subWindow = view->findChild<QQuickWindow *>("__internalWindow");
QObject::connect(subWindow, &QObject::destroyed, view, &QObject::deleteLater); // When destroying window, detroy visual root object too
// Create instance and set attributes.
QObject *instance = mComponents[type]->create();
qInfo() << QStringLiteral("Create notification:") << instance;
int * screenHeightOffset = &mScreenHeightOffset[screen->name()]; // Access optimization
QRect availableGeometry = screen->availableGeometry();
int heightOffset = availableGeometry.y() + screen->devicePixelRatio()*(availableGeometry.height() - subWindow->height());
mInstancesNumber++;
subWindow->setX(availableGeometry.x()+availableGeometry.width()*screen->devicePixelRatio()-subWindow->property("width").toInt()*screen->devicePixelRatio());
subWindow->setY(heightOffset-(*screenHeightOffset % heightOffset));
{
QQuickWindow *window = instance->findChild<QQuickWindow *>(NotificationPropertyWindow);
Q_CHECK_PTR(window);
*screenHeightOffset = (subWindow->height() + *screenHeightOffset) + NotificationSpacing;
if (*screenHeightOffset - heightOffset + availableGeometry.y() >= 0)
*screenHeightOffset = 0;
QScreen *screen = window->screen();
Q_CHECK_PTR(screen);
// if(primaryScreen != screen){ //Useful when doing manual scaling jobs. Need to implement scaler in GUI objects
// //subwindow->setProperty("xScale", (double)screen->availableVirtualGeometry().width()/availableGeometry.width() );
// //subwindow->setProperty("yScale", (double)screen->availableVirtualGeometry().height()/availableGeometry.height());
// }
wrapperItem = view->findChild<QQuickItem *>("__internalWrapper");
::setProperty(*wrapperItem, NotificationPropertyData,data);
view->setGeometry(subWindow->geometry()); // Ensure to have sufficient space to both let painter do job without error, and stay behind popup
QRect geometry = screen->availableGeometry();
// Set X/Y. (Not Pokémon games.)
int windowHeight = window->height();
int offset = geometry.y() + geometry.height() - windowHeight;
::setProperty(*instance, NotificationPropertyX, geometry.x() + geometry.width() - window->width());
::setProperty(*instance, NotificationPropertyY, offset - (mOffset % offset));
// Update offset.
mOffset = (windowHeight + mOffset) + NotificationSpacing;
if (mOffset - offset + geometry.y() >= 0)
mOffset = 0;
}
mMutex->unlock();
return instance;
if(previousWrapper!=nullptr){ // Link objects in order to propagate events without having to store them
QObject::connect(wrapperItem, SIGNAL(isOpened()), previousWrapper,SLOT(open()));
QObject::connect(wrapperItem, SIGNAL(isClosed()), previousWrapper,SLOT(close()));
QObject::connect(wrapperItem, &QObject::destroyed, previousWrapper, &QObject::deleteLater);
}
previousWrapper = wrapperItem; // The last one is used as a point of start when deleting and openning
view->show();
}
qInfo() << QStringLiteral("Create notifications:") << wrapperItem;
}
mMutex->unlock();
return wrapperItem;
}
// -----------------------------------------------------------------------------
@ -204,7 +218,7 @@ void Notifier::deleteNotification (QVariant notification) {
Q_ASSERT(mInstancesNumber >= 0);
if (mInstancesNumber == 0)
mOffset = 0;
mScreenHeightOffset.clear();
mMutex->unlock();
@ -213,14 +227,11 @@ void Notifier::deleteNotification (QVariant notification) {
// =============================================================================
#define CREATE_NOTIFICATION(TYPE) \
QObject * notification = createNotification(TYPE); \
#define CREATE_NOTIFICATION(TYPE, DATA) \
QObject * notification = createNotification(TYPE, DATA); \
if (!notification) \
return; \
const int timeout = Notifications[TYPE].timeout * 1000;
#define SHOW_NOTIFICATION(DATA) \
::setProperty(*notification, NotificationPropertyData, DATA); \
const int timeout = Notifications[TYPE].timeout * 1000; \
showNotification(notification, timeout);
// -----------------------------------------------------------------------------
@ -228,8 +239,6 @@ void Notifier::deleteNotification (QVariant notification) {
// -----------------------------------------------------------------------------
void Notifier::notifyReceivedMessage (const shared_ptr<linphone::ChatMessage> &message) {
CREATE_NOTIFICATION(Notifier::ReceivedMessage)
QVariantMap map;
map["message"] = message->getFileTransferInformation()
? tr("newFileMessage")
@ -239,62 +248,46 @@ void Notifier::notifyReceivedMessage (const shared_ptr<linphone::ChatMessage> &m
map["peerAddress"] = Utils::coreStringToAppString(chatRoom->getPeerAddress()->asStringUriOnly());
map["localAddress"] = Utils::coreStringToAppString(chatRoom->getLocalAddress()->asStringUriOnly());
map["window"].setValue(App::getInstance()->getMainWindow());
SHOW_NOTIFICATION(map)
CREATE_NOTIFICATION(Notifier::ReceivedMessage, map)
}
void Notifier::notifyReceivedFileMessage (const shared_ptr<linphone::ChatMessage> &message) {
CREATE_NOTIFICATION(Notifier::ReceivedFileMessage)
QVariantMap map;
map["fileUri"] = Utils::coreStringToAppString(message->getFileTransferInformation()->getFilePath());
map["fileSize"] = quint64(message->getFileTransferInformation()->getSize() +message->getFileTransferInformation()->getFileSize());
SHOW_NOTIFICATION(map)
CREATE_NOTIFICATION(Notifier::ReceivedFileMessage, map)
}
void Notifier::notifyReceivedCall (const shared_ptr<linphone::Call> &call) {
CREATE_NOTIFICATION(Notifier::ReceivedCall)
CallModel *callModel = &call->getData<CallModel>("call-model");
QVariantMap map;
map["call"].setValue(callModel);
CREATE_NOTIFICATION(Notifier::ReceivedCall, map)
QObject::connect(callModel, &CallModel::statusChanged, notification, [this, notification](CallModel::CallStatus status) {
if (status == CallModel::CallStatusEnded || status == CallModel::CallStatusConnected)
deleteNotification(QVariant::fromValue(notification));
});
QVariantMap map;
map["call"].setValue(callModel);
SHOW_NOTIFICATION(map)
}
void Notifier::notifyNewVersionAvailable (const QString &version, const QString &url) {
CREATE_NOTIFICATION(Notifier::NewVersionAvailable)
QVariantMap map;
map["message"] = tr("newVersionAvailable").arg(version);
map["url"] = url;
SHOW_NOTIFICATION(map)
CREATE_NOTIFICATION(Notifier::NewVersionAvailable, map)
}
void Notifier::notifySnapshotWasTaken (const QString &filePath) {
CREATE_NOTIFICATION(Notifier::SnapshotWasTaken)
QVariantMap map;
map["filePath"] = filePath;
SHOW_NOTIFICATION(map)
CREATE_NOTIFICATION(Notifier::SnapshotWasTaken, map)
}
void Notifier::notifyRecordingCompleted (const QString &filePath) {
CREATE_NOTIFICATION(Notifier::RecordingCompleted)
QVariantMap map;
map["filePath"] = filePath;
SHOW_NOTIFICATION(map)
CREATE_NOTIFICATION(Notifier::RecordingCompleted, map)
}
#undef SHOW_NOTIFICATION

View file

@ -24,6 +24,7 @@
#include <memory>
#include <QObject>
#include <QHash>
// =============================================================================
@ -72,10 +73,10 @@ private:
int timeout;
};
QObject *createNotification (NotificationType type);
QObject *createNotification (NotificationType type, QVariantMap data);
void showNotification (QObject *notification, int timeout);
int mOffset = 0;
QHash<QString,int> mScreenHeightOffset;
int mInstancesNumber = 0;
QMutex *mMutex = nullptr;

View file

@ -1,5 +1,9 @@
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Window 2.12
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Qt.labs.platform 1.0
import Common.Styles 1.0
@ -7,56 +11,57 @@ import Common.Styles 1.0
Item {
id: wrapper
objectName: '__internalWrapper'
// ---------------------------------------------------------------------------
property alias popupX: window.x
property alias popupY: window.y
property alias popupX: windowId.x
property alias popupY: windowId.y
property bool requestActivate: false
property int flags: Qt.SplashScreen
readonly property alias popupWidth: window.width
readonly property alias popupHeight: window.height
readonly property alias popupWidth: windowId.width
readonly property alias popupHeight: windowId.height
default property alias _content: content.data
property bool _isOpen: false
signal isOpened()
signal isClosed()
signal dataChanged()
on_ContentChanged: dataChanged(_content)
// ---------------------------------------------------------------------------
function open () {
_isOpen = true
_isOpen = true;
isOpened();
}
function close () {
_isOpen = false
isClosed()
}
// ---------------------------------------------------------------------------
// DO NOT TOUCH THESE PROPERTIES.
// No visible.
visible: false
// No size, no position.
height: 0
width: 0
x: 0
y: 0
visible:true
Window {
id: window
// Used for internal purposes only. Like Notifications.
id: windowId
objectName: '__internalWindow'
flags: wrapper.flags
opacity: 0
property bool isFrameLess : false;
// Don't use Popup for flags : it could lead to error in geometry
flags: Qt.BypassWindowManagerHint | Qt.WindowStaysOnTopHint | Qt.Window | Qt.FramelessWindowHint;
opacity: 1.0
height: _content[0] != null ? _content[0].height : 0
width: _content[0] != null ? _content[0].width : 0
visible:true
Item {
id: content
anchors.fill:parent
property var $parent: wrapper
}
@ -65,37 +70,32 @@ Item {
// ---------------------------------------------------------------------------
states: State {
name: 'opened'
name: 'opening'
when: _isOpen
PropertyChanges {
opacity: 1.0
target: window
target: windowId
}
}
transitions: [
Transition {
from: ''
to: 'opened'
to: 'opening'
ScriptAction {
script: {
window.showNormal()
if (wrapper.requestActivate) {
window.requestActivate()
windowId.requestActivate()
}
}
}
},
Transition {
from: 'opened'
from: '*'
to: ''
ScriptAction {
script: window.hide()
script: windowId.hide()
}
}
]

View file

@ -23,11 +23,6 @@ DesktopPopup {
deleteNotification(notification)
}
flags: {
return (Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) |
(Qt.platform.os === 'osx' ? Qt.Window : Qt.Popup)
}
Rectangle {
color: NotificationStyle.color
height: overrodeHeight || NotificationStyle.height

View file

@ -29,7 +29,7 @@ Notification {
}
sourceComponent: ColumnLayout {
spacing: NotificationReceivedCallStyle.spacing
spacing: NotificationReceivedCallStyle.spacing
Contact {
Layout.fillWidth: true
@ -77,7 +77,6 @@ Notification {
ActionButton {
icon: 'hangup'
onClicked: notification._close(notification.call.terminate)
}
}