Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add call notification dialog. #4426

Merged
merged 1 commit into from
Apr 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@
<file>src/gui/tray/ActivityItemActions.qml</file>
<file>src/gui/tray/ActivityItemContent.qml</file>
<file>src/gui/tray/TalkReplyTextField.qml</file>
<file>src/gui/tray/CallNotificationDialog.qml</file>
</qresource>
</RCC>
13 changes: 13 additions & 0 deletions src/gui/generalsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ GeneralSettings::GeneralSettings(QWidget *parent)
this, &GeneralSettings::slotToggleOptionalServerNotifications);
_ui->serverNotificationsCheckBox->setToolTip(tr("Server notifications that require attention."));

connect(_ui->callNotificationsCheckBox, &QAbstractButton::toggled,
this, &GeneralSettings::slotToggleCallNotifications);
_ui->callNotificationsCheckBox->setToolTip(tr("Show call notification dialogs."));

connect(_ui->showInExplorerNavigationPaneCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::slotShowInExplorerNavigationPane);

// Rename 'Explorer' appropriately on non-Windows
Expand Down Expand Up @@ -247,6 +251,8 @@ void GeneralSettings::loadMiscSettings()
ConfigFile cfgFile;
_ui->monoIconsCheckBox->setChecked(cfgFile.monoIcons());
_ui->serverNotificationsCheckBox->setChecked(cfgFile.optionalServerNotifications());
_ui->callNotificationsCheckBox->setEnabled(_ui->serverNotificationsCheckBox->isEnabled());
_ui->callNotificationsCheckBox->setChecked(cfgFile.showCallNotifications());
_ui->showInExplorerNavigationPaneCheckBox->setChecked(cfgFile.showInExplorerNavigationPane());
_ui->crashreporterCheckBox->setChecked(cfgFile.crashReporter());
auto newFolderLimit = cfgFile.newBigFolderSizeLimit();
Expand Down Expand Up @@ -428,6 +434,13 @@ void GeneralSettings::slotToggleOptionalServerNotifications(bool enable)
{
ConfigFile cfgFile;
cfgFile.setOptionalServerNotifications(enable);
_ui->callNotificationsCheckBox->setEnabled(enable);
}

void GeneralSettings::slotToggleCallNotifications(bool enable)
{
ConfigFile cfgFile;
cfgFile.setShowCallNotifications(enable);
}

void GeneralSettings::slotShowInExplorerNavigationPane(bool checked)
Expand Down
1 change: 1 addition & 0 deletions src/gui/generalsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ private slots:
void saveMiscSettings();
void slotToggleLaunchOnStartup(bool);
void slotToggleOptionalServerNotifications(bool);
void slotToggleCallNotifications(bool);
void slotShowInExplorerNavigationPane(bool);
void slotIgnoreFilesEditor();
void slotCreateDebugArchive();
Expand Down
11 changes: 9 additions & 2 deletions src/gui/generalsettings.ui
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>554</width>
<height>558</height>
<width>556</width>
<height>563</height>
</rect>
</property>
<property name="windowTitle">
Expand Down Expand Up @@ -90,6 +90,13 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="callNotificationsCheckBox">
<property name="text">
<string>Show Call Notifications</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
Expand Down
214 changes: 192 additions & 22 deletions src/gui/systray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickWindow>
#include <QVariantMap>
#include <QScreen>
#include <QMenu>
#include <QGuiApplication>
Expand Down Expand Up @@ -159,6 +160,44 @@ void Systray::create()
}
}

void Systray::createCallDialog(const Activity &callNotification)
{
qCDebug(lcSystray) << "Starting a new call dialog for notification with id: " << callNotification._id << "with text: " << callNotification._subject;

if (_trayEngine && !_callsAlreadyNotified.contains(callNotification._id)) {
const QVariantMap talkNotificationData{
{"conversationToken", callNotification._talkNotificationData.conversationToken},
{"messageId", callNotification._talkNotificationData.messageId},
{"messageSent", callNotification._talkNotificationData.messageSent},
{"userAvatar", callNotification._talkNotificationData.userAvatar},
};

QVariantList links;
for(const auto &link : callNotification._links) {
links.append(QVariantMap{
{"imageSource", link._imageSource},
{"imageSourceHovered", link._imageSourceHovered},
{"label", link._label},
{"link", link._link},
{"primary", link._primary},
{"verb", link._verb},
});
}

const QVariantMap initialProperties{
{"talkNotificationData", talkNotificationData},
{"links", links},
{"subject", callNotification._subject},
{"link", callNotification._link},
};

const auto callDialog = new QQmlComponent(_trayEngine, QStringLiteral("qrc:/qml/src/gui/tray/CallNotificationDialog.qml"));
callDialog->createWithInitialProperties(initialProperties);

_callsAlreadyNotified.insert(callNotification._id);
}
}

void Systray::slotNewUserSelected()
{
if (_trayEngine) {
Expand Down Expand Up @@ -308,6 +347,30 @@ void Systray::forceWindowInit(QQuickWindow *window) const
#endif
}

void Systray::positionNotificationWindow(QQuickWindow *window) const
{
if (!useNormalWindow()) {
window->setScreen(currentScreen());
if(geometry().isValid()) {
// On OSes where the QSystemTrayIcon geometry method isn't borked, we can actually figure out where the system tray is located
// We can therefore use our normal routines
const auto position = computeNotificationPosition(window->width(), window->height());
window->setPosition(position);
} else if (QProcessEnvironment::systemEnvironment().contains(QStringLiteral("XDG_CURRENT_DESKTOP")) &&
(QProcessEnvironment::systemEnvironment().value(QStringLiteral("XDG_CURRENT_DESKTOP")).contains(QStringLiteral("GNOME")))) {
// We can safely hardcode the top-right position for the notification when running GNOME
const auto position = computeNotificationPosition(window->width(), window->height(), 0, NotificationPosition::TopRight);
window->setPosition(position);
} else {
// For other DEs we play it safe and place the notification in the centre of the screen
const QPoint windowAdjustment(window->geometry().width() / 2, window->geometry().height() / 2);
const auto position = currentScreen()->geometry().center();// - windowAdjustment;
window->setPosition(position);
}
// TODO: Get actual notification positions for the DEs
}
}

QScreen *Systray::currentScreen() const
{
const auto screen = QGuiApplication::screenAt(QCursor::pos());
Expand Down Expand Up @@ -446,8 +509,85 @@ QPoint Systray::computeWindowReferencePoint() const
Q_UNREACHABLE();
}

QPoint Systray::computeNotificationReferencePoint(int spacing, NotificationPosition position) const
{
auto trayIconCenter = calcTrayIconCenter();
auto taskbarScreenEdge = taskbarOrientation();
auto taskbarRect = taskbarGeometry();
const auto screenRect = currentScreenRect();

if(position == NotificationPosition::TopLeft) {
taskbarScreenEdge = TaskBarPosition::Top;
trayIconCenter = QPoint(0, 0);
taskbarRect = QRect(0, 0, screenRect.width(), 32);
} else if(position == NotificationPosition::TopRight) {
taskbarScreenEdge = TaskBarPosition::Top;
trayIconCenter = QPoint(screenRect.width(), 0);
taskbarRect = QRect(0, 0, screenRect.width(), 32);
} else if(position == NotificationPosition::BottomLeft) {
taskbarScreenEdge = TaskBarPosition::Bottom;
trayIconCenter = QPoint(0, screenRect.height());
taskbarRect = QRect(0, 0, screenRect.width(), 32);
} else if(position == NotificationPosition::BottomRight) {
taskbarScreenEdge = TaskBarPosition::Bottom;
trayIconCenter = QPoint(screenRect.width(), screenRect.height());
taskbarRect = QRect(0, 0, screenRect.width(), 32);
}

qCDebug(lcSystray) << "screenRect:" << screenRect;
qCDebug(lcSystray) << "taskbarRect:" << taskbarRect;
qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
qCDebug(lcSystray) << "trayIconCenter:" << trayIconCenter;

switch(taskbarScreenEdge) {
case TaskBarPosition::Bottom:
return {
trayIconCenter.x() < screenRect.center().x() ? screenRect.left() + spacing : screenRect.right() - spacing,
screenRect.bottom() - taskbarRect.height() - spacing
};
case TaskBarPosition::Left:
return {
screenRect.left() + taskbarRect.width() + spacing,
trayIconCenter.y() < screenRect.center().y() ? screenRect.top() + spacing : screenRect.bottom() - spacing
};
case TaskBarPosition::Top:
return {
trayIconCenter.x() < screenRect.center().x() ? screenRect.left() + spacing : screenRect.right() - spacing,
screenRect.top() + taskbarRect.height() + spacing
};
case TaskBarPosition::Right:
return {
screenRect.right() - taskbarRect.width() - spacing,
trayIconCenter.y() < screenRect.center().y() ? screenRect.top() + spacing : screenRect.bottom() - spacing
};
}
Q_UNREACHABLE();
}

QRect Systray::computeWindowRect(int spacing, const QPoint &topLeft, const QPoint &bottomRight) const
{
const auto screenRect = currentScreenRect();
const auto rect = QRect(topLeft, bottomRight);
auto offset = QPoint();

if (rect.left() < screenRect.left()) {
offset.setX(screenRect.left() - rect.left() + spacing);
} else if (rect.right() > screenRect.right()) {
offset.setX(screenRect.right() - rect.right() - spacing);
}

if (rect.top() < screenRect.top()) {
offset.setY(screenRect.top() - rect.top() + spacing);
} else if (rect.bottom() > screenRect.bottom()) {
offset.setY(screenRect.bottom() - rect.bottom() - spacing);
}

return rect.translated(offset);
}

QPoint Systray::computeWindowPosition(int width, int height) const
{
constexpr auto spacing = 4;
const auto referencePoint = computeWindowReferencePoint();

const auto taskbarScreenEdge = taskbarOrientation();
Expand All @@ -467,44 +607,74 @@ QPoint Systray::computeWindowPosition(int width, int height) const
Q_UNREACHABLE();
}();
const auto bottomRight = topLeft + QPoint(width, height);
const auto windowRect = [=]() {
const auto rect = QRect(topLeft, bottomRight);
auto offset = QPoint();

if (rect.left() < screenRect.left()) {
offset.setX(screenRect.left() - rect.left() + 4);
} else if (rect.right() > screenRect.right()) {
offset.setX(screenRect.right() - rect.right() - 4);
}
const auto windowRect = computeWindowRect(spacing, topLeft, bottomRight);

if (rect.top() < screenRect.top()) {
offset.setY(screenRect.top() - rect.top() + 4);
} else if (rect.bottom() > screenRect.bottom()) {
offset.setY(screenRect.bottom() - rect.bottom() - 4);
}
qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
qCDebug(lcSystray) << "screenRect:" << screenRect;
qCDebug(lcSystray) << "windowRect (reference)" << QRect(topLeft, bottomRight);
qCDebug(lcSystray) << "windowRect (adjusted)" << windowRect;

return windowRect.topLeft();
}

QPoint Systray::computeNotificationPosition(int width, int height, int spacing, NotificationPosition position) const
{
const auto referencePoint = computeNotificationReferencePoint(spacing, position);

return rect.translated(offset);
auto trayIconCenter = calcTrayIconCenter();
auto taskbarScreenEdge = taskbarOrientation();
const auto screenRect = currentScreenRect();

if(position == NotificationPosition::TopLeft) {
taskbarScreenEdge = TaskBarPosition::Top;
trayIconCenter = QPoint(0, 0);
} else if(position == NotificationPosition::TopRight) {
taskbarScreenEdge = TaskBarPosition::Top;
trayIconCenter = QPoint(screenRect.width(), 0);
} else if(position == NotificationPosition::BottomLeft) {
taskbarScreenEdge = TaskBarPosition::Bottom;
trayIconCenter = QPoint(0, screenRect.height());
} else if(position == NotificationPosition::BottomRight) {
taskbarScreenEdge = TaskBarPosition::Bottom;
trayIconCenter = QPoint(screenRect.width(), screenRect.height());
}

const auto topLeft = [=]() {
switch(taskbarScreenEdge) {
case TaskBarPosition::Bottom:
return trayIconCenter.x() < screenRect.center().x() ? referencePoint - QPoint(0, height) : referencePoint - QPoint(width, height);
case TaskBarPosition::Left:
return trayIconCenter.y() < screenRect.center().y() ? referencePoint : referencePoint - QPoint(0, height);
case TaskBarPosition::Top:
return trayIconCenter.x() < screenRect.center().x() ? referencePoint : referencePoint - QPoint(width, 0);
case TaskBarPosition::Right:
return trayIconCenter.y() < screenRect.center().y() ? referencePoint - QPoint(width, 0) : QPoint(width, height);
}
Q_UNREACHABLE();
}();
const auto bottomRight = topLeft + QPoint(width, height);
const auto windowRect = computeWindowRect(spacing, topLeft, bottomRight);

qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
qCDebug(lcSystray) << "screenRect:" << screenRect;
qCDebug(lcSystray) << "windowRect (reference)" << QRect(topLeft, bottomRight);
qCDebug(lcSystray) << "windowRect (adjusted)" << windowRect;
qCDebug(lcSystray) << "referencePoint" << referencePoint;

return windowRect.topLeft();
}

QPoint Systray::calcTrayIconCenter() const
{
// QSystemTrayIcon::geometry() is broken for ages on most Linux DEs (invalid geometry returned)
// thus we can use this only for Windows and macOS
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
auto trayIconCenter = geometry().center();
return trayIconCenter;
#else
if(geometry().isValid()) {
// QSystemTrayIcon::geometry() is broken for ages on most Linux DEs (invalid geometry returned)
// thus we can use this only for Windows and macOS
auto trayIconCenter = geometry().center();
return trayIconCenter;
}

// On Linux, fall back to mouse position (assuming tray icon is activated by mouse click)
return QCursor::pos(currentScreen());
#endif
}

AccessManagerFactory::AccessManagerFactory()
Expand Down
10 changes: 10 additions & 0 deletions src/gui/systray.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class Systray

enum class TaskBarPosition { Bottom, Left, Top, Right };
Q_ENUM(TaskBarPosition);

enum class NotificationPosition { Default, TopLeft, TopRight, BottomLeft, BottomRight };
Q_ENUM(NotificationPosition);

void setTrayEngine(QQmlApplicationEngine *trayEngine);
void create();
Expand All @@ -72,13 +75,15 @@ class Systray
bool isOpen();
QString windowTitle() const;
bool useNormalWindow() const;
void createCallDialog(const Activity &callNotification);

Q_INVOKABLE void pauseResumeSync();
Q_INVOKABLE bool syncIsPaused();
Q_INVOKABLE void setOpened();
Q_INVOKABLE void setClosed();
Q_INVOKABLE void positionWindow(QQuickWindow *window) const;
Q_INVOKABLE void forceWindowInit(QQuickWindow *window) const;
Q_INVOKABLE void positionNotificationWindow(QQuickWindow *window) const;

signals:
void currentUserChanged();
Expand Down Expand Up @@ -110,16 +115,21 @@ private slots:
QScreen *currentScreen() const;
QRect currentScreenRect() const;
QPoint computeWindowReferencePoint() const;
QPoint computeNotificationReferencePoint(int spacing = 20, NotificationPosition position = NotificationPosition::Default) const;
QPoint calcTrayIconCenter() const;
TaskBarPosition taskbarOrientation() const;
QRect taskbarGeometry() const;
QRect computeWindowRect(int spacing, const QPoint &topLeft, const QPoint &bottomRight) const;
QPoint computeWindowPosition(int width, int height) const;
QPoint computeNotificationPosition(int width, int height, int spacing = 20, NotificationPosition position = NotificationPosition::Default) const;

bool _isOpen = false;
bool _syncIsPaused = true;
QPointer<QQmlApplicationEngine> _trayEngine;

AccessManagerFactory _accessManagerFactory;

QSet<qlonglong> _callsAlreadyNotified;
};

} // namespace OCC
Expand Down
1 change: 1 addition & 0 deletions src/gui/tray/ActivityItem.qml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ MouseArea {
readonly property bool isChatActivity: model.objectType === "chat" || model.objectType === "room" || model.objectType === "call"
readonly property bool isTalkReplyPossible: model.conversationToken !== ""
property bool isTalkReplyOptionVisible: model.messageSent !== ""
readonly property bool isCallActivity: model.objectType === "call"

signal fileActivityButtonClicked(string absolutePath)

Expand Down
Loading