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
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 --noninteractive install --user flathub org.kde.Platform//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
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
mkdir ../artifacts

View file

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

View file

@ -10,7 +10,9 @@ set(
CACHE
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)
include("cmake/HunterGate.cmake")
@ -333,7 +335,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare(
MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
GIT_TAG f5c78f4331b62a1e25a2d839cb38b07bb9bd7829
GIT_TAG 795b6a82d4f10c629ce0eb99803cc677c413f940
)
FetchContent_MakeAvailable(MatrixClient)
else()
@ -421,7 +423,7 @@ endif()
# single instance functionality
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)
@ -515,6 +517,9 @@ set(TRANSLATION_DEPS ${LANG_QRC} ${QRC} ${QM_SRC})
if (APPLE)
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)
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)
file(DOWNLOAD
"https://raw.githubusercontent.com/mohabouje/WinToast/41ed1c58d5dce0ee9c01dbdeac05be45358d4f57/src/wintoastlib.cpp"
@ -578,6 +583,13 @@ target_link_libraries(nheko PRIVATE
tweeny
SingleApplication::SingleApplication)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
target_precompile_headers(nheko
PRIVATE
<string>
)
endif()
if(MSVC)
target_link_libraries(nheko PRIVATE ntdll)
endif()

View file

@ -131,7 +131,7 @@
{
"sha256": "59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722",
"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",
"sources": [
{
"sha256": "16203a92b03c488178b31bedca9d9015b1d406443f7e5363a2e09171e50f8dfc",
"sha256": "7ba85bb477c9e17e2389faf2333034aa9ceae4b1c65d6bf5f7067f2799e9b770",
"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
width: 48
height: 48
radius: settings.avatar_circles ? height/2 : 3
radius: settings.avatarCircles ? height/2 : 3
property alias url: img.source
property string displayName
@ -39,7 +39,7 @@ Rectangle {
anchors.fill: parent
width: avatar.width
height: avatar.height
radius: settings.avatar_circles ? height/2 : 3
radius: settings.avatarCircles ? height/2 : 3
}
}
}

View file

@ -57,7 +57,7 @@ Flow {
Text {
anchors.baseline: reactionCounter.baseline
id: reactionText
text: textMetrics.elidedText + (textMetrics.elidedText == model.key ? "" : "…")
text: textMetrics.elidedText + (textMetrics.elidedText == textMetrics.text ? "" : "…")
font.family: settings.emoji_font_family
color: reaction.hovered ? colors.highlight : colors.text
maximumLineCount: 1
@ -65,7 +65,7 @@ Flow {
Rectangle {
id: divider
height: reactionCounter.implicitHeight * 1.4
height: Math.floor(reactionCounter.implicitHeight * 1.4)
width: 1
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)
}
Rectangle {
color: (timelineSettings.message_hover_highlight && parent.containsMouse) ? colors.base : "transparent"
color: (settings.messageHoverHighlight && parent.containsMouse) ? colors.base : "transparent"
anchors.fill: row
}
RowLayout {
id: row
anchors.leftMargin: avatarSize + 4
anchors.leftMargin: avatarSize + 16
anchors.left: parent.left
anchors.right: parent.right
@ -97,7 +97,7 @@ MouseArea {
event_id: model.id
}
ImageButton {
visible: timelineSettings.buttons
visible: settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
@ -113,7 +113,7 @@ MouseArea {
onClicked: chat.model.replyAction(model.id)
}
ImageButton {
visible: timelineSettings.buttons
visible: settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16

View file

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

View file

@ -6,4 +6,5 @@ MatrixText {
width: parent ? parent.width : undefined
height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined
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_p.h"
#include "EventAccessors.h"
#include "Logging.h"
#include "Utils.h"
@ -1947,13 +1948,14 @@ Cache::saveTimelineMessages(lmdb::txn &txn,
json obj = json::object();
obj["event"] = utils::serialize_event(e);
obj["event"] = mtx::accessors::serialize_event(e);
obj["token"] = res.prev_batch;
lmdb::dbi_put(txn,
db,
lmdb::val(std::to_string(utils::event_timestamp(e))),
lmdb::val(obj.dump()));
lmdb::dbi_put(
txn,
db,
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;
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...
if (room_id.compare(notif.room_id) != 0) {

View file

@ -26,6 +26,7 @@
#include "Cache.h"
#include "Cache_p.h"
#include "ChatPage.h"
#include "EventAccessors.h"
#include "Logging.h"
#include "MainWindow.h"
#include "MatrixClient.h"
@ -255,7 +256,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
text_input_, &TextInputWidget::startedTyping, this, &ChatPage::sendTypingNotifications);
connect(typingRefresher_, &QTimer::timeout, this, &ChatPage::sendTypingNotifications);
connect(text_input_, &TextInputWidget::stoppedTyping, this, [this]() {
if (!userSettings_->isTypingNotificationsEnabled())
if (!userSettings_->typingNotifications())
return;
typingRefresher_->stop();
@ -482,7 +483,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
activateWindow();
});
setGroupViewState(userSettings_->isGroupViewEnabled());
setGroupViewState(userSettings_->groupView());
connect(userSettings_.data(),
&UserSettings::groupViewStateChanged,
@ -891,7 +892,7 @@ void
ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
{
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 {
if (item.read) {
@ -901,7 +902,8 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
if (!cache::isNotificationSent(event_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.
cache::markSentNotification(event_id);
@ -1213,7 +1215,7 @@ ChatPage::unbanUser(QString userid, QString reason)
void
ChatPage::sendTypingNotifications()
{
if (!userSettings_->isTypingNotificationsEnabled())
if (!userSettings_->typingNotifications())
return;
http::client()->start_typing(
@ -1349,7 +1351,7 @@ ChatPage::hideSideBars()
void
ChatPage::showSideBars()
{
if (userSettings_->isGroupViewEnabled())
if (userSettings_->groupView())
communitiesList_->show();
sideBar_->show();

View file

@ -7,7 +7,6 @@
#include "ui/Theme.h"
class RippleOverlay;
class QPainter;
class QMouseEvent;
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);
}
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
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 <QLabel>
#include <QPushButton>
#include "InviteeItem.h"

View file

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

View file

@ -16,6 +16,7 @@
*/
#include <QDesktopServices>
#include <QLabel>
#include <QPainter>
#include <QStyleOption>
@ -118,7 +119,7 @@ LoginPage::LoginPage(QWidget *parent)
deviceName_->setLabel(tr("Device name"));
deviceName_->setToolTip(
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_->setLabel("Homeserver address");
@ -132,7 +133,7 @@ LoginPage::LoginPage(QWidget *parent)
form_layout_->addLayout(matrixidLayout_);
form_layout_->addWidget(password_input_);
form_layout_->addWidget(deviceName_, Qt::AlignHCenter, nullptr);
form_layout_->addWidget(deviceName_, Qt::AlignHCenter);
form_layout_->addLayout(serverLayout_);
button_layout_ = new QHBoxLayout();
@ -179,6 +180,12 @@ LoginPage::LoginPage(QWidget *parent)
connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
}
void
LoginPage::loginError(const QString &msg)
{
error_label_->setText(msg);
}
void
LoginPage::onMatrixIdEntered()
{

View file

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

View file

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

View file

@ -17,7 +17,8 @@ MxcImageResponse::run()
auto data = cache::image(fileName);
if (!data.isNull()) {
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);
if (!m_image.isNull()) {

View file

@ -15,6 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QLabel>
#include <QMetaType>
#include <QPainter>
#include <QStyleOption>
@ -106,10 +107,10 @@ RegisterPage::RegisterPage(QWidget *parent)
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."));
form_layout_->addWidget(username_input_, Qt::AlignHCenter, nullptr);
form_layout_->addWidget(password_input_, Qt::AlignHCenter, nullptr);
form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter, nullptr);
form_layout_->addWidget(server_input_, Qt::AlignHCenter, nullptr);
form_layout_->addWidget(username_input_, Qt::AlignHCenter);
form_layout_->addWidget(password_input_, Qt::AlignHCenter);
form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter);
form_layout_->addWidget(server_input_, Qt::AlignHCenter);
button_layout_ = new QHBoxLayout();
button_layout_->setSpacing(0);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -30,6 +30,7 @@
#include <QScrollArea>
#include <QScroller>
#include <QSettings>
#include <QSpinBox>
#include <QStandardPaths>
#include <QString>
#include <QTextStream>
@ -51,54 +52,206 @@ void
UserSettings::load()
{
QSettings settings;
isTrayEnabled_ = settings.value("user/window/tray", false).toBool();
hasDesktopNotifications_ = settings.value("user/desktop_notifications", true).toBool();
isStartInTrayEnabled_ = settings.value("user/window/start_in_tray", false).toBool();
isGroupViewEnabled_ = settings.value("user/group_view", true).toBool();
isButtonsInTimelineEnabled_ = settings.value("user/timeline/buttons", true).toBool();
isMessageHoverHighlightEnabled_ =
tray_ = settings.value("user/window/tray", false).toBool();
hasDesktopNotifications_ = settings.value("user/desktop_notifications", true).toBool();
startInTray_ = settings.value("user/window/start_in_tray", false).toBool();
groupView_ = settings.value("user/group_view", true).toBool();
buttonsInTimeline_ = settings.value("user/timeline/buttons", true).toBool();
timelineMaxWidth_ = settings.value("user/timeline/max_width", 0).toInt();
messageHoverHighlight_ =
settings.value("user/timeline/message_hover_highlight", false).toBool();
isMarkdownEnabled_ = settings.value("user/markdown_enabled", true).toBool();
isTypingNotificationsEnabled_ = settings.value("user/typing_notifications", true).toBool();
sortByImportance_ = settings.value("user/sort_by_unread", true).toBool();
isReadReceiptsEnabled_ = settings.value("user/read_receipts", true).toBool();
theme_ = settings.value("user/theme", defaultTheme_).toString();
font_ = settings.value("user/font_family", "default").toString();
avatarCircles_ = settings.value("user/avatar_circles", true).toBool();
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();
enlargeEmojiOnlyMessages_ =
settings.value("user/timeline/enlarge_emoji_only_msg", false).toBool();
markdown_ = settings.value("user/markdown_enabled", true).toBool();
typingNotifications_ = settings.value("user/typing_notifications", true).toBool();
sortByImportance_ = settings.value("user/sort_by_unread", true).toBool();
readReceipts_ = settings.value("user/read_receipts", true).toBool();
theme_ = settings.value("user/theme", defaultTheme_).toString();
font_ = settings.value("user/font_family", "default").toString();
avatarCircles_ = settings.value("user/avatar_circles", true).toBool();
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();
}
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
UserSettings::setFontSize(double size)
{
if (size == baseFontSize_)
return;
baseFontSize_ = size;
emit fontSizeChanged(size);
save();
}
void
UserSettings::setFontFamily(QString family)
{
if (family == font_)
return;
font_ = family;
emit fontChanged(family);
save();
}
void
UserSettings::setEmojiFontFamily(QString family)
{
if (family == emojiFont_)
return;
emojiFont_ = family;
emit emojiFontChanged(family);
save();
}
void
UserSettings::setTheme(QString theme)
{
if (theme == theme)
return;
theme_ = theme;
save();
applyTheme();
emit themeChanged(theme);
}
void
@ -161,29 +314,33 @@ UserSettings::save()
settings.beginGroup("user");
settings.beginGroup("window");
settings.setValue("tray", isTrayEnabled_);
settings.setValue("start_in_tray", isStartInTrayEnabled_);
settings.setValue("tray", tray_);
settings.setValue("start_in_tray", startInTray_);
settings.endGroup();
settings.beginGroup("timeline");
settings.setValue("buttons", isButtonsInTimelineEnabled_);
settings.setValue("message_hover_highlight", isMessageHoverHighlightEnabled_);
settings.setValue("buttons", buttonsInTimeline_);
settings.setValue("message_hover_highlight", messageHoverHighlight_);
settings.setValue("enlarge_emoji_only_msg", enlargeEmojiOnlyMessages_);
settings.setValue("max_width", timelineMaxWidth_);
settings.endGroup();
settings.setValue("avatar_circles", avatarCircles_);
settings.setValue("decrypt_sidebar", decryptSidebar_);
settings.setValue("font_size", baseFontSize_);
settings.setValue("typing_notifications", isTypingNotificationsEnabled_);
settings.setValue("typing_notifications", typingNotifications_);
settings.setValue("minor_events", sortByImportance_);
settings.setValue("read_receipts", isReadReceiptsEnabled_);
settings.setValue("group_view", isGroupViewEnabled_);
settings.setValue("markdown_enabled", isMarkdownEnabled_);
settings.setValue("read_receipts", readReceipts_);
settings.setValue("group_view", groupView_);
settings.setValue("markdown_enabled", markdown_);
settings.setValue("desktop_notifications", hasDesktopNotifications_);
settings.setValue("theme", theme());
settings.setValue("font_family", font_);
settings.setValue("emoji_font_family", emojiFont_);
settings.endGroup();
settings.sync();
}
HorizontalLine::HorizontalLine(QWidget *parent)
@ -231,24 +388,26 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
general_->setFont(font);
trayToggle_ = new Toggle{this};
startInTrayToggle_ = new Toggle{this};
avatarCircles_ = new Toggle{this};
decryptSidebar_ = new Toggle(this);
groupViewToggle_ = new Toggle{this};
timelineButtonsToggle_ = new Toggle{this};
typingNotifications_ = new Toggle{this};
messageHoverHighlight_ = new Toggle{this};
sortByImportance_ = new Toggle{this};
readReceipts_ = new Toggle{this};
markdownEnabled_ = new Toggle{this};
desktopNotifications_ = new Toggle{this};
scaleFactorCombo_ = new QComboBox{this};
fontSizeCombo_ = new QComboBox{this};
fontSelectionCombo_ = new QComboBox{this};
emojiFontSelectionCombo_ = new QComboBox{this};
trayToggle_ = new Toggle{this};
startInTrayToggle_ = new Toggle{this};
avatarCircles_ = new Toggle{this};
decryptSidebar_ = new Toggle(this);
groupViewToggle_ = new Toggle{this};
timelineButtonsToggle_ = new Toggle{this};
typingNotifications_ = new Toggle{this};
messageHoverHighlight_ = new Toggle{this};
enlargeEmojiOnlyMessages_ = new Toggle{this};
sortByImportance_ = new Toggle{this};
readReceipts_ = new Toggle{this};
markdown_ = new Toggle{this};
desktopNotifications_ = new Toggle{this};
scaleFactorCombo_ = new QComboBox{this};
fontSizeCombo_ = new QComboBox{this};
fontSelectionCombo_ = new QComboBox{this};
emojiFontSelectionCombo_ = new QComboBox{this};
timelineMaxWidthSpin_ = new QSpinBox{this};
if (!settings_->isTrayEnabled())
if (!settings_->tray())
startInTrayToggle_->setDisabled(true);
avatarCircles_->setFixedSize(64, 48);
@ -291,6 +450,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
int themeIndex = themeCombo_->findText(themeStr);
themeCombo_->setCurrentIndex(themeIndex);
timelineMaxWidthSpin_->setMinimum(0);
timelineMaxWidthSpin_->setMaximum(100'000'000);
timelineMaxWidthSpin_->setSingleStep(10);
auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this};
encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin);
encryptionLabel_->setAlignment(Qt::AlignBottom);
@ -323,11 +486,15 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
sessionKeysLayout->addWidget(sessionKeysExportBtn, 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};
label->setFont(font);
label->setMargin(OptionMargin);
if (!tooltipText.isEmpty()) {
label->setToolTip(tooltipText);
}
auto layout = new QHBoxLayout;
layout->addWidget(field, 0, Qt::AlignRight);
@ -336,25 +503,70 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
formLayout_->addRow(general_);
formLayout_->addRow(new HorizontalLine{this});
boxWrap(tr("Minimize to tray"), trayToggle_);
boxWrap(tr("Start in tray"), startInTrayToggle_);
boxWrap(
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});
boxWrap(tr("Circular Avatars"), avatarCircles_);
boxWrap(tr("Group's sidebar"), groupViewToggle_);
boxWrap(tr("Decrypt messages in sidebar"), decryptSidebar_);
boxWrap(tr("Show buttons in timeline"), timelineButtonsToggle_);
boxWrap(tr("Typing notifications"), typingNotifications_);
boxWrap(tr("Sort rooms by unreads"), sortByImportance_);
boxWrap(tr("Circular Avatars"),
avatarCircles_,
tr("Change the appearance of user avatars in chats.\nOFF - square, ON - Circle."));
boxWrap(tr("Group's sidebar"),
groupViewToggle_,
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});
boxWrap(tr("Read receipts"), readReceipts_);
boxWrap(tr("Send messages as Markdown"), markdownEnabled_);
boxWrap(tr("Desktop notifications"), desktopNotifications_);
boxWrap(tr("Highlight message on hover"), messageHoverHighlight_);
boxWrap(tr("Read receipts"),
readReceipts_,
tr("Show if your message was read.\nStatus is displayed next to timestamps."));
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(new HorizontalLine{this});
#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
scaleFactorCombo_->hide();
#endif
@ -400,78 +612,87 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
topLayout_->addWidget(versionInfo);
connect(themeCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated),
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &text) {
settings_->setTheme(text.toLower());
emit themeChanged();
});
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()); });
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()); });
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()); });
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()); });
connect(trayToggle_, &Toggle::toggled, this, [this](bool isDisabled) {
settings_->setTray(!isDisabled);
if (isDisabled) {
connect(trayToggle_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setTray(!disabled);
if (disabled) {
startInTrayToggle_->setDisabled(true);
} else {
startInTrayToggle_->setEnabled(true);
}
emit trayOptionChanged(!isDisabled);
emit trayOptionChanged(!disabled);
});
connect(startInTrayToggle_, &Toggle::toggled, this, [this](bool isDisabled) {
settings_->setStartInTray(!isDisabled);
connect(startInTrayToggle_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setStartInTray(!disabled);
});
connect(groupViewToggle_, &Toggle::toggled, this, [this](bool isDisabled) {
settings_->setGroupView(!isDisabled);
connect(groupViewToggle_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setGroupView(!disabled);
});
connect(decryptSidebar_, &Toggle::toggled, this, [this](bool isDisabled) {
settings_->setDecryptSidebar(!isDisabled);
connect(decryptSidebar_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setDecryptSidebar(!disabled);
emit decryptSidebarChanged();
});
connect(avatarCircles_, &Toggle::toggled, this, [this](bool isDisabled) {
settings_->setAvatarCircles(!isDisabled);
connect(avatarCircles_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setAvatarCircles(!disabled);
});
connect(markdownEnabled_, &Toggle::toggled, this, [this](bool isDisabled) {
settings_->setMarkdownEnabled(!isDisabled);
connect(markdown_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setMarkdown(!disabled);
});
connect(typingNotifications_, &Toggle::toggled, this, [this](bool isDisabled) {
settings_->setTypingNotifications(!isDisabled);
connect(typingNotifications_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setTypingNotifications(!disabled);
});
connect(sortByImportance_, &Toggle::toggled, this, [this](bool isDisabled) {
settings_->setSortByImportance(!isDisabled);
connect(sortByImportance_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setSortByImportance(!disabled);
});
connect(timelineButtonsToggle_, &Toggle::toggled, this, [this](bool isDisabled) {
settings_->setButtonsInTimeline(!isDisabled);
connect(timelineButtonsToggle_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setButtonsInTimeline(!disabled);
});
connect(readReceipts_, &Toggle::toggled, this, [this](bool isDisabled) {
settings_->setReadReceipts(!isDisabled);
connect(readReceipts_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setReadReceipts(!disabled);
});
connect(desktopNotifications_, &Toggle::toggled, this, [this](bool isDisabled) {
settings_->setDesktopNotifications(!isDisabled);
connect(desktopNotifications_, &Toggle::toggled, this, [this](bool disabled) {
settings_->setDesktopNotifications(!disabled);
});
connect(messageHoverHighlight_, &Toggle::toggled, this, [this](bool isDisabled) {
settings_->setMessageHoverHighlight(!isDisabled);
connect(messageHoverHighlight_, &Toggle::toggled, this, [this](bool disabled) {
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(
sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys);
@ -493,19 +714,21 @@ UserSettingsPage::showEvent(QShowEvent *)
utils::restoreCombobox(themeCombo_, settings_->theme());
// FIXME: Toggle treats true as "off"
trayToggle_->setState(!settings_->isTrayEnabled());
startInTrayToggle_->setState(!settings_->isStartInTrayEnabled());
groupViewToggle_->setState(!settings_->isGroupViewEnabled());
decryptSidebar_->setState(!settings_->isDecryptSidebarEnabled());
avatarCircles_->setState(!settings_->isAvatarCirclesEnabled());
typingNotifications_->setState(!settings_->isTypingNotificationsEnabled());
sortByImportance_->setState(!settings_->isSortByImportanceEnabled());
timelineButtonsToggle_->setState(!settings_->isButtonsInTimelineEnabled());
readReceipts_->setState(!settings_->isReadReceiptsEnabled());
markdownEnabled_->setState(!settings_->isMarkdownEnabled());
trayToggle_->setState(!settings_->tray());
startInTrayToggle_->setState(!settings_->startInTray());
groupViewToggle_->setState(!settings_->groupView());
decryptSidebar_->setState(!settings_->decryptSidebar());
avatarCircles_->setState(!settings_->avatarCircles());
typingNotifications_->setState(!settings_->typingNotifications());
sortByImportance_->setState(!settings_->sortByImportance());
timelineButtonsToggle_->setState(!settings_->buttonsInTimeline());
readReceipts_->setState(!settings_->readReceipts());
markdown_->setState(!settings_->markdown());
desktopNotifications_->setState(!settings_->hasDesktopNotifications());
messageHoverHighlight_->setState(!settings_->isMessageHoverHighlightEnabled());
messageHoverHighlight_->setState(!settings_->messageHoverHighlight());
enlargeEmojiOnlyMessages_->setState(!settings_->enlargeEmojiOnlyMessages());
deviceIdValue_->setText(QString::fromStdString(http::client()->device_id()));
timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth());
deviceFingerprintValue_->setText(
utils::humanReadableFingerprint(olm::client()->identity_keys().ed25519));

View file

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

View file

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

View file

@ -36,6 +36,9 @@ namespace utils {
using TimelineEvent = mtx::events::collections::TimelineEvents;
bool
codepointIsEmoji(uint code);
QString
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>
QString
message_body(const mtx::events::collections::TimelineEvents &event)

View file

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

View file

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

View file

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

View file

@ -1,5 +1,6 @@
#include <QDebug>
#include <QIcon>
#include <QLabel>
#include <QListWidgetItem>
#include <QPainter>
#include <QPushButton>
@ -74,15 +75,17 @@ ReceiptItem::dateFormat(const QDateTime &then) const
auto days = then.daysTo(now);
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)
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)
return QString("%1 %2")
.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)
@ -163,3 +166,10 @@ ReadReceipts::paintEvent(QPaintEvent *)
QPainter 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 <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QVBoxLayout>
class Avatar;
class QLabel;
class QListWidget;
class QHBoxLayout;
class QVBoxLayout;
namespace dialogs {
@ -47,11 +47,7 @@ public slots:
protected:
void paintEvent(QPaintEvent *event) override;
void hideEvent(QHideEvent *event) override
{
userList_->clear();
QFrame::hideEvent(event);
}
void hideEvent(QHideEvent *event) override;
private:
QLabel *topLabel_;

View file

@ -1,5 +1,6 @@
#include <QApplication>
#include <QComboBox>
#include <QEvent>
#include <QFileDialog>
#include <QFontDatabase>
#include <QImageReader>
@ -41,6 +42,17 @@ constexpr int WIDGET_SPACING = 15;
constexpr int TEXT_SPACING = 4;
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)
: QWidget(parent)
, roomId_{roomId}
@ -93,6 +105,28 @@ EditModal::EditModal(const QString &roomId, QWidget *parent)
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
EditModal::applyClicked()
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -219,6 +219,7 @@ TimelineModel::roleNames() const
{Section, "section"},
{Type, "type"},
{TypeString, "typeString"},
{IsOnlyEmoji, "isOnlyEmoji"},
{Body, "body"},
{FormattedBody, "formattedBody"},
{UserId, "userId"},
@ -284,6 +285,22 @@ TimelineModel::data(const QString &id, int role) const
return QVariant(toRoomEventType(event));
case TypeString:
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:
return QVariant(utils::replaceEmoji(QString::fromStdString(body(event))));
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[Type], data(id, static_cast<int>(Type)));
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[FormattedBody], data(id, static_cast<int>(FormattedBody)));
m.insert(names[UserId], data(id, static_cast<int>(UserId)));
@ -810,7 +828,7 @@ TimelineModel::escapeEmoji(QString str) const
void
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));
Q_UNUSED(dialog);
}
@ -824,7 +842,7 @@ TimelineModel::viewDecryptedRawMessage(QString id) const
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));
Q_UNUSED(dialog);
}
@ -1849,6 +1867,8 @@ TimelineModel::formatMemberEvent(QString id)
rendered = tr("%1 changed their display name.").arg(name);
else if (avatarChanged)
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
// just show it as join
} else {

View file

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

View file

@ -21,7 +21,7 @@ Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
void
TimelineViewManager::updateEncryptedDescriptions()
{
auto decrypt = settings->isDecryptSidebarEnabled();
auto decrypt = settings->decryptSidebar();
QHash<QString, QSharedPointer<TimelineModel>>::iterator i;
for (i = models.begin(); i != models.end(); ++i) {
auto ptr = i.value();
@ -96,6 +96,7 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin
#endif
container->setMinimumSize(200, 200);
view->rootContext()->setContextProperty("timelineManager", this);
view->rootContext()->setContextProperty("settings", settings.data());
updateColorPalette();
view->engine()->addImageProvider("MxcImage", imgProvider);
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));
room_model->addEvents(room.timeline);
if (ChatPage::instance()->userSettings()->isTypingNotificationsEnabled()) {
if (ChatPage::instance()->userSettings()->typingNotifications()) {
std::vector<QString> typing;
typing.reserve(room.ephemeral.typing.size());
for (const auto &user : room.ephemeral.typing) {
@ -141,7 +142,7 @@ TimelineViewManager::addRoom(const QString &room_id)
{
if (!models.contains(room_id)) {
QSharedPointer<TimelineModel> newRoom(new TimelineModel(this, room_id));
newRoom->setDecryptDescription(settings->isDecryptSidebarEnabled());
newRoom->setDecryptDescription(settings->decryptSidebar());
connect(newRoom.data(),
&TimelineModel::newEncryptedImage,
@ -225,7 +226,7 @@ TimelineViewManager::queueTextMessage(const QString &msg)
mtx::events::msg::Text text = {};
text.body = msg.trimmed().toStdString();
if (settings->isMarkdownEnabled()) {
if (settings->markdown()) {
text.formatted_body = utils::markdownToHtml(msg).toStdString();
// 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!
text.format = "org.matrix.custom.html";
if (settings->isMarkdownEnabled())
if (settings->markdown())
text.formatted_body =
utils::getFormattedQuoteBody(related, utils::markdownToHtml(msg))
.toStdString();
@ -276,7 +277,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
mtx::events::msg::Emote emote;
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.format = "org.matrix.custom.html";
}

View file

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

View file

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

View file

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

View file

@ -6,4 +6,11 @@
/examples/basic/basic
/examples/calculator/calculator
/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
=========
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__
----------

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)
# 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
singleapplication.cpp
singleapplication_p.cpp
)
)
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
# Find dependencies
find_package(Qt5Network)
find_package(Qt5 COMPONENTS Network REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Network)
if(QAPPLICATION_CLASS STREQUAL QApplication)
find_package(Qt5 COMPONENTS Widgets REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Widgets)
elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication)
find_package(Qt5 COMPONENTS Gui REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Gui)
else()
set(QAPPLICATION_CLASS QCoreApplication)
find_package(Qt5 COMPONENTS Core REQUIRED)
endif()
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)
target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Core)
endif()
if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE advapi32)
endif()
target_compile_definitions(${PROJECT_NAME} PUBLIC QAPPLICATION_CLASS=${QAPPLICATION_CLASS})
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)
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
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,5 +1,6 @@
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`.
@ -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`
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
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
submodule to your project and include it's contents with a `.pri` file. Here is
how:
submodule to your project. Here is how:
```bash
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
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for 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
is not `QCoreApplication` as in examples above.
The `Instance Started` signal
------------------------
-----------------------------
The SingleApplication class implements a `instanceStarted()` signal. You can
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.
---
```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
```cpp

View file

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

View file

@ -1,6 +1,6 @@
// 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
// 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;
time.start();
@ -172,7 +172,19 @@ qint64 SingleApplication::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);

View file

@ -43,7 +43,7 @@ class SingleApplication : public QAPPLICATION_CLASS
{
Q_OBJECT
typedef QAPPLICATION_CLASS app_t;
using app_t = QAPPLICATION_CLASS;
public:
/**
@ -86,7 +86,7 @@ public:
* @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 );
~SingleApplication();
~SingleApplication() override;
/**
* @brief Returns if the instance is the primary instance
@ -112,6 +112,18 @@ public:
*/
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.
* @param {int} timeout - Timeout for connecting
@ -119,7 +131,7 @@ public:
* @note sendMessage() will return false if invoked from the primary
* instance.
*/
bool sendMessage( QByteArray message, int timeout = 100 );
bool sendMessage( const QByteArray &message, int timeout = 100 );
Q_SIGNALS:
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)
//
// 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
// of this software and associated documentation files (the "Software"), to deal
@ -69,18 +69,52 @@ SingleApplicationPrivate::~SingleApplicationPrivate()
delete socket;
}
memory->lock();
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
if( server != nullptr ) {
server->close();
delete server;
inst->primary = false;
inst->primaryPid = -1;
inst->checksum = blockChecksum();
}
memory->unlock();
if( memory != nullptr ) {
memory->lock();
auto *inst = static_cast<InstancesInfo*>(memory->data());
if( server != nullptr ) {
server->close();
delete server;
inst->primary = false;
inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
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()
@ -105,28 +139,7 @@ void SingleApplicationPrivate::genBlockServerName()
// User level block requires a user specific data in the hash
if( options & SingleApplication::Mode::User ) {
#ifdef Q_OS_WIN
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
appData.addData( getUsername().toUtf8() );
}
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
@ -136,10 +149,11 @@ void SingleApplicationPrivate::genBlockServerName()
void SingleApplicationPrivate::initializeMemoryBlock()
{
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() );
auto *inst = static_cast<InstancesInfo*>( memory->data() );
inst->primary = false;
inst->secondary = 0;
inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum();
}
@ -169,10 +183,12 @@ void SingleApplicationPrivate::startPrimary()
);
// Reset the number of connections
InstancesInfo* inst = static_cast <InstancesInfo*>( memory->data() );
auto *inst = static_cast <InstancesInfo*>( memory->data() );
inst->primary = true;
inst->primaryPid = q->applicationPid();
strncpy( inst->primaryUser, getUsername().toUtf8().data(), 127 );
inst->primaryUser[127] = '\0';
inst->checksum = blockChecksum();
instanceNumber = 0;
@ -250,13 +266,25 @@ qint64 SingleApplicationPrivate::primaryPid()
qint64 pid;
memory->lock();
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() );
auto *inst = static_cast<InstancesInfo*>( memory->data() );
pid = inst->primaryPid;
memory->unlock();
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
*/

View file

@ -1,6 +1,6 @@
// 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
// of this software and associated documentation files (the "Software"), to deal
@ -42,14 +42,13 @@ struct InstancesInfo {
quint32 secondary;
qint64 primaryPid;
quint16 checksum;
char primaryUser[128];
};
struct ConnectionInfo {
explicit ConnectionInfo() :
msgLen(0), instanceId(0), stage(0) {}
qint64 msgLen;
quint32 instanceId;
quint8 stage;
qint64 msgLen = 0;
quint32 instanceId = 0;
quint8 stage = 0;
};
class SingleApplicationPrivate : public QObject {
@ -69,8 +68,9 @@ public:
Q_DECLARE_PUBLIC(SingleApplication)
SingleApplicationPrivate( SingleApplication *q_ptr );
~SingleApplicationPrivate();
~SingleApplicationPrivate() override;
QString getUsername();
void genBlockServerName();
void initializeMemoryBlock();
void startPrimary();
@ -78,6 +78,7 @@ public:
void connectToPrimary(int msecs, ConnectionType connectionType );
quint16 blockChecksum();
qint64 primaryPid();
QString primaryUser();
void readInitMessageHeader(QLocalSocket *socket);
void readInitMessageBody(QLocalSocket *socket);