Merge master and fix conflicts

This commit is contained in:
Joseph Donofry 2020-06-09 20:51:36 -04:00
commit 6bb73f84a3
No known key found for this signature in database
GPG Key ID: E8A1D78EF044B0CB
66 changed files with 2535 additions and 460 deletions

View File

@ -3,6 +3,7 @@
set -ex set -ex
if [ "$FLATPAK" ]; then if [ "$FLATPAK" ]; then
sudo apt-get -y install flatpak flatpak-builder elfutils
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak --noninteractive install --user flathub org.kde.Platform//5.14 flatpak --noninteractive install --user flathub org.kde.Platform//5.14
flatpak --noninteractive install --user flathub org.kde.Sdk//5.14 flatpak --noninteractive install --user flathub org.kde.Sdk//5.14

View File

@ -6,7 +6,26 @@ if [ "$FLATPAK" ]; then
mkdir -p build-flatpak mkdir -p build-flatpak
cd build-flatpak cd build-flatpak
flatpak-builder --ccache --repo=repo --subject="Build of Nheko ${VERSION} `date`" app ../io.github.NhekoReborn.Nheko.json jobsarg=""
if [ "$ARCH" = "arm64" ]; then
jobsarg="--jobs=2"
fi
flatpak-builder --ccache --repo=repo --subject="Build of Nheko ${VERSION} $jobsarg `date`" app ../io.github.NhekoReborn.Nheko.json &
# to prevent flatpak builder from timing out on arm, run it in the background and print something every minute for up to 30 minutes.
minutes=0
limit=40
while kill -0 $! >/dev/null 2>&1; do
if [ $minutes == $limit ]; then
break;
fi
minutes=$((minutes+1))
sleep 60
done
flatpak build-bundle repo nheko-${VERSION}-${ARCH}.flatpak io.github.NhekoReborn.Nheko master flatpak build-bundle repo nheko-${VERSION}-${ARCH}.flatpak io.github.NhekoReborn.Nheko master
mkdir ../artifacts mkdir ../artifacts

View File

@ -113,10 +113,6 @@ matrix:
apt: apt:
sources: sources:
- sourceline: 'ppa:alexlarsson/flatpak' - sourceline: 'ppa:alexlarsson/flatpak'
packages:
- flatpak
- flatpak-builder
- elfutils
- os: linux - os: linux
arch: arm64 arch: arm64
env: env:
@ -128,20 +124,17 @@ matrix:
sources: sources:
- sourceline: 'ppa:alexlarsson/flatpak' - sourceline: 'ppa:alexlarsson/flatpak'
packages: packages:
- flatpak
- flatpak-builder
- elfutils
- librsvg2-bin - librsvg2-bin
before_install: before_install:
# Use TRAVIS_TAG if defined, or the short commit SHA otherwise # Use TRAVIS_TAG if defined, or the short commit SHA otherwise
- export VERSION=${TRAVIS_TAG:-$(git rev-parse --short HEAD)} - export VERSION=${TRAVIS_TAG:-$(git rev-parse --short HEAD)}
install: install:
- travis_wait ./.ci/install.sh - ./.ci/install.sh
- export PATH=/usr/local/bin:${PATH} - export PATH=/usr/local/bin:${PATH}
script: script:
- travis_wait ./.ci/script.sh - ./.ci/script.sh
- sed -i -e "s/VERSION_NAME_VALUE/${VERSION}/g" ./.ci/bintray-release.json || true - sed -i -e "s/VERSION_NAME_VALUE/${VERSION}/g" ./.ci/bintray-release.json || true
- cp ./.ci/bintray-release.json . - cp ./.ci/bintray-release.json .
deploy: deploy:

View File

@ -10,7 +10,9 @@ set(
CACHE CACHE
FILEPATH "Default toolchain" FILEPATH "Default toolchain"
) )
set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard")
set(CMAKE_CXX_STANDARD_REQUIRED ON CACHE BOOL "Require C++ standard to be supported")
set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "compile as PIC by default")
option(HUNTER_ENABLED "Enable Hunter package manager" OFF) option(HUNTER_ENABLED "Enable Hunter package manager" OFF)
include("cmake/HunterGate.cmake") include("cmake/HunterGate.cmake")
@ -333,7 +335,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare( FetchContent_Declare(
MatrixClient MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
GIT_TAG f5c78f4331b62a1e25a2d839cb38b07bb9bd7829 GIT_TAG 795b6a82d4f10c629ce0eb99803cc677c413f940
) )
FetchContent_MakeAvailable(MatrixClient) FetchContent_MakeAvailable(MatrixClient)
else() else()
@ -421,7 +423,7 @@ endif()
# single instance functionality # single instance functionality
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
add_subdirectory(third_party/SingleApplication-3.0.19/) add_subdirectory(third_party/SingleApplication-3.1.3.1/)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
@ -515,6 +517,9 @@ set(TRANSLATION_DEPS ${LANG_QRC} ${QRC} ${QM_SRC})
if (APPLE) if (APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa")
set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/emoji/MacHelper.mm) set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/emoji/MacHelper.mm)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
set_source_files_properties( src/notifications/ManagerMac.mm src/emoji/MacHelper.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
endif()
elseif (WIN32) elseif (WIN32)
file(DOWNLOAD file(DOWNLOAD
"https://raw.githubusercontent.com/mohabouje/WinToast/41ed1c58d5dce0ee9c01dbdeac05be45358d4f57/src/wintoastlib.cpp" "https://raw.githubusercontent.com/mohabouje/WinToast/41ed1c58d5dce0ee9c01dbdeac05be45358d4f57/src/wintoastlib.cpp"
@ -578,6 +583,13 @@ target_link_libraries(nheko PRIVATE
tweeny tweeny
SingleApplication::SingleApplication) SingleApplication::SingleApplication)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
target_precompile_headers(nheko
PRIVATE
<string>
)
endif()
if(MSVC) if(MSVC)
target_link_libraries(nheko PRIVATE ntdll) target_link_libraries(nheko PRIVATE ntdll)
endif() endif()

View File

@ -131,7 +131,7 @@
{ {
"sha256": "59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722", "sha256": "59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722",
"type": "archive", "type": "archive",
"url": "https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.bz2" "url": "https://sourceforge.net/projects/boost/files/boost/1.72.0/boost_1_72_0.tar.bz2"
} }
] ]
}, },
@ -146,9 +146,9 @@
"name": "mtxclient", "name": "mtxclient",
"sources": [ "sources": [
{ {
"sha256": "16203a92b03c488178b31bedca9d9015b1d406443f7e5363a2e09171e50f8dfc", "sha256": "7ba85bb477c9e17e2389faf2333034aa9ceae4b1c65d6bf5f7067f2799e9b770",
"type": "archive", "type": "archive",
"url": "https://github.com/Nheko-Reborn/mtxclient/archive/f5c78f4331b62a1e25a2d839cb38b07bb9bd7829.tar.gz" "url": "https://github.com/Nheko-Reborn/mtxclient/archive/795b6a82d4f10c629ce0eb99803cc677c413f940.tar.gz"
} }
] ]
}, },

1586
resources/langs/nheko_it.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ Rectangle {
id: avatar id: avatar
width: 48 width: 48
height: 48 height: 48
radius: settings.avatar_circles ? height/2 : 3 radius: settings.avatarCircles ? height/2 : 3
property alias url: img.source property alias url: img.source
property string displayName property string displayName
@ -39,7 +39,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
width: avatar.width width: avatar.width
height: avatar.height height: avatar.height
radius: settings.avatar_circles ? height/2 : 3 radius: settings.avatarCircles ? height/2 : 3
} }
} }
} }

View File

@ -57,7 +57,7 @@ Flow {
Text { Text {
anchors.baseline: reactionCounter.baseline anchors.baseline: reactionCounter.baseline
id: reactionText id: reactionText
text: textMetrics.elidedText + (textMetrics.elidedText == model.key ? "" : "…") text: textMetrics.elidedText + (textMetrics.elidedText == textMetrics.text ? "" : "…")
font.family: settings.emoji_font_family font.family: settings.emoji_font_family
color: reaction.hovered ? colors.highlight : colors.text color: reaction.hovered ? colors.highlight : colors.text
maximumLineCount: 1 maximumLineCount: 1
@ -65,7 +65,7 @@ Flow {
Rectangle { Rectangle {
id: divider id: divider
height: reactionCounter.implicitHeight * 1.4 height: Math.floor(reactionCounter.implicitHeight * 1.4)
width: 1 width: 1
color: (reaction.hovered || model.selfReactedEvent !== '') ? colors.highlight : colors.text color: (reaction.hovered || model.selfReactedEvent !== '') ? colors.highlight : colors.text
} }

View File

@ -26,13 +26,13 @@ MouseArea {
messageContextMenu.show(model.id, model.type, model.isEncrypted, row) messageContextMenu.show(model.id, model.type, model.isEncrypted, row)
} }
Rectangle { Rectangle {
color: (timelineSettings.message_hover_highlight && parent.containsMouse) ? colors.base : "transparent" color: (settings.messageHoverHighlight && parent.containsMouse) ? colors.base : "transparent"
anchors.fill: row anchors.fill: row
} }
RowLayout { RowLayout {
id: row id: row
anchors.leftMargin: avatarSize + 4 anchors.leftMargin: avatarSize + 16
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@ -97,7 +97,7 @@ MouseArea {
event_id: model.id event_id: model.id
} }
ImageButton { ImageButton {
visible: timelineSettings.buttons visible: settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 Layout.preferredHeight: 16
width: 16 width: 16
@ -113,7 +113,7 @@ MouseArea {
onClicked: chat.model.replyAction(model.id) onClicked: chat.model.replyAction(model.id)
} }
ImageButton { ImageButton {
visible: timelineSettings.buttons visible: settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 Layout.preferredHeight: 16
width: 16 width: 16

View File

@ -3,7 +3,6 @@ import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import Qt.labs.settings 1.0
import im.nheko 1.0 import im.nheko 1.0
import im.nheko.EmojiModel 1.0 import im.nheko.EmojiModel 1.0
@ -122,7 +121,7 @@ Page {
BusyIndicator { BusyIndicator {
visible: running visible: running
anchors.centerIn: parent anchors.centerIn: parent
running: timelineManager.isInitialSync running: timelineManager.isInitialSync
height: 200 height: 200
width: 200 width: 200
z: 3 z: 3
@ -133,12 +132,12 @@ Page {
visible: timelineManager.timeline != null visible: timelineManager.timeline != null
cacheBuffer: 500 cacheBuffer: 400
anchors.left: parent.left anchors.horizontalCenter: parent.horizontalCenter
anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: chatFooter.top anchors.bottom: chatFooter.top
width: parent.width
anchors.leftMargin: 4 anchors.leftMargin: 4
anchors.rightMargin: scrollbar.width anchors.rightMargin: scrollbar.width
@ -180,7 +179,7 @@ Page {
id: scrollbar id: scrollbar
parent: chat.parent parent: chat.parent
anchors.top: chat.top anchors.top: chat.top
anchors.left: chat.right anchors.right: chat.right
anchors.bottom: chat.bottom anchors.bottom: chat.bottom
} }
@ -195,7 +194,8 @@ Page {
id: wrapper id: wrapper
property Item section property Item section
width: chat.width anchors.horizontalCenter: parent.horizontalCenter
width: (settings.timelineMaxWidth > 100 && (parent.width - settings.timelineMaxWidth) > 32) ? settings.timelineMaxWidth : (parent.width - 32)
height: section ? section.height + timelinerow.height : timelinerow.height height: section ? section.height + timelinerow.height : timelinerow.height
color: "transparent" color: "transparent"
@ -265,7 +265,8 @@ Page {
} }
Row { Row {
height: userName.height height: userName.height
spacing: 4 spacing: 8
Avatar { Avatar {
width: avatarSize width: avatarSize
height: avatarSize height: avatarSize

View File

@ -6,4 +6,5 @@ MatrixText {
width: parent ? parent.width : undefined width: parent ? parent.width : undefined
height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined
clip: true clip: true
font.pointSize: (settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? settings.fontSize * 3 : settings.fontSize
} }

View File

@ -0,0 +1,67 @@
import QtQuick 2.9
import QtQuick.Controls 2.9
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.9
import im.nheko 1.0
import im.nheko.EmojiModel 1.0
GridView {
id: root
property var category
property var emojiPopup
property EmojiProxyModel model: EmojiProxyModel {
sourceModel: EmojiModel {
viewCategory: category
}
}
interactive: false
cellWidth: 52
cellHeight: 52
height: 52 * ( model.count / 7 + 1 )
clip: true
// Individual emoji
delegate: AbstractButton {
width: 48
height: 48
contentItem: Text {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.family: settings.emoji_font_family
font.pixelSize: 36
text: model.unicode
}
background: Rectangle {
anchors.fill: parent
color: hovered ? colors.highlight : 'transparent'
radius: 5
}
hoverEnabled: true
ToolTip.text: model.shortName
ToolTip.visible: hovered
// give the emoji a little oomf
DropShadow {
width: parent.width;
height: parent.height;
horizontalOffset: 3
verticalOffset: 3
radius: 8.0
samples: 17
color: "#80000000"
source: parent.contentItem
}
// TODO: maybe add favorites at some point?
onClicked: {
console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id + " in room " + emojiPopup.room_id)
emojiPopup.picked(emojiPopup.room_id, emojiPopup.event_id, model.unicode)
}
}
}

View File

@ -31,6 +31,7 @@
#include "Cache.h" #include "Cache.h"
#include "Cache_p.h" #include "Cache_p.h"
#include "EventAccessors.h"
#include "Logging.h" #include "Logging.h"
#include "Utils.h" #include "Utils.h"
@ -1947,13 +1948,14 @@ Cache::saveTimelineMessages(lmdb::txn &txn,
json obj = json::object(); json obj = json::object();
obj["event"] = utils::serialize_event(e); obj["event"] = mtx::accessors::serialize_event(e);
obj["token"] = res.prev_batch; obj["token"] = res.prev_batch;
lmdb::dbi_put(txn, lmdb::dbi_put(
db, txn,
lmdb::val(std::to_string(utils::event_timestamp(e))), db,
lmdb::val(obj.dump())); lmdb::val(std::to_string(obj["event"]["origin_server_ts"].get<uint64_t>())),
lmdb::val(obj.dump()));
} }
} }
@ -2026,7 +2028,7 @@ Cache::saveTimelineMentions(lmdb::txn &txn,
using namespace mtx::events::state; using namespace mtx::events::state;
for (const auto &notif : res) { for (const auto &notif : res) {
const auto event_id = utils::event_id(notif.event); const auto event_id = mtx::accessors::event_id(notif.event);
// double check that we have the correct room_id... // double check that we have the correct room_id...
if (room_id.compare(notif.room_id) != 0) { if (room_id.compare(notif.room_id) != 0) {

View File

@ -26,6 +26,7 @@
#include "Cache.h" #include "Cache.h"
#include "Cache_p.h" #include "Cache_p.h"
#include "ChatPage.h" #include "ChatPage.h"
#include "EventAccessors.h"
#include "Logging.h" #include "Logging.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "MatrixClient.h" #include "MatrixClient.h"
@ -255,7 +256,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
text_input_, &TextInputWidget::startedTyping, this, &ChatPage::sendTypingNotifications); text_input_, &TextInputWidget::startedTyping, this, &ChatPage::sendTypingNotifications);
connect(typingRefresher_, &QTimer::timeout, this, &ChatPage::sendTypingNotifications); connect(typingRefresher_, &QTimer::timeout, this, &ChatPage::sendTypingNotifications);
connect(text_input_, &TextInputWidget::stoppedTyping, this, [this]() { connect(text_input_, &TextInputWidget::stoppedTyping, this, [this]() {
if (!userSettings_->isTypingNotificationsEnabled()) if (!userSettings_->typingNotifications())
return; return;
typingRefresher_->stop(); typingRefresher_->stop();
@ -482,7 +483,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
activateWindow(); activateWindow();
}); });
setGroupViewState(userSettings_->isGroupViewEnabled()); setGroupViewState(userSettings_->groupView());
connect(userSettings_.data(), connect(userSettings_.data(),
&UserSettings::groupViewStateChanged, &UserSettings::groupViewStateChanged,
@ -891,7 +892,7 @@ void
ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res) ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
{ {
for (const auto &item : res.notifications) { for (const auto &item : res.notifications) {
const auto event_id = utils::event_id(item.event); const auto event_id = mtx::accessors::event_id(item.event);
try { try {
if (item.read) { if (item.read) {
@ -901,7 +902,8 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
if (!cache::isNotificationSent(event_id)) { if (!cache::isNotificationSent(event_id)) {
const auto room_id = QString::fromStdString(item.room_id); const auto room_id = QString::fromStdString(item.room_id);
const auto user_id = utils::event_sender(item.event); const auto user_id =
QString::fromStdString(mtx::accessors::sender(item.event));
// We should only sent one notification per event. // We should only sent one notification per event.
cache::markSentNotification(event_id); cache::markSentNotification(event_id);
@ -1213,7 +1215,7 @@ ChatPage::unbanUser(QString userid, QString reason)
void void
ChatPage::sendTypingNotifications() ChatPage::sendTypingNotifications()
{ {
if (!userSettings_->isTypingNotificationsEnabled()) if (!userSettings_->typingNotifications())
return; return;
http::client()->start_typing( http::client()->start_typing(
@ -1349,7 +1351,7 @@ ChatPage::hideSideBars()
void void
ChatPage::showSideBars() ChatPage::showSideBars()
{ {
if (userSettings_->isGroupViewEnabled()) if (userSettings_->groupView())
communitiesList_->show(); communitiesList_->show();
sideBar_->show(); sideBar_->show();

View File

@ -7,7 +7,6 @@
#include "ui/Theme.h" #include "ui/Theme.h"
class RippleOverlay; class RippleOverlay;
class QPainter;
class QMouseEvent; class QMouseEvent;
class CommunitiesListItem : public QWidget class CommunitiesListItem : public QWidget

View File

@ -400,3 +400,9 @@ mtx::accessors::media_width(const mtx::events::collections::TimelineEvents &even
{ {
return std::visit(EventMediaWidth{}, event); return std::visit(EventMediaWidth{}, event);
} }
nlohmann::json
mtx::accessors::serialize_event(const mtx::events::collections::TimelineEvents &event)
{
return std::visit([](const auto &e) { return nlohmann::json(e); }, event);
}

View File

@ -63,4 +63,7 @@ media_height(const mtx::events::collections::TimelineEvents &event);
uint64_t uint64_t
media_width(const mtx::events::collections::TimelineEvents &event); media_width(const mtx::events::collections::TimelineEvents &event);
nlohmann::json
serialize_event(const mtx::events::collections::TimelineEvents &event);
} }

View File

@ -1,4 +1,5 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel>
#include <QPushButton> #include <QPushButton>
#include "InviteeItem.h" #include "InviteeItem.h"

View File

@ -1,11 +1,11 @@
#pragma once #pragma once
#include <QLabel>
#include <QWidget> #include <QWidget>
#include <mtx/identifiers.hpp> #include <mtx/identifiers.hpp>
class QPushButton; class QPushButton;
class QLabel;
class InviteeItem : public QWidget class InviteeItem : public QWidget
{ {

View File

@ -16,6 +16,7 @@
*/ */
#include <QDesktopServices> #include <QDesktopServices>
#include <QLabel>
#include <QPainter> #include <QPainter>
#include <QStyleOption> #include <QStyleOption>
@ -118,7 +119,7 @@ LoginPage::LoginPage(QWidget *parent)
deviceName_->setLabel(tr("Device name")); deviceName_->setLabel(tr("Device name"));
deviceName_->setToolTip( deviceName_->setToolTip(
tr("A name for this device, which will be shown to others, when verifying your devices. " tr("A name for this device, which will be shown to others, when verifying your devices. "
"If none is provided, a random string is used for privacy purposes.")); "If none is provided a default is used."));
serverInput_ = new TextField(this); serverInput_ = new TextField(this);
serverInput_->setLabel("Homeserver address"); serverInput_->setLabel("Homeserver address");
@ -132,7 +133,7 @@ LoginPage::LoginPage(QWidget *parent)
form_layout_->addLayout(matrixidLayout_); form_layout_->addLayout(matrixidLayout_);
form_layout_->addWidget(password_input_); form_layout_->addWidget(password_input_);
form_layout_->addWidget(deviceName_, Qt::AlignHCenter, nullptr); form_layout_->addWidget(deviceName_, Qt::AlignHCenter);
form_layout_->addLayout(serverLayout_); form_layout_->addLayout(serverLayout_);
button_layout_ = new QHBoxLayout(); button_layout_ = new QHBoxLayout();
@ -179,6 +180,12 @@ LoginPage::LoginPage(QWidget *parent)
connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered())); connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
} }
void
LoginPage::loginError(const QString &msg)
{
error_label_->setText(msg);
}
void void
LoginPage::onMatrixIdEntered() LoginPage::onMatrixIdEntered()
{ {

View File

@ -17,8 +17,6 @@
#pragma once #pragma once
#include <QLabel>
#include <QLayout>
#include <QWidget> #include <QWidget>
class FlatButton; class FlatButton;
@ -26,6 +24,9 @@ class LoadingIndicator;
class OverlayModal; class OverlayModal;
class RaisedButton; class RaisedButton;
class TextField; class TextField;
class QLabel;
class QVBoxLayout;
class QHBoxLayout;
namespace mtx { namespace mtx {
namespace responses { namespace responses {
@ -65,7 +66,7 @@ protected:
public slots: public slots:
// Displays errors produced during the login. // Displays errors produced during the login.
void loginError(const QString &msg) { error_label_->setText(msg); } void loginError(const QString &msg);
private slots: private slots:
// Callback for the back button. // Callback for the back button.

View File

@ -148,7 +148,7 @@ MainWindow::MainWindow(QWidget *parent)
QSettings settings; QSettings settings;
trayIcon_->setVisible(userSettings_->isTrayEnabled()); trayIcon_->setVisible(userSettings_->tray());
if (hasActiveUser()) { if (hasActiveUser()) {
QString token = settings.value("auth/access_token").toString(); QString token = settings.value("auth/access_token").toString();
@ -286,7 +286,7 @@ void
MainWindow::closeEvent(QCloseEvent *event) MainWindow::closeEvent(QCloseEvent *event)
{ {
if (!qApp->isSavingSession() && isVisible() && pageSupportsTray() && if (!qApp->isSavingSession() && isVisible() && pageSupportsTray() &&
userSettings_->isTrayEnabled()) { userSettings_->tray()) {
event->ignore(); event->ignore();
hide(); hide();
} }

View File

@ -17,7 +17,8 @@ MxcImageResponse::run()
auto data = cache::image(fileName); auto data = cache::image(fileName);
if (!data.isNull()) { if (!data.isNull()) {
m_image = utils::readImage(&data); m_image = utils::readImage(&data);
m_image = m_image.scaled(m_requestedSize, Qt::KeepAspectRatio); m_image = m_image.scaled(
m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
m_image.setText("mxc url", "mxc://" + m_id); m_image.setText("mxc url", "mxc://" + m_id);
if (!m_image.isNull()) { if (!m_image.isNull()) {

View File

@ -15,6 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <QLabel>
#include <QMetaType> #include <QMetaType>
#include <QPainter> #include <QPainter>
#include <QStyleOption> #include <QStyleOption>
@ -106,10 +107,10 @@ RegisterPage::RegisterPage(QWidget *parent)
tr("A server that allows registration. Since matrix is decentralized, you need to first " tr("A server that allows registration. Since matrix is decentralized, you need to first "
"find a server you can register on or host your own.")); "find a server you can register on or host your own."));
form_layout_->addWidget(username_input_, Qt::AlignHCenter, nullptr); form_layout_->addWidget(username_input_, Qt::AlignHCenter);
form_layout_->addWidget(password_input_, Qt::AlignHCenter, nullptr); form_layout_->addWidget(password_input_, Qt::AlignHCenter);
form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter, nullptr); form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter);
form_layout_->addWidget(server_input_, Qt::AlignHCenter, nullptr); form_layout_->addWidget(server_input_, Qt::AlignHCenter);
button_layout_ = new QHBoxLayout(); button_layout_ = new QHBoxLayout();
button_layout_->setSpacing(0); button_layout_->setSpacing(0);

View File

@ -17,8 +17,8 @@
#pragma once #pragma once
#include <QLabel> #include <QWidget>
#include <QLayout>
#include <memory> #include <memory>
#include <mtx/user_interactive.hpp> #include <mtx/user_interactive.hpp>
@ -26,6 +26,9 @@
class FlatButton; class FlatButton;
class RaisedButton; class RaisedButton;
class TextField; class TextField;
class QLabel;
class QVBoxLayout;
class QHBoxLayout;
class RegisterPage : public QWidget class RegisterPage : public QWidget
{ {

View File

@ -451,7 +451,7 @@ RoomInfoListItem::calculateImportance() const
// returns ImportanceDisabled or Invite // returns ImportanceDisabled or Invite
if (isInvite()) { if (isInvite()) {
return Invite; return Invite;
} else if (!settings->isSortByImportanceEnabled()) { } else if (!settings->sortByImportance()) {
return ImportanceDisabled; return ImportanceDisabled;
} else if (unreadHighlightedMsgCount_) { } else if (unreadHighlightedMsgCount_) {
return NewMentions; return NewMentions;

View File

@ -21,6 +21,8 @@
#include <QObject> #include <QObject>
#include <QPainter> #include <QPainter>
#include <QScroller> #include <QScroller>
#include <QStyle>
#include <QStyleOption>
#include <QTimer> #include <QTimer>
#include "Logging.h" #include "Logging.h"

View File

@ -1,6 +1,8 @@
#include <QIcon> #include <QIcon>
#include <QPainter> #include <QPainter>
#include <QResizeEvent> #include <QResizeEvent>
#include <QStyle>
#include <QStyleOption>
#include <mtx/requests.hpp> #include <mtx/requests.hpp>

View File

@ -27,7 +27,6 @@ class Menu;
class TextLabel; class TextLabel;
class OverlayModal; class OverlayModal;
class QPainter;
class QLabel; class QLabel;
class QHBoxLayout; class QHBoxLayout;
class QVBoxLayout; class QVBoxLayout;

View File

@ -16,7 +16,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <QLabel>
#include <QPainter> #include <QPainter>
#include <QStyle>
#include <QStyleOption>
#include <QTimer> #include <QTimer>
#include <iostream> #include <iostream>

View File

@ -17,13 +17,16 @@
#pragma once #pragma once
#include <QLabel> #include <QWidget>
#include <QLayout>
class Avatar; class Avatar;
class FlatButton; class FlatButton;
class OverlayModal; class OverlayModal;
class QLabel;
class QHBoxLayout;
class QVBoxLayout;
class UserInfoWidget : public QWidget class UserInfoWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT

View File

@ -30,6 +30,7 @@
#include <QScrollArea> #include <QScrollArea>
#include <QScroller> #include <QScroller>
#include <QSettings> #include <QSettings>
#include <QSpinBox>
#include <QStandardPaths> #include <QStandardPaths>
#include <QString> #include <QString>
#include <QTextStream> #include <QTextStream>
@ -51,54 +52,206 @@ void
UserSettings::load() UserSettings::load()
{ {
QSettings settings; QSettings settings;
isTrayEnabled_ = settings.value("user/window/tray", false).toBool(); tray_ = settings.value("user/window/tray", false).toBool();
hasDesktopNotifications_ = settings.value("user/desktop_notifications", true).toBool(); hasDesktopNotifications_ = settings.value("user/desktop_notifications", true).toBool();
isStartInTrayEnabled_ = settings.value("user/window/start_in_tray", false).toBool(); startInTray_ = settings.value("user/window/start_in_tray", false).toBool();
isGroupViewEnabled_ = settings.value("user/group_view", true).toBool(); groupView_ = settings.value("user/group_view", true).toBool();
isButtonsInTimelineEnabled_ = settings.value("user/timeline/buttons", true).toBool(); buttonsInTimeline_ = settings.value("user/timeline/buttons", true).toBool();
isMessageHoverHighlightEnabled_ = timelineMaxWidth_ = settings.value("user/timeline/max_width", 0).toInt();
messageHoverHighlight_ =
settings.value("user/timeline/message_hover_highlight", false).toBool(); settings.value("user/timeline/message_hover_highlight", false).toBool();
isMarkdownEnabled_ = settings.value("user/markdown_enabled", true).toBool(); enlargeEmojiOnlyMessages_ =
isTypingNotificationsEnabled_ = settings.value("user/typing_notifications", true).toBool(); settings.value("user/timeline/enlarge_emoji_only_msg", false).toBool();
sortByImportance_ = settings.value("user/sort_by_unread", true).toBool(); markdown_ = settings.value("user/markdown_enabled", true).toBool();
isReadReceiptsEnabled_ = settings.value("user/read_receipts", true).toBool(); typingNotifications_ = settings.value("user/typing_notifications", true).toBool();
theme_ = settings.value("user/theme", defaultTheme_).toString(); sortByImportance_ = settings.value("user/sort_by_unread", true).toBool();
font_ = settings.value("user/font_family", "default").toString(); readReceipts_ = settings.value("user/read_receipts", true).toBool();
avatarCircles_ = settings.value("user/avatar_circles", true).toBool(); theme_ = settings.value("user/theme", defaultTheme_).toString();
decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool(); font_ = settings.value("user/font_family", "default").toString();
emojiFont_ = settings.value("user/emoji_font_family", "default").toString(); avatarCircles_ = settings.value("user/avatar_circles", true).toBool();
baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble(); decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool();
emojiFont_ = settings.value("user/emoji_font_family", "default").toString();
baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble();
applyTheme(); applyTheme();
} }
void
UserSettings::setMessageHoverHighlight(bool state)
{
if (state == messageHoverHighlight_)
return;
messageHoverHighlight_ = state;
emit messageHoverHighlightChanged(state);
save();
}
void
UserSettings::setEnlargeEmojiOnlyMessages(bool state)
{
if (state == enlargeEmojiOnlyMessages_)
return;
enlargeEmojiOnlyMessages_ = state;
emit enlargeEmojiOnlyMessagesChanged(state);
save();
}
void
UserSettings::setTray(bool state)
{
if (state == tray_)
return;
tray_ = state;
emit trayChanged(state);
save();
}
void
UserSettings::setStartInTray(bool state)
{
if (state == startInTray_)
return;
startInTray_ = state;
emit startInTrayChanged(state);
save();
}
void
UserSettings::setGroupView(bool state)
{
if (groupView_ != state)
emit groupViewStateChanged(state);
groupView_ = state;
save();
}
void
UserSettings::setMarkdown(bool state)
{
if (state == markdown_)
return;
markdown_ = state;
emit markdownChanged(state);
save();
}
void
UserSettings::setReadReceipts(bool state)
{
if (state == readReceipts_)
return;
readReceipts_ = state;
emit readReceiptsChanged(state);
save();
}
void
UserSettings::setTypingNotifications(bool state)
{
if (state == typingNotifications_)
return;
typingNotifications_ = state;
emit typingNotificationsChanged(state);
save();
}
void
UserSettings::setSortByImportance(bool state)
{
if (state == sortByImportance_)
return;
sortByImportance_ = state;
emit roomSortingChanged(state);
save();
}
void
UserSettings::setButtonsInTimeline(bool state)
{
if (state == buttonsInTimeline_)
return;
buttonsInTimeline_ = state;
emit buttonInTimelineChanged(state);
save();
}
void
UserSettings::setTimelineMaxWidth(int state)
{
if (state == timelineMaxWidth_)
return;
timelineMaxWidth_ = state;
emit timelineMaxWidthChanged(state);
save();
}
void
UserSettings::setDesktopNotifications(bool state)
{
if (state == hasDesktopNotifications_)
return;
hasDesktopNotifications_ = state;
emit desktopNotificationsChanged(state);
save();
}
void
UserSettings::setAvatarCircles(bool state)
{
if (state == avatarCircles_)
return;
avatarCircles_ = state;
emit avatarCirclesChanged(state);
save();
}
void
UserSettings::setDecryptSidebar(bool state)
{
if (state == decryptSidebar_)
return;
decryptSidebar_ = state;
emit decryptSidebarChanged(state);
save();
}
void void
UserSettings::setFontSize(double size) UserSettings::setFontSize(double size)
{ {
if (size == baseFontSize_)
return;
baseFontSize_ = size; baseFontSize_ = size;
emit fontSizeChanged(size);
save(); save();
} }
void void
UserSettings::setFontFamily(QString family) UserSettings::setFontFamily(QString family)
{ {
if (family == font_)
return;
font_ = family; font_ = family;
emit fontChanged(family);
save(); save();
} }
void void
UserSettings::setEmojiFontFamily(QString family) UserSettings::setEmojiFontFamily(QString family)
{ {
if (family == emojiFont_)
return;
emojiFont_ = family; emojiFont_ = family;
emit emojiFontChanged(family);
save(); save();
} }
void void
UserSettings::setTheme(QString theme) UserSettings::setTheme(QString theme)
{ {
if (theme == theme)
return;
theme_ = theme; theme_ = theme;
save(); save();
applyTheme(); applyTheme();
emit themeChanged(theme);
} }
void void
@ -161,29 +314,33 @@ UserSettings::save()
settings.beginGroup("user"); settings.beginGroup("user");
settings.beginGroup("window"); settings.beginGroup("window");
settings.setValue("tray", isTrayEnabled_); settings.setValue("tray", tray_);
settings.setValue("start_in_tray", isStartInTrayEnabled_); settings.setValue("start_in_tray", startInTray_);
settings.endGroup(); settings.endGroup();
settings.beginGroup("timeline"); settings.beginGroup("timeline");
settings.setValue("buttons", isButtonsInTimelineEnabled_); settings.setValue("buttons", buttonsInTimeline_);
settings.setValue("message_hover_highlight", isMessageHoverHighlightEnabled_); settings.setValue("message_hover_highlight", messageHoverHighlight_);
settings.setValue("enlarge_emoji_only_msg", enlargeEmojiOnlyMessages_);
settings.setValue("max_width", timelineMaxWidth_);
settings.endGroup(); settings.endGroup();
settings.setValue("avatar_circles", avatarCircles_); settings.setValue("avatar_circles", avatarCircles_);
settings.setValue("decrypt_sidebar", decryptSidebar_); settings.setValue("decrypt_sidebar", decryptSidebar_);
settings.setValue("font_size", baseFontSize_); settings.setValue("font_size", baseFontSize_);
settings.setValue("typing_notifications", isTypingNotificationsEnabled_); settings.setValue("typing_notifications", typingNotifications_);
settings.setValue("minor_events", sortByImportance_); settings.setValue("minor_events", sortByImportance_);
settings.setValue("read_receipts", isReadReceiptsEnabled_); settings.setValue("read_receipts", readReceipts_);
settings.setValue("group_view", isGroupViewEnabled_); settings.setValue("group_view", groupView_);
settings.setValue("markdown_enabled", isMarkdownEnabled_); settings.setValue("markdown_enabled", markdown_);
settings.setValue("desktop_notifications", hasDesktopNotifications_); settings.setValue("desktop_notifications", hasDesktopNotifications_);
settings.setValue("theme", theme()); settings.setValue("theme", theme());
settings.setValue("font_family", font_); settings.setValue("font_family", font_);
settings.setValue("emoji_font_family", emojiFont_); settings.setValue("emoji_font_family", emojiFont_);
settings.endGroup(); settings.endGroup();
settings.sync();
} }
HorizontalLine::HorizontalLine(QWidget *parent) HorizontalLine::HorizontalLine(QWidget *parent)
@ -231,24 +388,26 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
general_->setFont(font); general_->setFont(font);
trayToggle_ = new Toggle{this}; trayToggle_ = new Toggle{this};
startInTrayToggle_ = new Toggle{this}; startInTrayToggle_ = new Toggle{this};
avatarCircles_ = new Toggle{this}; avatarCircles_ = new Toggle{this};
decryptSidebar_ = new Toggle(this); decryptSidebar_ = new Toggle(this);
groupViewToggle_ = new Toggle{this}; groupViewToggle_ = new Toggle{this};
timelineButtonsToggle_ = new Toggle{this}; timelineButtonsToggle_ = new Toggle{this};
typingNotifications_ = new Toggle{this}; typingNotifications_ = new Toggle{this};
messageHoverHighlight_ = new Toggle{this}; messageHoverHighlight_ = new Toggle{this};
sortByImportance_ = new Toggle{this}; enlargeEmojiOnlyMessages_ = new Toggle{this};
readReceipts_ = new Toggle{this}; sortByImportance_ = new Toggle{this};
markdownEnabled_ = new Toggle{this}; readReceipts_ = new Toggle{this};
desktopNotifications_ = new Toggle{this}; markdown_ = new Toggle{this};
scaleFactorCombo_ = new QComboBox{this}; desktopNotifications_ = new Toggle{this};
fontSizeCombo_ = new QComboBox{this}; scaleFactorCombo_ = new QComboBox{this};
fontSelectionCombo_ = new QComboBox{this}; fontSizeCombo_ = new QComboBox{this};
emojiFontSelectionCombo_ = new QComboBox{this}; fontSelectionCombo_ = new QComboBox{this};
emojiFontSelectionCombo_ = new QComboBox{this};
timelineMaxWidthSpin_ = new QSpinBox{this};
if (!settings_->isTrayEnabled()) if (!settings_->tray())
startInTrayToggle_->setDisabled(true); startInTrayToggle_->setDisabled(true);
avatarCircles_->setFixedSize(64, 48); avatarCircles_->setFixedSize(64, 48);
@ -291,6 +450,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
int themeIndex = themeCombo_->findText(themeStr); int themeIndex = themeCombo_->findText(themeStr);
themeCombo_->setCurrentIndex(themeIndex); themeCombo_->setCurrentIndex(themeIndex);
timelineMaxWidthSpin_->setMinimum(0);
timelineMaxWidthSpin_->setMaximum(100'000'000);
timelineMaxWidthSpin_->setSingleStep(10);
auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this}; auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this};
encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin); encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin);
encryptionLabel_->setAlignment(Qt::AlignBottom); encryptionLabel_->setAlignment(Qt::AlignBottom);
@ -323,11 +486,15 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
sessionKeysLayout->addWidget(sessionKeysExportBtn, 0, Qt::AlignRight); sessionKeysLayout->addWidget(sessionKeysExportBtn, 0, Qt::AlignRight);
sessionKeysLayout->addWidget(sessionKeysImportBtn, 0, Qt::AlignRight); sessionKeysLayout->addWidget(sessionKeysImportBtn, 0, Qt::AlignRight);
auto boxWrap = [this, &font](QString labelText, QWidget *field) { auto boxWrap = [this, &font](QString labelText, QWidget *field, QString tooltipText = "") {
auto label = new QLabel{labelText, this}; auto label = new QLabel{labelText, this};
label->setFont(font); label->setFont(font);
label->setMargin(OptionMargin); label->setMargin(OptionMargin);
if (!tooltipText.isEmpty()) {
label->setToolTip(tooltipText);
}
auto layout = new QHBoxLayout; auto layout = new QHBoxLayout;
layout->addWidget(field, 0, Qt::AlignRight); layout->addWidget(field, 0, Qt::AlignRight);
@ -336,25 +503,70 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
formLayout_->addRow(general_); formLayout_->addRow(general_);
formLayout_->addRow(new HorizontalLine{this}); formLayout_->addRow(new HorizontalLine{this});
boxWrap(tr("Minimize to tray"), trayToggle_); boxWrap(
boxWrap(tr("Start in tray"), startInTrayToggle_); tr("Minimize to tray"),
trayToggle_,
tr("Keep the application running in the background after closing the client window."));
boxWrap(tr("Start in tray"),
startInTrayToggle_,
tr("Start the application in the background without showing the client window."));
formLayout_->addRow(new HorizontalLine{this}); formLayout_->addRow(new HorizontalLine{this});
boxWrap(tr("Circular Avatars"), avatarCircles_); boxWrap(tr("Circular Avatars"),
boxWrap(tr("Group's sidebar"), groupViewToggle_); avatarCircles_,
boxWrap(tr("Decrypt messages in sidebar"), decryptSidebar_); tr("Change the appearance of user avatars in chats.\nOFF - square, ON - Circle."));
boxWrap(tr("Show buttons in timeline"), timelineButtonsToggle_); boxWrap(tr("Group's sidebar"),
boxWrap(tr("Typing notifications"), typingNotifications_); groupViewToggle_,
boxWrap(tr("Sort rooms by unreads"), sortByImportance_); tr("Show a column containing groups and tags next to the room list."));
boxWrap(tr("Decrypt messages in sidebar"),
decryptSidebar_,
tr("Decrypt the messages shown in the sidebar.\nOnly affects messages in "
"encrypted chats."));
boxWrap(tr("Show buttons in timeline"),
timelineButtonsToggle_,
tr("Show buttons to quickly reply, react or access additional options next to each "
"message."));
boxWrap(tr("Limit width of timeline"),
timelineMaxWidthSpin_,
tr("Set the max width of messages in the timeline (in pixels). This can help "
"readability on wide screen, when Nheko is maximised"));
boxWrap(tr("Typing notifications"),
typingNotifications_,
tr("Show who is typing in a room.\nThis will also enable or disable sending typing "
"notifications to others."));
boxWrap(
tr("Sort rooms by unreads"),
sortByImportance_,
tr(
"Display rooms with new messages first.\nIf this is off, the list of rooms will only "
"be sorted by the timestamp of the last message in a room.\nIf this is on, rooms which "
"have active notifications (the small circle with a number in it) will be sorted on "
"top. Rooms, that you have muted, will still be sorted by timestamp, since you don't "
"seem to consider them as important as the other rooms."));
formLayout_->addRow(new HorizontalLine{this}); formLayout_->addRow(new HorizontalLine{this});
boxWrap(tr("Read receipts"), readReceipts_); boxWrap(tr("Read receipts"),
boxWrap(tr("Send messages as Markdown"), markdownEnabled_); readReceipts_,
boxWrap(tr("Desktop notifications"), desktopNotifications_); tr("Show if your message was read.\nStatus is displayed next to timestamps."));
boxWrap(tr("Highlight message on hover"), messageHoverHighlight_); boxWrap(
tr("Send messages as Markdown"),
markdown_,
tr("Allow using markdown in messages.\nWhen disabled, all messages are sent as a plain "
"text."));
boxWrap(tr("Desktop notifications"),
desktopNotifications_,
tr("Notify about received message when the client is not currently focused."));
boxWrap(tr("Highlight message on hover"),
messageHoverHighlight_,
tr("Change the background color of messages when you hover over them."));
boxWrap(tr("Large Emoji in timeline"),
enlargeEmojiOnlyMessages_,
tr("Make font size larger if messages with only a few emojis are displayed."));
formLayout_->addRow(uiLabel_); formLayout_->addRow(uiLabel_);
formLayout_->addRow(new HorizontalLine{this}); formLayout_->addRow(new HorizontalLine{this});
#if !defined(Q_OS_MAC) #if !defined(Q_OS_MAC)
boxWrap(tr("Scale factor"), scaleFactorCombo_); boxWrap(tr("Scale factor"),
scaleFactorCombo_,
tr("Change the scale factor of the whole user interface."));
#else #else
scaleFactorCombo_->hide(); scaleFactorCombo_->hide();
#endif #endif
@ -400,78 +612,87 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
topLayout_->addWidget(versionInfo); topLayout_->addWidget(versionInfo);
connect(themeCombo_, connect(themeCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated), static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &text) { [this](const QString &text) {
settings_->setTheme(text.toLower()); settings_->setTheme(text.toLower());
emit themeChanged(); emit themeChanged();
}); });
connect(scaleFactorCombo_, connect(scaleFactorCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated), static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[](const QString &factor) { utils::setScaleFactor(factor.toFloat()); }); [](const QString &factor) { utils::setScaleFactor(factor.toFloat()); });
connect(fontSizeCombo_, connect(fontSizeCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated), static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &size) { settings_->setFontSize(size.trimmed().toDouble()); }); [this](const QString &size) { settings_->setFontSize(size.trimmed().toDouble()); });
connect(fontSelectionCombo_, connect(fontSelectionCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated), static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &family) { settings_->setFontFamily(family.trimmed()); }); [this](const QString &family) { settings_->setFontFamily(family.trimmed()); });
connect(emojiFontSelectionCombo_, connect(emojiFontSelectionCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated), static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); }); [this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); });
connect(trayToggle_, &Toggle::toggled, this, [this](bool isDisabled) { connect(trayToggle_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setTray(!isDisabled); settings_->setTray(!disabled);
if (isDisabled) { if (disabled) {
startInTrayToggle_->setDisabled(true); startInTrayToggle_->setDisabled(true);
} else { } else {
startInTrayToggle_->setEnabled(true); startInTrayToggle_->setEnabled(true);
} }
emit trayOptionChanged(!isDisabled); emit trayOptionChanged(!disabled);
}); });
connect(startInTrayToggle_, &Toggle::toggled, this, [this](bool isDisabled) { connect(startInTrayToggle_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setStartInTray(!isDisabled); settings_->setStartInTray(!disabled);
}); });
connect(groupViewToggle_, &Toggle::toggled, this, [this](bool isDisabled) { connect(groupViewToggle_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setGroupView(!isDisabled); settings_->setGroupView(!disabled);
}); });
connect(decryptSidebar_, &Toggle::toggled, this, [this](bool isDisabled) { connect(decryptSidebar_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setDecryptSidebar(!isDisabled); settings_->setDecryptSidebar(!disabled);
emit decryptSidebarChanged(); emit decryptSidebarChanged();
}); });
connect(avatarCircles_, &Toggle::toggled, this, [this](bool isDisabled) { connect(avatarCircles_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setAvatarCircles(!isDisabled); settings_->setAvatarCircles(!disabled);
}); });
connect(markdownEnabled_, &Toggle::toggled, this, [this](bool isDisabled) { connect(markdown_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setMarkdownEnabled(!isDisabled); settings_->setMarkdown(!disabled);
}); });
connect(typingNotifications_, &Toggle::toggled, this, [this](bool isDisabled) { connect(typingNotifications_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setTypingNotifications(!isDisabled); settings_->setTypingNotifications(!disabled);
}); });
connect(sortByImportance_, &Toggle::toggled, this, [this](bool isDisabled) { connect(sortByImportance_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setSortByImportance(!isDisabled); settings_->setSortByImportance(!disabled);
}); });
connect(timelineButtonsToggle_, &Toggle::toggled, this, [this](bool isDisabled) { connect(timelineButtonsToggle_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setButtonsInTimeline(!isDisabled); settings_->setButtonsInTimeline(!disabled);
}); });
connect(readReceipts_, &Toggle::toggled, this, [this](bool isDisabled) { connect(readReceipts_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setReadReceipts(!isDisabled); settings_->setReadReceipts(!disabled);
}); });
connect(desktopNotifications_, &Toggle::toggled, this, [this](bool isDisabled) { connect(desktopNotifications_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setDesktopNotifications(!isDisabled); settings_->setDesktopNotifications(!disabled);
}); });
connect(messageHoverHighlight_, &Toggle::toggled, this, [this](bool isDisabled) { connect(messageHoverHighlight_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setMessageHoverHighlight(!isDisabled); settings_->setMessageHoverHighlight(!disabled);
}); });
connect(enlargeEmojiOnlyMessages_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setEnlargeEmojiOnlyMessages(!disabled);
});
connect(timelineMaxWidthSpin_,
qOverload<int>(&QSpinBox::valueChanged),
this,
[this](int newValue) { settings_->setTimelineMaxWidth(newValue); });
connect( connect(
sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys); sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys);
@ -493,19 +714,21 @@ UserSettingsPage::showEvent(QShowEvent *)
utils::restoreCombobox(themeCombo_, settings_->theme()); utils::restoreCombobox(themeCombo_, settings_->theme());
// FIXME: Toggle treats true as "off" // FIXME: Toggle treats true as "off"
trayToggle_->setState(!settings_->isTrayEnabled()); trayToggle_->setState(!settings_->tray());
startInTrayToggle_->setState(!settings_->isStartInTrayEnabled()); startInTrayToggle_->setState(!settings_->startInTray());
groupViewToggle_->setState(!settings_->isGroupViewEnabled()); groupViewToggle_->setState(!settings_->groupView());
decryptSidebar_->setState(!settings_->isDecryptSidebarEnabled()); decryptSidebar_->setState(!settings_->decryptSidebar());
avatarCircles_->setState(!settings_->isAvatarCirclesEnabled()); avatarCircles_->setState(!settings_->avatarCircles());
typingNotifications_->setState(!settings_->isTypingNotificationsEnabled()); typingNotifications_->setState(!settings_->typingNotifications());
sortByImportance_->setState(!settings_->isSortByImportanceEnabled()); sortByImportance_->setState(!settings_->sortByImportance());
timelineButtonsToggle_->setState(!settings_->isButtonsInTimelineEnabled()); timelineButtonsToggle_->setState(!settings_->buttonsInTimeline());
readReceipts_->setState(!settings_->isReadReceiptsEnabled()); readReceipts_->setState(!settings_->readReceipts());
markdownEnabled_->setState(!settings_->isMarkdownEnabled()); markdown_->setState(!settings_->markdown());
desktopNotifications_->setState(!settings_->hasDesktopNotifications()); desktopNotifications_->setState(!settings_->hasDesktopNotifications());
messageHoverHighlight_->setState(!settings_->isMessageHoverHighlightEnabled()); messageHoverHighlight_->setState(!settings_->messageHoverHighlight());
enlargeEmojiOnlyMessages_->setState(!settings_->enlargeEmojiOnlyMessages());
deviceIdValue_->setText(QString::fromStdString(http::client()->device_id())); deviceIdValue_->setText(QString::fromStdString(http::client()->device_id()));
timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth());
deviceFingerprintValue_->setText( deviceFingerprintValue_->setText(
utils::humanReadableFingerprint(olm::client()->identity_keys().ed25519)); utils::humanReadableFingerprint(olm::client()->identity_keys().ed25519));

View File

@ -17,17 +17,19 @@
#pragma once #pragma once
#include <QComboBox>
#include <QFontDatabase> #include <QFontDatabase>
#include <QFormLayout>
#include <QFrame> #include <QFrame>
#include <QLabel>
#include <QLayout>
#include <QProcessEnvironment> #include <QProcessEnvironment>
#include <QSharedPointer> #include <QSharedPointer>
#include <QWidget> #include <QWidget>
class Toggle; class Toggle;
class QLabel;
class QFormLayout;
class QComboBox;
class QSpinBox;
class QHBoxLayout;
class QVBoxLayout;
constexpr int OptionMargin = 6; constexpr int OptionMargin = 6;
constexpr int LayoutTopMargin = 50; constexpr int LayoutTopMargin = 50;
@ -37,6 +39,36 @@ class UserSettings : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged)
Q_PROPERTY(bool messageHoverHighlight READ messageHoverHighlight WRITE
setMessageHoverHighlight NOTIFY messageHoverHighlightChanged)
Q_PROPERTY(bool enlargeEmojiOnlyMessages READ enlargeEmojiOnlyMessages WRITE
setEnlargeEmojiOnlyMessages NOTIFY enlargeEmojiOnlyMessagesChanged)
Q_PROPERTY(bool tray READ tray WRITE setTray NOTIFY trayChanged)
Q_PROPERTY(bool startInTray READ startInTray WRITE setStartInTray NOTIFY startInTrayChanged)
Q_PROPERTY(bool groupView READ groupView WRITE setGroupView NOTIFY groupViewStateChanged)
Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged)
Q_PROPERTY(bool typingNotifications READ typingNotifications WRITE setTypingNotifications
NOTIFY typingNotificationsChanged)
Q_PROPERTY(bool sortByImportance READ sortByImportance WRITE setSortByImportance NOTIFY
roomSortingChanged)
Q_PROPERTY(bool buttonsInTimeline READ buttonsInTimeline WRITE setButtonsInTimeline NOTIFY
buttonInTimelineChanged)
Q_PROPERTY(
bool readReceipts READ readReceipts WRITE setReadReceipts NOTIFY readReceiptsChanged)
Q_PROPERTY(bool desktopNotifications READ hasDesktopNotifications WRITE
setDesktopNotifications NOTIFY desktopNotificationsChanged)
Q_PROPERTY(
bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged)
Q_PROPERTY(bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY
decryptSidebarChanged)
Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY
timelineMaxWidthChanged)
Q_PROPERTY(double fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged)
Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged)
Q_PROPERTY(
QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged)
public: public:
UserSettings(); UserSettings();
@ -44,104 +76,62 @@ public:
void load(); void load();
void applyTheme(); void applyTheme();
void setTheme(QString theme); void setTheme(QString theme);
void setMessageHoverHighlight(bool state) void setMessageHoverHighlight(bool state);
{ void setEnlargeEmojiOnlyMessages(bool state);
isMessageHoverHighlightEnabled_ = state; void setTray(bool state);
save(); void setStartInTray(bool state);
}
void setTray(bool state)
{
isTrayEnabled_ = state;
save();
}
void setStartInTray(bool state)
{
isStartInTrayEnabled_ = state;
save();
}
void setFontSize(double size); void setFontSize(double size);
void setFontFamily(QString family); void setFontFamily(QString family);
void setEmojiFontFamily(QString family); void setEmojiFontFamily(QString family);
void setGroupView(bool state);
void setGroupView(bool state) void setMarkdown(bool state);
{ void setReadReceipts(bool state);
if (isGroupViewEnabled_ != state) void setTypingNotifications(bool state);
emit groupViewStateChanged(state); void setSortByImportance(bool state);
void setButtonsInTimeline(bool state);
isGroupViewEnabled_ = state; void setTimelineMaxWidth(int state);
save(); void setDesktopNotifications(bool state);
} void setAvatarCircles(bool state);
void setDecryptSidebar(bool state);
void setMarkdownEnabled(bool state)
{
isMarkdownEnabled_ = state;
save();
}
void setReadReceipts(bool state)
{
isReadReceiptsEnabled_ = state;
save();
}
void setTypingNotifications(bool state)
{
isTypingNotificationsEnabled_ = state;
save();
}
void setSortByImportance(bool state)
{
sortByImportance_ = state;
emit roomSortingChanged();
}
void setButtonsInTimeline(bool state)
{
isButtonsInTimelineEnabled_ = state;
save();
}
void setDesktopNotifications(bool state)
{
hasDesktopNotifications_ = state;
save();
}
void setAvatarCircles(bool state)
{
avatarCircles_ = state;
save();
}
void setDecryptSidebar(bool state)
{
decryptSidebar_ = state;
save();
}
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; } QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
bool isMessageHoverHighlightEnabled() const { return isMessageHoverHighlightEnabled_; } bool messageHoverHighlight() const { return messageHoverHighlight_; }
bool isTrayEnabled() const { return isTrayEnabled_; } bool enlargeEmojiOnlyMessages() const { return enlargeEmojiOnlyMessages_; }
bool isStartInTrayEnabled() const { return isStartInTrayEnabled_; } bool tray() const { return tray_; }
bool isGroupViewEnabled() const { return isGroupViewEnabled_; } bool startInTray() const { return startInTray_; }
bool isAvatarCirclesEnabled() const { return avatarCircles_; } bool groupView() const { return groupView_; }
bool isDecryptSidebarEnabled() const { return decryptSidebar_; } bool avatarCircles() const { return avatarCircles_; }
bool isMarkdownEnabled() const { return isMarkdownEnabled_; } bool decryptSidebar() const { return decryptSidebar_; }
bool isTypingNotificationsEnabled() const { return isTypingNotificationsEnabled_; } bool markdown() const { return markdown_; }
bool isSortByImportanceEnabled() const { return sortByImportance_; } bool typingNotifications() const { return typingNotifications_; }
bool isButtonsInTimelineEnabled() const { return isButtonsInTimelineEnabled_; } bool sortByImportance() const { return sortByImportance_; }
bool isReadReceiptsEnabled() const { return isReadReceiptsEnabled_; } bool buttonsInTimeline() const { return buttonsInTimeline_; }
bool readReceipts() const { return readReceipts_; }
bool hasDesktopNotifications() const { return hasDesktopNotifications_; } bool hasDesktopNotifications() const { return hasDesktopNotifications_; }
int timelineMaxWidth() const { return timelineMaxWidth_; }
double fontSize() const { return baseFontSize_; } double fontSize() const { return baseFontSize_; }
QString font() const { return font_; } QString font() const { return font_; }
QString emojiFont() const { return emojiFont_; } QString emojiFont() const { return emojiFont_; }
signals: signals:
void groupViewStateChanged(bool state); void groupViewStateChanged(bool state);
void roomSortingChanged(); void roomSortingChanged(bool state);
void themeChanged(QString state);
void messageHoverHighlightChanged(bool state);
void enlargeEmojiOnlyMessagesChanged(bool state);
void trayChanged(bool state);
void startInTrayChanged(bool state);
void markdownChanged(bool state);
void typingNotificationsChanged(bool state);
void buttonInTimelineChanged(bool state);
void readReceiptsChanged(bool state);
void desktopNotificationsChanged(bool state);
void avatarCirclesChanged(bool state);
void decryptSidebarChanged(bool state);
void timelineMaxWidthChanged(int state);
void fontSizeChanged(double state);
void fontChanged(QString state);
void emojiFontChanged(QString state);
private: private:
// Default to system theme if QT_QPA_PLATFORMTHEME var is set. // Default to system theme if QT_QPA_PLATFORMTHEME var is set.
@ -150,18 +140,20 @@ private:
? "light" ? "light"
: "system"; : "system";
QString theme_; QString theme_;
bool isMessageHoverHighlightEnabled_; bool messageHoverHighlight_;
bool isTrayEnabled_; bool enlargeEmojiOnlyMessages_;
bool isStartInTrayEnabled_; bool tray_;
bool isGroupViewEnabled_; bool startInTray_;
bool isMarkdownEnabled_; bool groupView_;
bool isTypingNotificationsEnabled_; bool markdown_;
bool typingNotifications_;
bool sortByImportance_; bool sortByImportance_;
bool isButtonsInTimelineEnabled_; bool buttonsInTimeline_;
bool isReadReceiptsEnabled_; bool readReceipts_;
bool hasDesktopNotifications_; bool hasDesktopNotifications_;
bool avatarCircles_; bool avatarCircles_;
bool decryptSidebar_; bool decryptSidebar_;
int timelineMaxWidth_;
double baseFontSize_; double baseFontSize_;
QString font_; QString font_;
QString emojiFont_; QString emojiFont_;
@ -211,9 +203,10 @@ private:
Toggle *timelineButtonsToggle_; Toggle *timelineButtonsToggle_;
Toggle *typingNotifications_; Toggle *typingNotifications_;
Toggle *messageHoverHighlight_; Toggle *messageHoverHighlight_;
Toggle *enlargeEmojiOnlyMessages_;
Toggle *sortByImportance_; Toggle *sortByImportance_;
Toggle *readReceipts_; Toggle *readReceipts_;
Toggle *markdownEnabled_; Toggle *markdown_;
Toggle *desktopNotifications_; Toggle *desktopNotifications_;
Toggle *avatarCircles_; Toggle *avatarCircles_;
Toggle *decryptSidebar_; Toggle *decryptSidebar_;
@ -226,5 +219,7 @@ private:
QComboBox *fontSelectionCombo_; QComboBox *fontSelectionCombo_;
QComboBox *emojiFontSelectionCombo_; QComboBox *emojiFontSelectionCombo_;
QSpinBox *timelineMaxWidthSpin_;
int sideMargin_ = 0; int sideMargin_ = 0;
}; };

View File

@ -51,6 +51,14 @@ utils::localUser()
return QString::fromStdString(http::client()->user_id().to_string()); return QString::fromStdString(http::client()->user_id().to_string());
} }
bool
utils::codepointIsEmoji(uint code)
{
// TODO: Be more precise here.
return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x1f300 && code <= 0x1f3ff) ||
(code >= 0x1f000 && code <= 0x1faff);
}
QString QString
utils::replaceEmoji(const QString &body) utils::replaceEmoji(const QString &body)
{ {
@ -63,9 +71,7 @@ utils::replaceEmoji(const QString &body)
bool insideFontBlock = false; bool insideFontBlock = false;
for (auto &code : utf32_string) { for (auto &code : utf32_string) {
// TODO: Be more precise here. if (utils::codepointIsEmoji(code)) {
if ((code >= 0x2600 && code <= 0x27bf) || (code >= 0x1f300 && code <= 0x1f3ff) ||
(code >= 0x1f000 && code <= 0x1faff)) {
if (!insideFontBlock) { if (!insideFontBlock) {
fmtBody += QString("<font face=\"" + userFontFamily + "\">"); fmtBody += QString("<font face=\"" + userFontFamily + "\">");
insideFontBlock = true; insideFontBlock = true;
@ -136,13 +142,13 @@ utils::descriptiveTime(const QDateTime &then)
const auto days = then.daysTo(now); const auto days = then.daysTo(now);
if (days == 0) if (days == 0)
return then.time().toString(Qt::DefaultLocaleShortDate); return QLocale::system().toString(then.time(), QLocale::ShortFormat);
else if (days < 2) else if (days < 2)
return QString(QCoreApplication::translate("descriptiveTime", "Yesterday")); return QString(QCoreApplication::translate("descriptiveTime", "Yesterday"));
else if (days < 7) else if (days < 7)
return then.toString("dddd"); return then.toString("dddd");
return then.date().toString(Qt::DefaultLocaleShortDate); return QLocale::system().toString(then.date(), QLocale::ShortFormat);
} }
DescInfo DescInfo

View File

@ -36,6 +36,9 @@ namespace utils {
using TimelineEvent = mtx::events::collections::TimelineEvents; using TimelineEvent = mtx::events::collections::TimelineEvents;
bool
codepointIsEmoji(uint code);
QString QString
replaceEmoji(const QString &body); replaceEmoji(const QString &body);
@ -183,42 +186,6 @@ erase_if(ContainerT &items, const PredicateT &predicate)
} }
} }
inline uint64_t
event_timestamp(const mtx::events::collections::TimelineEvents &event)
{
return std::visit([](auto msg) { return msg.origin_server_ts; }, event);
}
inline nlohmann::json
serialize_event(const mtx::events::collections::TimelineEvents &event)
{
return std::visit([](auto msg) { return json(msg); }, event);
}
inline mtx::events::EventType
event_type(const mtx::events::collections::TimelineEvents &event)
{
return std::visit([](auto msg) { return msg.type; }, event);
}
inline std::string
event_id(const mtx::events::collections::TimelineEvents &event)
{
return std::visit([](auto msg) { return msg.event_id; }, event);
}
inline QString
eventId(const mtx::events::collections::TimelineEvents &event)
{
return QString::fromStdString(event_id(event));
}
inline QString
event_sender(const mtx::events::collections::TimelineEvents &event)
{
return std::visit([](auto msg) { return QString::fromStdString(msg.sender); }, event);
}
template<class T> template<class T>
QString QString
message_body(const mtx::events::collections::TimelineEvents &event) message_body(const mtx::events::collections::TimelineEvents &event)

View File

@ -112,7 +112,7 @@ CreateRoom::CreateRoom(QWidget *parent)
}); });
connect(visibilityCombo_, connect(visibilityCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated), static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &text) { [this](const QString &text) {
if (text == "Private") { if (text == "Private") {
request_.visibility = mtx::requests::Visibility::Private; request_.visibility = mtx::requests::Visibility::Private;
@ -122,7 +122,7 @@ CreateRoom::CreateRoom(QWidget *parent)
}); });
connect(presetCombo_, connect(presetCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated), static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &text) { [this](const QString &text) {
if (text == "Private Chat") { if (text == "Private Chat") {
request_.preset = mtx::requests::Preset::PrivateChat; request_.preset = mtx::requests::Preset::PrivateChat;

View File

@ -1,5 +1,6 @@
#include <QDebug> #include <QDebug>
#include <QIcon> #include <QIcon>
#include <QLabel>
#include <QListWidget> #include <QListWidget>
#include <QListWidgetItem> #include <QListWidgetItem>
#include <QPushButton> #include <QPushButton>

View File

@ -1,13 +1,13 @@
#pragma once #pragma once
#include <QFrame> #include <QFrame>
#include <QLabel>
#include <QListWidgetItem>
#include <QStringList> #include <QStringList>
class QPushButton; class QPushButton;
class QLabel;
class TextField; class TextField;
class QListWidget; class QListWidget;
class QListWidgetItem;
namespace dialogs { namespace dialogs {

View File

@ -1,5 +1,6 @@
#include <QDebug> #include <QDebug>
#include <QIcon> #include <QIcon>
#include <QLabel>
#include <QListWidgetItem> #include <QListWidgetItem>
#include <QPainter> #include <QPainter>
#include <QPushButton> #include <QPushButton>
@ -74,15 +75,17 @@ ReceiptItem::dateFormat(const QDateTime &then) const
auto days = then.daysTo(now); auto days = then.daysTo(now);
if (days == 0) if (days == 0)
return tr("Today %1").arg(then.time().toString(Qt::DefaultLocaleShortDate)); return tr("Today %1")
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
else if (days < 2) else if (days < 2)
return tr("Yesterday %1").arg(then.time().toString(Qt::DefaultLocaleShortDate)); return tr("Yesterday %1")
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
else if (days < 7) else if (days < 7)
return QString("%1 %2") return QString("%1 %2")
.arg(then.toString("dddd")) .arg(then.toString("dddd"))
.arg(then.time().toString(Qt::DefaultLocaleShortDate)); .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
return then.toString(Qt::DefaultLocaleShortDate); return QLocale::system().toString(then.time(), QLocale::ShortFormat);
} }
ReadReceipts::ReadReceipts(QWidget *parent) ReadReceipts::ReadReceipts(QWidget *parent)
@ -163,3 +166,10 @@ ReadReceipts::paintEvent(QPaintEvent *)
QPainter p(this); QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
} }
void
ReadReceipts::hideEvent(QHideEvent *event)
{
userList_->clear();
QFrame::hideEvent(event);
}

View File

@ -2,12 +2,12 @@
#include <QDateTime> #include <QDateTime>
#include <QFrame> #include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QVBoxLayout>
class Avatar; class Avatar;
class QLabel;
class QListWidget;
class QHBoxLayout;
class QVBoxLayout;
namespace dialogs { namespace dialogs {
@ -47,11 +47,7 @@ public slots:
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void hideEvent(QHideEvent *event) override void hideEvent(QHideEvent *event) override;
{
userList_->clear();
QFrame::hideEvent(event);
}
private: private:
QLabel *topLabel_; QLabel *topLabel_;

View File

@ -1,5 +1,6 @@
#include <QApplication> #include <QApplication>
#include <QComboBox> #include <QComboBox>
#include <QEvent>
#include <QFileDialog> #include <QFileDialog>
#include <QFontDatabase> #include <QFontDatabase>
#include <QImageReader> #include <QImageReader>
@ -41,6 +42,17 @@ constexpr int WIDGET_SPACING = 15;
constexpr int TEXT_SPACING = 4; constexpr int TEXT_SPACING = 4;
constexpr int BUTTON_SPACING = 2 * TEXT_SPACING; constexpr int BUTTON_SPACING = 2 * TEXT_SPACING;
bool
ClickableFilter::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseButtonRelease) {
emit clicked();
return true;
}
return QObject::eventFilter(obj, event);
}
EditModal::EditModal(const QString &roomId, QWidget *parent) EditModal::EditModal(const QString &roomId, QWidget *parent)
: QWidget(parent) : QWidget(parent)
, roomId_{roomId} , roomId_{roomId}
@ -93,6 +105,28 @@ EditModal::EditModal(const QString &roomId, QWidget *parent)
move(center.x() - (width() * 0.5), center.y() - (height() * 0.5)); move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
} }
void
EditModal::topicEventSent()
{
errorField_->hide();
close();
}
void
EditModal::nameEventSent(const QString &name)
{
errorField_->hide();
emit nameChanged(name);
close();
}
void
EditModal::error(const QString &msg)
{
errorField_->setText(msg);
errorField_->show();
}
void void
EditModal::applyClicked() EditModal::applyClicked()
{ {

View File

@ -1,9 +1,7 @@
#pragma once #pragma once
#include <QEvent>
#include <QFrame> #include <QFrame>
#include <QImage> #include <QImage>
#include <QLabel>
#include <mtx/events/guest_access.hpp> #include <mtx/events/guest_access.hpp>
@ -21,6 +19,8 @@ class QPixmap;
class TextField; class TextField;
class TextField; class TextField;
class Toggle; class Toggle;
class QLabel;
class QEvent;
class ClickableFilter : public QObject class ClickableFilter : public QObject
{ {
@ -35,15 +35,7 @@ signals:
void clicked(); void clicked();
protected: protected:
bool eventFilter(QObject *obj, QEvent *event) override bool eventFilter(QObject *obj, QEvent *event) override;
{
if (event->type() == QEvent::MouseButtonRelease) {
emit clicked();
return true;
}
return QObject::eventFilter(obj, event);
}
}; };
/// Convenience class which connects events emmited from threads /// Convenience class which connects events emmited from threads
@ -72,24 +64,9 @@ signals:
void nameChanged(const QString &roomName); void nameChanged(const QString &roomName);
private slots: private slots:
void topicEventSent() void topicEventSent();
{ void nameEventSent(const QString &name);
errorField_->hide(); void error(const QString &msg);
close();
}
void nameEventSent(const QString &name)
{
errorField_->hide();
emit nameChanged(name);
close();
}
void error(const QString &msg)
{
errorField_->setText(msg);
errorField_->show();
}
void applyClicked(); void applyClicked();

View File

@ -15,9 +15,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <QLabel>
#include <QListView>
#include <QPainter> #include <QPainter>
#include <QScrollBar> #include <QScrollBar>
#include <QStyleOption> #include <QStyleOption>
#include <QVBoxLayout>
#include "Config.h" #include "Config.h"

View File

@ -18,13 +18,14 @@
#pragma once #pragma once
#include <QColor> #include <QColor>
#include <QLabel>
#include <QLayout>
#include <QListView>
#include <QStandardItemModel>
#include "ItemDelegate.h" #include "ItemDelegate.h"
class QLabel;
class QListView;
class QStandardItemModel;
class QVBoxLayout;
namespace emoji { namespace emoji {
class Category : public QWidget class Category : public QWidget

View File

@ -1,3 +1,4 @@
#include <QLabel>
#include <QPaintEvent> #include <QPaintEvent>
#include <QPainter> #include <QPainter>
#include <QStyleOption> #include <QStyleOption>

View File

@ -1,8 +1,5 @@
#pragma once #pragma once
#include <QHBoxLayout>
#include <QLabel>
#include <QPoint>
#include <QWidget> #include <QWidget>
#include "../AvatarProvider.h" #include "../AvatarProvider.h"
@ -10,6 +7,8 @@
class Avatar; class Avatar;
struct SearchResult; struct SearchResult;
class QLabel;
class QHBoxLayout;
class PopupItem : public QWidget class PopupItem : public QWidget
{ {

View File

@ -1,8 +1,5 @@
#pragma once #pragma once
#include <QHBoxLayout>
#include <QLabel>
#include <QPoint>
#include <QWidget> #include <QWidget>
#include "CacheStructs.h" #include "CacheStructs.h"

View File

@ -8,9 +8,9 @@
#include "Cache.h" #include "Cache.h"
#include "ChatPage.h" #include "ChatPage.h"
#include "EventAccessors.h"
#include "Logging.h" #include "Logging.h"
#include "UserMentions.h" #include "UserMentions.h"
//#include "timeline/TimelineItem.h"
using namespace popups; using namespace popups;
@ -75,12 +75,15 @@ UserMentions::initializeMentions(const QMap<QString, mtx::responses::Notificatio
for (const auto &item : notifs) { for (const auto &item : notifs) {
for (const auto &notif : item.notifications) { for (const auto &notif : item.notifications) {
const auto event_id = QString::fromStdString(utils::event_id(notif.event)); const auto event_id =
QString::fromStdString(mtx::accessors::event_id(notif.event));
try { try {
const auto room_id = QString::fromStdString(notif.room_id); const auto room_id = QString::fromStdString(notif.room_id);
const auto user_id = utils::event_sender(notif.event); const auto user_id =
const auto body = utils::event_body(notif.event); QString::fromStdString(mtx::accessors::sender(notif.event));
const auto body =
QString::fromStdString(mtx::accessors::body(notif.event));
pushItem(event_id, pushItem(event_id,
user_id, user_id,

View File

@ -219,6 +219,7 @@ TimelineModel::roleNames() const
{Section, "section"}, {Section, "section"},
{Type, "type"}, {Type, "type"},
{TypeString, "typeString"}, {TypeString, "typeString"},
{IsOnlyEmoji, "isOnlyEmoji"},
{Body, "body"}, {Body, "body"},
{FormattedBody, "formattedBody"}, {FormattedBody, "formattedBody"},
{UserId, "userId"}, {UserId, "userId"},
@ -284,6 +285,22 @@ TimelineModel::data(const QString &id, int role) const
return QVariant(toRoomEventType(event)); return QVariant(toRoomEventType(event));
case TypeString: case TypeString:
return QVariant(toRoomEventTypeString(event)); return QVariant(toRoomEventTypeString(event));
case IsOnlyEmoji: {
QString qBody = QString::fromStdString(body(event));
QVector<uint> utf32_string = qBody.toUcs4();
int emojiCount = 0;
for (auto &code : utf32_string) {
if (utils::codepointIsEmoji(code)) {
emojiCount++;
} else {
return QVariant(0);
}
}
return QVariant(emojiCount);
}
case Body: case Body:
return QVariant(utils::replaceEmoji(QString::fromStdString(body(event)))); return QVariant(utils::replaceEmoji(QString::fromStdString(body(event))));
case FormattedBody: { case FormattedBody: {
@ -386,6 +403,7 @@ TimelineModel::data(const QString &id, int role) const
// m.insert(names[Section], data(id, static_cast<int>(Section))); // m.insert(names[Section], data(id, static_cast<int>(Section)));
m.insert(names[Type], data(id, static_cast<int>(Type))); m.insert(names[Type], data(id, static_cast<int>(Type)));
m.insert(names[TypeString], data(id, static_cast<int>(TypeString))); m.insert(names[TypeString], data(id, static_cast<int>(TypeString)));
m.insert(names[IsOnlyEmoji], data(id, static_cast<int>(IsOnlyEmoji)));
m.insert(names[Body], data(id, static_cast<int>(Body))); m.insert(names[Body], data(id, static_cast<int>(Body)));
m.insert(names[FormattedBody], data(id, static_cast<int>(FormattedBody))); m.insert(names[FormattedBody], data(id, static_cast<int>(FormattedBody)));
m.insert(names[UserId], data(id, static_cast<int>(UserId))); m.insert(names[UserId], data(id, static_cast<int>(UserId)));
@ -810,7 +828,7 @@ TimelineModel::escapeEmoji(QString str) const
void void
TimelineModel::viewRawMessage(QString id) const TimelineModel::viewRawMessage(QString id) const
{ {
std::string ev = utils::serialize_event(events.value(id)).dump(4); std::string ev = mtx::accessors::serialize_event(events.value(id)).dump(4);
auto dialog = new dialogs::RawMessage(QString::fromStdString(ev)); auto dialog = new dialogs::RawMessage(QString::fromStdString(ev));
Q_UNUSED(dialog); Q_UNUSED(dialog);
} }
@ -824,7 +842,7 @@ TimelineModel::viewDecryptedRawMessage(QString id) const
event = decryptEvent(*e).event; event = decryptEvent(*e).event;
} }
std::string ev = utils::serialize_event(event).dump(4); std::string ev = mtx::accessors::serialize_event(event).dump(4);
auto dialog = new dialogs::RawMessage(QString::fromStdString(ev)); auto dialog = new dialogs::RawMessage(QString::fromStdString(ev));
Q_UNUSED(dialog); Q_UNUSED(dialog);
} }
@ -1849,6 +1867,8 @@ TimelineModel::formatMemberEvent(QString id)
rendered = tr("%1 changed their display name.").arg(name); rendered = tr("%1 changed their display name.").arg(name);
else if (avatarChanged) else if (avatarChanged)
rendered = tr("%1 changed their avatar.").arg(name); rendered = tr("%1 changed their avatar.").arg(name);
else
rendered = tr("%1 changed some profile info.").arg(name);
// the case of nothing changed but join follows join shouldn't happen, so // the case of nothing changed but join follows join shouldn't happen, so
// just show it as join // just show it as join
} else { } else {

View File

@ -142,6 +142,7 @@ public:
Section, Section,
Type, Type,
TypeString, TypeString,
IsOnlyEmoji,
Body, Body,
FormattedBody, FormattedBody,
UserId, UserId,

View File

@ -21,7 +21,7 @@ Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
void void
TimelineViewManager::updateEncryptedDescriptions() TimelineViewManager::updateEncryptedDescriptions()
{ {
auto decrypt = settings->isDecryptSidebarEnabled(); auto decrypt = settings->decryptSidebar();
QHash<QString, QSharedPointer<TimelineModel>>::iterator i; QHash<QString, QSharedPointer<TimelineModel>>::iterator i;
for (i = models.begin(); i != models.end(); ++i) { for (i = models.begin(); i != models.end(); ++i) {
auto ptr = i.value(); auto ptr = i.value();
@ -96,6 +96,7 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin
#endif #endif
container->setMinimumSize(200, 200); container->setMinimumSize(200, 200);
view->rootContext()->setContextProperty("timelineManager", this); view->rootContext()->setContextProperty("timelineManager", this);
view->rootContext()->setContextProperty("settings", settings.data());
updateColorPalette(); updateColorPalette();
view->engine()->addImageProvider("MxcImage", imgProvider); view->engine()->addImageProvider("MxcImage", imgProvider);
view->engine()->addImageProvider("colorimage", colorImgProvider); view->engine()->addImageProvider("colorimage", colorImgProvider);
@ -121,7 +122,7 @@ TimelineViewManager::sync(const mtx::responses::Rooms &rooms)
const auto &room_model = models.value(QString::fromStdString(room_id)); const auto &room_model = models.value(QString::fromStdString(room_id));
room_model->addEvents(room.timeline); room_model->addEvents(room.timeline);
if (ChatPage::instance()->userSettings()->isTypingNotificationsEnabled()) { if (ChatPage::instance()->userSettings()->typingNotifications()) {
std::vector<QString> typing; std::vector<QString> typing;
typing.reserve(room.ephemeral.typing.size()); typing.reserve(room.ephemeral.typing.size());
for (const auto &user : room.ephemeral.typing) { for (const auto &user : room.ephemeral.typing) {
@ -141,7 +142,7 @@ TimelineViewManager::addRoom(const QString &room_id)
{ {
if (!models.contains(room_id)) { if (!models.contains(room_id)) {
QSharedPointer<TimelineModel> newRoom(new TimelineModel(this, room_id)); QSharedPointer<TimelineModel> newRoom(new TimelineModel(this, room_id));
newRoom->setDecryptDescription(settings->isDecryptSidebarEnabled()); newRoom->setDecryptDescription(settings->decryptSidebar());
connect(newRoom.data(), connect(newRoom.data(),
&TimelineModel::newEncryptedImage, &TimelineModel::newEncryptedImage,
@ -225,7 +226,7 @@ TimelineViewManager::queueTextMessage(const QString &msg)
mtx::events::msg::Text text = {}; mtx::events::msg::Text text = {};
text.body = msg.trimmed().toStdString(); text.body = msg.trimmed().toStdString();
if (settings->isMarkdownEnabled()) { if (settings->markdown()) {
text.formatted_body = utils::markdownToHtml(msg).toStdString(); text.formatted_body = utils::markdownToHtml(msg).toStdString();
// Don't send formatted_body, when we don't need to // Don't send formatted_body, when we don't need to
@ -253,7 +254,7 @@ TimelineViewManager::queueTextMessage(const QString &msg)
// NOTE(Nico): rich replies always need a formatted_body! // NOTE(Nico): rich replies always need a formatted_body!
text.format = "org.matrix.custom.html"; text.format = "org.matrix.custom.html";
if (settings->isMarkdownEnabled()) if (settings->markdown())
text.formatted_body = text.formatted_body =
utils::getFormattedQuoteBody(related, utils::markdownToHtml(msg)) utils::getFormattedQuoteBody(related, utils::markdownToHtml(msg))
.toStdString(); .toStdString();
@ -276,7 +277,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
mtx::events::msg::Emote emote; mtx::events::msg::Emote emote;
emote.body = msg.trimmed().toStdString(); emote.body = msg.trimmed().toStdString();
if (html != msg.trimmed().toHtmlEscaped() && settings->isMarkdownEnabled()) { if (html != msg.trimmed().toHtmlEscaped() && settings->markdown()) {
emote.formatted_body = html.toStdString(); emote.formatted_body = html.toStdString();
emote.format = "org.matrix.custom.html"; emote.format = "org.matrix.custom.html";
} }

View File

@ -1,4 +1,5 @@
#include <QPainter> #include <QPainter>
#include <QPainterPath>
#include <QSettings> #include <QSettings>
#include "AvatarProvider.h" #include "AvatarProvider.h"

View File

@ -4,6 +4,7 @@
#include <QDateTime> #include <QDateTime>
#include <QLocale> #include <QLocale>
#include <QPainter> #include <QPainter>
#include <QPainterPath>
#include <QPen> #include <QPen>
#include <QtGlobal> #include <QtGlobal>

View File

@ -3,6 +3,7 @@
#include <QFontMetrics> #include <QFontMetrics>
#include <QPaintDevice> #include <QPaintDevice>
#include <QPainter> #include <QPainter>
#include <QPainterPath>
#include <QtGlobal> #include <QtGlobal>
class Painter : public QPainter class Painter : public QPainter
@ -163,5 +164,5 @@ public:
private: private:
Painter &_painter; Painter &_painter;
QPainter::RenderHints hints_ = 0; QPainter::RenderHints hints_ = {};
}; };

View File

@ -6,4 +6,11 @@
/examples/basic/basic /examples/basic/basic
/examples/calculator/calculator /examples/calculator/calculator
/examples/sending_arguments/sending_arguments /examples/sending_arguments/sending_arguments
CMakeLists.txt.user /**/CMakeLists.txt.user
/**/CMakeCache.txt
/**/CMakeCache/*
/**/CMakeFiles/*
/**/Makefile
/**/cmake_install.cmake
/**/*_autogen/
libSingleApplication.a

View File

@ -1,6 +1,40 @@
Changelog Changelog
========= =========
If by accident I have forgotten to credit someone in the CHANGELOG, email me and I will fix it.
__3.1.3.1__
---------
* CMake build system improvements
* Fixed Clang Tidy warnings
_Hennadii Chernyshchyk_
__3.1.3__
---------
* Improved `CMakeLists.txt`
_Hennadii Chernyshchyk_
__3.1.2__
---------
* Fix a crash when exiting an application on Android and iOS
_Emeric Grange_
__3.1.1a__
----------
* Added currentUser() method that returns the user the current instance is running as.
_Leander Schulten_
__3.1.0a__
----------
* Added primaryUser() method that returns the user the primary instance is running as.
__3.0.19__ __3.0.19__
---------- ----------

View File

@ -1,45 +1,34 @@
cmake_minimum_required(VERSION 3.1.0) cmake_minimum_required(VERSION 3.7.0)
project(SingleApplication) project(SingleApplication LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
# SingleApplication base class
set(QAPPLICATION_CLASS QCoreApplication CACHE STRING "Inheritance class for SingleApplication")
set_property(CACHE QAPPLICATION_CLASS PROPERTY STRINGS QApplication QGuiApplication QCoreApplication)
# Libary target
add_library(${PROJECT_NAME} STATIC add_library(${PROJECT_NAME} STATIC
singleapplication.cpp singleapplication.cpp
singleapplication_p.cpp singleapplication_p.cpp
) )
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
# Find dependencies # Find dependencies
find_package(Qt5Network) find_package(Qt5 COMPONENTS Network REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Network)
if(QAPPLICATION_CLASS STREQUAL QApplication) if(QAPPLICATION_CLASS STREQUAL QApplication)
find_package(Qt5 COMPONENTS Widgets REQUIRED) find_package(Qt5 COMPONENTS Widgets REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Widgets)
elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication) elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication)
find_package(Qt5 COMPONENTS Gui REQUIRED) find_package(Qt5 COMPONENTS Gui REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Gui)
else() else()
set(QAPPLICATION_CLASS QCoreApplication)
find_package(Qt5 COMPONENTS Core REQUIRED) find_package(Qt5 COMPONENTS Core REQUIRED)
endif() target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Core)
target_compile_definitions(${PROJECT_NAME} PUBLIC QAPPLICATION_CLASS=${QAPPLICATION_CLASS})
# Link dependencies
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Network)
if(QAPPLICATION_CLASS STREQUAL QApplication)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets)
elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Gui)
else()
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core)
endif() endif()
if(WIN32) if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE advapi32) target_link_libraries(${PROJECT_NAME} PRIVATE advapi32)
endif() endif()
target_compile_definitions(${PROJECT_NAME} PUBLIC QAPPLICATION_CLASS=${QAPPLICATION_CLASS})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})

View File

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) Itay Grudev 2015 - 2016 Copyright (c) Itay Grudev 2015 - 2020
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,5 +1,6 @@
SingleApplication SingleApplication
================= =================
[![CI](https://github.com/itay-grudev/SingleApplication/workflows/CI:%20Build%20Test/badge.svg)](https://github.com/itay-grudev/SingleApplication/actions)
This is a replacement of the QtSingleApplication for `Qt5`. This is a replacement of the QtSingleApplication for `Qt5`.
@ -15,18 +16,6 @@ class you specify via the `QAPPLICATION_CLASS` macro (`QCoreApplication` is the
default). Further usage is similar to the use of the `Q[Core|Gui]Application` default). Further usage is similar to the use of the `Q[Core|Gui]Application`
classes. classes.
The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
instance of your Application is your Primary Instance. It would check if the
shared memory block exists and if not it will start a `QLocalServer` and listen
for connections. Each subsequent instance of your application would check if the
shared memory block exists and if it does, it will connect to the QLocalServer
to notify the primary instance that a new instance had been started, after which
it would terminate with status code `0`. In the Primary Instance
`SingleApplication` would emit the `instanceStarted()` signal upon detecting
that a new instance had been started.
The library uses `stdlib` to terminate the program with the `exit()` function.
You can use the library as if you use any other `QCoreApplication` derived You can use the library as if you use any other `QCoreApplication` derived
class: class:
@ -43,8 +32,7 @@ int main( int argc, char* argv[] )
``` ```
To include the library files I would recommend that you add it as a git To include the library files I would recommend that you add it as a git
submodule to your project and include it's contents with a `.pri` file. Here is submodule to your project. Here is how:
how:
```bash ```bash
git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication
@ -66,13 +54,27 @@ Then include the subdirectory in your `CMakeLists.txt` project file.
```cmake ```cmake
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
add_subdirectory(src/third-party/singleapplication) add_subdirectory(src/third-party/singleapplication)
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)
``` ```
The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
instance of your Application is your Primary Instance. It would check if the
shared memory block exists and if not it will start a `QLocalServer` and listen
for connections. Each subsequent instance of your application would check if the
shared memory block exists and if it does, it will connect to the QLocalServer
to notify the primary instance that a new instance had been started, after which
it would terminate with status code `0`. In the Primary Instance
`SingleApplication` would emit the `instanceStarted()` signal upon detecting
that a new instance had been started.
The library uses `stdlib` to terminate the program with the `exit()` function.
Also don't forget to specify which `QCoreApplication` class your app is using if it Also don't forget to specify which `QCoreApplication` class your app is using if it
is not `QCoreApplication` as in examples above. is not `QCoreApplication` as in examples above.
The `Instance Started` signal The `Instance Started` signal
------------------------ -----------------------------
The SingleApplication class implements a `instanceStarted()` signal. You can The SingleApplication class implements a `instanceStarted()` signal. You can
bind to that signal to raise your application's window when a new instance had bind to that signal to raise your application's window when a new instance had
@ -204,6 +206,22 @@ qint64 SingleApplication::primaryPid()
Returns the process ID (PID) of the primary instance. Returns the process ID (PID) of the primary instance.
---
```cpp
QString SingleApplication::primaryUser()
```
Returns the username the primary instance is running as.
---
```cpp
QString SingleApplication::currentUser()
```
Returns the username the current instance is running as.
### Signals ### Signals
```cpp ```cpp

View File

@ -0,0 +1 @@
#include "singleapplication.h"

View File

@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2018 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -85,7 +85,7 @@ SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSeconda
} }
} }
InstancesInfo* inst = static_cast<InstancesInfo*>( d->memory->data() ); auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
QElapsedTimer time; QElapsedTimer time;
time.start(); time.start();
@ -172,7 +172,19 @@ qint64 SingleApplication::primaryPid()
return d->primaryPid(); return d->primaryPid();
} }
bool SingleApplication::sendMessage( QByteArray message, int timeout ) QString SingleApplication::primaryUser()
{
Q_D(SingleApplication);
return d->primaryUser();
}
QString SingleApplication::currentUser()
{
Q_D(SingleApplication);
return d->getUsername();
}
bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
{ {
Q_D(SingleApplication); Q_D(SingleApplication);

View File

@ -43,7 +43,7 @@ class SingleApplication : public QAPPLICATION_CLASS
{ {
Q_OBJECT Q_OBJECT
typedef QAPPLICATION_CLASS app_t; using app_t = QAPPLICATION_CLASS;
public: public:
/** /**
@ -86,7 +86,7 @@ public:
* @see See the corresponding QAPPLICATION_CLASS constructor for reference * @see See the corresponding QAPPLICATION_CLASS constructor for reference
*/ */
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 ); explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 );
~SingleApplication(); ~SingleApplication() override;
/** /**
* @brief Returns if the instance is the primary instance * @brief Returns if the instance is the primary instance
@ -112,6 +112,18 @@ public:
*/ */
qint64 primaryPid(); qint64 primaryPid();
/**
* @brief Returns the username of the user running the primary instance
* @returns {QString}
*/
QString primaryUser();
/**
* @brief Returns the username of the current user
* @returns {QString}
*/
QString currentUser();
/** /**
* @brief Sends a message to the primary instance. Returns true on success. * @brief Sends a message to the primary instance. Returns true on success.
* @param {int} timeout - Timeout for connecting * @param {int} timeout - Timeout for connecting
@ -119,7 +131,7 @@ public:
* @note sendMessage() will return false if invoked from the primary * @note sendMessage() will return false if invoked from the primary
* instance. * instance.
*/ */
bool sendMessage( QByteArray message, int timeout = 100 ); bool sendMessage( const QByteArray &message, int timeout = 100 );
Q_SIGNALS: Q_SIGNALS:
void instanceStarted(); void instanceStarted();

View File

@ -0,0 +1,20 @@
QT += core network
CONFIG += c++11
HEADERS += $$PWD/SingleApplication \
$$PWD/singleapplication.h \
$$PWD/singleapplication_p.h
SOURCES += $$PWD/singleapplication.cpp \
$$PWD/singleapplication_p.cpp
INCLUDEPATH += $$PWD
win32 {
msvc:LIBS += Advapi32.lib
gcc:LIBS += -ladvapi32
}
DISTFILES += \
$$PWD/README.md \
$$PWD/CHANGELOG.md \
$$PWD/Windows.md

View File

@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2018 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -69,18 +69,52 @@ SingleApplicationPrivate::~SingleApplicationPrivate()
delete socket; delete socket;
} }
memory->lock(); if( memory != nullptr ) {
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data()); memory->lock();
if( server != nullptr ) { auto *inst = static_cast<InstancesInfo*>(memory->data());
server->close(); if( server != nullptr ) {
delete server; server->close();
inst->primary = false; delete server;
inst->primaryPid = -1; inst->primary = false;
inst->checksum = blockChecksum(); inst->primaryPid = -1;
} inst->primaryUser[0] = '\0';
memory->unlock(); inst->checksum = blockChecksum();
}
memory->unlock();
delete memory; delete memory;
}
}
QString SingleApplicationPrivate::getUsername()
{
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if( GetUserNameW( username, &usernameLength ) )
return QString::fromWCharArray( username );
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
return QString::fromLocal8Bit( qgetenv( "USERNAME" ) );
#else
return qEnvironmentVariable( "USERNAME" );
#endif
#endif
#ifdef Q_OS_UNIX
QString username;
uid_t uid = geteuid();
struct passwd *pw = getpwuid( uid );
if( pw )
username = QString::fromLocal8Bit( pw->pw_name );
if ( username.isEmpty() ) {
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
username = QString::fromLocal8Bit( qgetenv( "USER" ) );
#else
username = qEnvironmentVariable( "USER" );
#endif
}
return username;
#endif
} }
void SingleApplicationPrivate::genBlockServerName() void SingleApplicationPrivate::genBlockServerName()
@ -105,28 +139,7 @@ void SingleApplicationPrivate::genBlockServerName()
// User level block requires a user specific data in the hash // User level block requires a user specific data in the hash
if( options & SingleApplication::Mode::User ) { if( options & SingleApplication::Mode::User ) {
#ifdef Q_OS_WIN appData.addData( getUsername().toUtf8() );
wchar_t username [ UNLEN + 1 ];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if( GetUserNameW( username, &usernameLength ) ) {
appData.addData( QString::fromWCharArray(username).toUtf8() );
} else {
appData.addData( qgetenv("USERNAME") );
}
#endif
#ifdef Q_OS_UNIX
QByteArray username;
uid_t uid = geteuid();
struct passwd *pw = getpwuid(uid);
if( pw ) {
username = pw->pw_name;
}
if( username.isEmpty() ) {
username = qgetenv("USER");
}
appData.addData(username);
#endif
} }
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
@ -136,10 +149,11 @@ void SingleApplicationPrivate::genBlockServerName()
void SingleApplicationPrivate::initializeMemoryBlock() void SingleApplicationPrivate::initializeMemoryBlock()
{ {
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() ); auto *inst = static_cast<InstancesInfo*>( memory->data() );
inst->primary = false; inst->primary = false;
inst->secondary = 0; inst->secondary = 0;
inst->primaryPid = -1; inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum(); inst->checksum = blockChecksum();
} }
@ -169,10 +183,12 @@ void SingleApplicationPrivate::startPrimary()
); );
// Reset the number of connections // Reset the number of connections
InstancesInfo* inst = static_cast <InstancesInfo*>( memory->data() ); auto *inst = static_cast <InstancesInfo*>( memory->data() );
inst->primary = true; inst->primary = true;
inst->primaryPid = q->applicationPid(); inst->primaryPid = q->applicationPid();
strncpy( inst->primaryUser, getUsername().toUtf8().data(), 127 );
inst->primaryUser[127] = '\0';
inst->checksum = blockChecksum(); inst->checksum = blockChecksum();
instanceNumber = 0; instanceNumber = 0;
@ -250,13 +266,25 @@ qint64 SingleApplicationPrivate::primaryPid()
qint64 pid; qint64 pid;
memory->lock(); memory->lock();
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() ); auto *inst = static_cast<InstancesInfo*>( memory->data() );
pid = inst->primaryPid; pid = inst->primaryPid;
memory->unlock(); memory->unlock();
return pid; return pid;
} }
QString SingleApplicationPrivate::primaryUser()
{
QByteArray username;
memory->lock();
auto *inst = static_cast<InstancesInfo*>( memory->data() );
username = inst->primaryUser;
memory->unlock();
return QString::fromUtf8( username );
}
/** /**
* @brief Executed when a connection has been made to the LocalServer * @brief Executed when a connection has been made to the LocalServer
*/ */

View File

@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2016 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -42,14 +42,13 @@ struct InstancesInfo {
quint32 secondary; quint32 secondary;
qint64 primaryPid; qint64 primaryPid;
quint16 checksum; quint16 checksum;
char primaryUser[128];
}; };
struct ConnectionInfo { struct ConnectionInfo {
explicit ConnectionInfo() : qint64 msgLen = 0;
msgLen(0), instanceId(0), stage(0) {} quint32 instanceId = 0;
qint64 msgLen; quint8 stage = 0;
quint32 instanceId;
quint8 stage;
}; };
class SingleApplicationPrivate : public QObject { class SingleApplicationPrivate : public QObject {
@ -69,8 +68,9 @@ public:
Q_DECLARE_PUBLIC(SingleApplication) Q_DECLARE_PUBLIC(SingleApplication)
SingleApplicationPrivate( SingleApplication *q_ptr ); SingleApplicationPrivate( SingleApplication *q_ptr );
~SingleApplicationPrivate(); ~SingleApplicationPrivate() override;
QString getUsername();
void genBlockServerName(); void genBlockServerName();
void initializeMemoryBlock(); void initializeMemoryBlock();
void startPrimary(); void startPrimary();
@ -78,6 +78,7 @@ public:
void connectToPrimary(int msecs, ConnectionType connectionType ); void connectToPrimary(int msecs, ConnectionType connectionType );
quint16 blockChecksum(); quint16 blockChecksum();
qint64 primaryPid(); qint64 primaryPid();
QString primaryUser();
void readInitMessageHeader(QLocalSocket *socket); void readInitMessageHeader(QLocalSocket *socket);
void readInitMessageBody(QLocalSocket *socket); void readInitMessageBody(QLocalSocket *socket);