mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 19:08:58 +03:00
Merge pull request #610 from Nheko-Reborn/spaces
Show some spaces in the community sidebar
This commit is contained in:
commit
36f0e04439
49 changed files with 1705 additions and 201 deletions
|
@ -356,7 +356,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 5d2f055ea9403770039ddf66b1900f890cd5cde7
|
GIT_TAG fdb2016eff4f2e91f17c343e9fcb0bfab5e78b63
|
||||||
)
|
)
|
||||||
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
||||||
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
||||||
|
@ -458,7 +458,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.2.0-dc8042b/)
|
add_subdirectory(third_party/SingleApplication-3.3.0/)
|
||||||
|
|
||||||
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,7 @@ modules:
|
||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
name: mtxclient
|
name: mtxclient
|
||||||
sources:
|
sources:
|
||||||
- commit: 5d2f055ea9403770039ddf66b1900f890cd5cde7
|
- commit: fdb2016eff4f2e91f17c343e9fcb0bfab5e78b63
|
||||||
type: git
|
type: git
|
||||||
url: https://github.com/Nheko-Reborn/mtxclient.git
|
url: https://github.com/Nheko-Reborn/mtxclient.git
|
||||||
- config-opts:
|
- config-opts:
|
||||||
|
|
|
@ -71,7 +71,7 @@ Rectangle {
|
||||||
AdaptiveLayoutElement {
|
AdaptiveLayoutElement {
|
||||||
id: timlineViewC
|
id: timlineViewC
|
||||||
|
|
||||||
minimumWidth: fontMetrics.averageCharacterWidth * 40 + Nheko.avatarSize + 2* Nheko.paddingMedium
|
minimumWidth: fontMetrics.averageCharacterWidth * 40 + Nheko.avatarSize + 2 * Nheko.paddingMedium
|
||||||
|
|
||||||
TimelineView {
|
TimelineView {
|
||||||
id: timeline
|
id: timeline
|
||||||
|
|
|
@ -12,7 +12,8 @@ TextEdit {
|
||||||
focus: false
|
focus: false
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
selectByMouse: !Settings.mobileMode
|
selectByMouse: !Settings.mobileMode
|
||||||
enabled: selectByMouse
|
// this always has to be enabled, otherwise you can't click links anymore!
|
||||||
|
//enabled: selectByMouse
|
||||||
color: Nheko.colors.text
|
color: Nheko.colors.text
|
||||||
onLinkActivated: Nheko.openLink(link)
|
onLinkActivated: Nheko.openLink(link)
|
||||||
ToolTip.visible: hoveredLink
|
ToolTip.visible: hoveredLink
|
||||||
|
|
|
@ -255,6 +255,8 @@ Page {
|
||||||
Label {
|
Label {
|
||||||
id: timestamp
|
id: timestamp
|
||||||
|
|
||||||
|
visible: !model.isInvite && !model.isSpace
|
||||||
|
width: visible ? 0 : undefined
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||||
font.pixelSize: fontMetrics.font.pixelSize * 0.9
|
font.pixelSize: fontMetrics.font.pixelSize * 0.9
|
||||||
color: roomItem.unimportantText
|
color: roomItem.unimportantText
|
||||||
|
@ -266,12 +268,11 @@ Page {
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 0
|
spacing: 0
|
||||||
visible: !model.isInvite
|
visible: !model.isInvite && !model.isSpace
|
||||||
height: visible ? 0 : undefined
|
height: visible ? 0 : undefined
|
||||||
|
|
||||||
ElidedLabel {
|
ElidedLabel {
|
||||||
color: roomItem.unimportantText
|
color: roomItem.unimportantText
|
||||||
font.weight: Font.Thin
|
|
||||||
font.pixelSize: fontMetrics.font.pixelSize * 0.9
|
font.pixelSize: fontMetrics.font.pixelSize * 0.9
|
||||||
elideWidth: textContent.width - (notificationBubble.visible ? notificationBubble.width : 0) - Nheko.paddingSmall
|
elideWidth: textContent.width - (notificationBubble.visible ? notificationBubble.width : 0) - Nheko.paddingSmall
|
||||||
fullText: model.lastMessage
|
fullText: model.lastMessage
|
||||||
|
@ -485,7 +486,6 @@ Page {
|
||||||
ElidedLabel {
|
ElidedLabel {
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
color: Nheko.colors.buttonText
|
color: Nheko.colors.buttonText
|
||||||
font.weight: Font.Thin
|
|
||||||
font.pointSize: fontMetrics.font.pointSize * 0.9
|
font.pointSize: fontMetrics.font.pointSize * 0.9
|
||||||
elideWidth: col.width
|
elideWidth: col.width
|
||||||
fullText: userInfoGrid.profile ? userInfoGrid.profile.userid : ""
|
fullText: userInfoGrid.profile ? userInfoGrid.profile.userid : ""
|
||||||
|
|
|
@ -41,7 +41,8 @@ Item {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: timelineLayout
|
id: timelineLayout
|
||||||
|
|
||||||
visible: room != null
|
visible: room != null && !room.isSpace
|
||||||
|
enabled: visible
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
|
@ -127,6 +128,79 @@ Item {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
visible: room != null && room.isSpace
|
||||||
|
enabled: visible
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Nheko.paddingLarge
|
||||||
|
spacing: Nheko.paddingLarge
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
url: room ? room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : ""
|
||||||
|
displayName: room ? room.roomName : ""
|
||||||
|
height: 130
|
||||||
|
width: 130
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixText {
|
||||||
|
text: room ? room.roomName : ""
|
||||||
|
font.pixelSize: 24
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixText {
|
||||||
|
text: qsTr("%1 member(s)").arg(room ? room.roomMemberCount : 0)
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
width: timelineView.width - Nheko.paddingLarge * 2
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
text: TimelineManager.escapeEmoji(room ? room.roomTopic : "")
|
||||||
|
wrapMode: TextEdit.WordWrap
|
||||||
|
textFormat: TextEdit.RichText
|
||||||
|
readOnly: true
|
||||||
|
background: null
|
||||||
|
selectByMouse: true
|
||||||
|
color: Nheko.colors.text
|
||||||
|
horizontalAlignment: TextEdit.AlignHCenter
|
||||||
|
onLinkActivated: Nheko.openLink(link)
|
||||||
|
|
||||||
|
CursorShape {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageButton {
|
||||||
|
id: backToRoomsButton
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Nheko.paddingMedium
|
||||||
|
width: Nheko.avatarSize
|
||||||
|
height: Nheko.avatarSize
|
||||||
|
visible: room != null && room.isSpace && showBackButton
|
||||||
|
enabled: visible
|
||||||
|
image: ":/icons/icons/ui/angle-pointing-to-left.png"
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Back to room list")
|
||||||
|
onClicked: Rooms.resetCurrentRoom()
|
||||||
|
}
|
||||||
|
|
||||||
NhekoDropArea {
|
NhekoDropArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
roomid: room ? room.roomId() : ""
|
roomid: room ? room.roomId() : ""
|
||||||
|
|
360
src/Cache.cpp
360
src/Cache.cpp
|
@ -55,6 +55,10 @@ constexpr auto BATCH_SIZE = 100;
|
||||||
//! Format: room_id -> RoomInfo
|
//! Format: room_id -> RoomInfo
|
||||||
constexpr auto ROOMS_DB("rooms");
|
constexpr auto ROOMS_DB("rooms");
|
||||||
constexpr auto INVITES_DB("invites");
|
constexpr auto INVITES_DB("invites");
|
||||||
|
//! maps each room to its parent space (id->id)
|
||||||
|
constexpr auto SPACES_PARENTS_DB("space_parents");
|
||||||
|
//! maps each space to its current children (id->id)
|
||||||
|
constexpr auto SPACES_CHILDREN_DB("space_children");
|
||||||
//! Information that must be kept between sync requests.
|
//! Information that must be kept between sync requests.
|
||||||
constexpr auto SYNC_STATE_DB("sync_state");
|
constexpr auto SYNC_STATE_DB("sync_state");
|
||||||
//! Read receipts per room/event.
|
//! Read receipts per room/event.
|
||||||
|
@ -237,12 +241,14 @@ Cache::setup()
|
||||||
env_.open(cacheDirectory_.toStdString().c_str());
|
env_.open(cacheDirectory_.toStdString().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto txn = lmdb::txn::begin(env_);
|
auto txn = lmdb::txn::begin(env_);
|
||||||
syncStateDb_ = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE);
|
syncStateDb_ = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE);
|
||||||
roomsDb_ = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE);
|
roomsDb_ = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE);
|
||||||
invitesDb_ = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE);
|
spacesChildrenDb_ = lmdb::dbi::open(txn, SPACES_CHILDREN_DB, MDB_CREATE | MDB_DUPSORT);
|
||||||
readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
|
spacesParentsDb_ = lmdb::dbi::open(txn, SPACES_PARENTS_DB, MDB_CREATE | MDB_DUPSORT);
|
||||||
notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE);
|
invitesDb_ = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE);
|
||||||
|
readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
|
||||||
|
notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE);
|
||||||
|
|
||||||
// Device management
|
// Device management
|
||||||
devicesDb_ = lmdb::dbi::open(txn, DEVICES_DB, MDB_CREATE);
|
devicesDb_ = lmdb::dbi::open(txn, DEVICES_DB, MDB_CREATE);
|
||||||
|
@ -899,7 +905,9 @@ Cache::runMigrations()
|
||||||
std::reverse(oldMessages.events.begin(),
|
std::reverse(oldMessages.events.begin(),
|
||||||
oldMessages.events.end());
|
oldMessages.events.end());
|
||||||
// save messages using the new method
|
// save messages using the new method
|
||||||
saveTimelineMessages(txn, room_id, oldMessages);
|
auto eventsDb = getEventsDb(txn, room_id);
|
||||||
|
saveTimelineMessages(
|
||||||
|
txn, eventsDb, room_id, oldMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete old messages db
|
// delete old messages db
|
||||||
|
@ -1194,24 +1202,73 @@ Cache::saveState(const mtx::responses::Sync &res)
|
||||||
|
|
||||||
auto userKeyCacheDb = getUserKeysDb(txn);
|
auto userKeyCacheDb = getUserKeysDb(txn);
|
||||||
|
|
||||||
|
std::set<std::string> spaces_with_updates;
|
||||||
|
std::set<std::string> rooms_with_space_updates;
|
||||||
|
|
||||||
// Save joined rooms
|
// Save joined rooms
|
||||||
for (const auto &room : res.rooms.join) {
|
for (const auto &room : res.rooms.join) {
|
||||||
auto statesdb = getStatesDb(txn, room.first);
|
auto statesdb = getStatesDb(txn, room.first);
|
||||||
auto stateskeydb = getStatesKeyDb(txn, room.first);
|
auto stateskeydb = getStatesKeyDb(txn, room.first);
|
||||||
auto membersdb = getMembersDb(txn, room.first);
|
auto membersdb = getMembersDb(txn, room.first);
|
||||||
|
auto eventsDb = getEventsDb(txn, room.first);
|
||||||
|
|
||||||
saveStateEvents(
|
saveStateEvents(txn,
|
||||||
txn, statesdb, stateskeydb, membersdb, room.first, room.second.state.events);
|
statesdb,
|
||||||
saveStateEvents(
|
stateskeydb,
|
||||||
txn, statesdb, stateskeydb, membersdb, room.first, room.second.timeline.events);
|
membersdb,
|
||||||
|
eventsDb,
|
||||||
|
room.first,
|
||||||
|
room.second.state.events);
|
||||||
|
saveStateEvents(txn,
|
||||||
|
statesdb,
|
||||||
|
stateskeydb,
|
||||||
|
membersdb,
|
||||||
|
eventsDb,
|
||||||
|
room.first,
|
||||||
|
room.second.timeline.events);
|
||||||
|
|
||||||
saveTimelineMessages(txn, room.first, room.second.timeline);
|
saveTimelineMessages(txn, eventsDb, room.first, room.second.timeline);
|
||||||
|
|
||||||
RoomInfo updatedInfo;
|
RoomInfo updatedInfo;
|
||||||
updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString();
|
updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString();
|
||||||
updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString();
|
updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString();
|
||||||
updatedInfo.avatar_url = getRoomAvatarUrl(txn, statesdb, membersdb).toStdString();
|
updatedInfo.avatar_url = getRoomAvatarUrl(txn, statesdb, membersdb).toStdString();
|
||||||
updatedInfo.version = getRoomVersion(txn, statesdb).toStdString();
|
updatedInfo.version = getRoomVersion(txn, statesdb).toStdString();
|
||||||
|
updatedInfo.is_space = getRoomIsSpace(txn, statesdb);
|
||||||
|
|
||||||
|
if (updatedInfo.is_space) {
|
||||||
|
bool space_updates = false;
|
||||||
|
for (const auto &e : room.second.state.events)
|
||||||
|
if (std::holds_alternative<StateEvent<state::space::Child>>(e) ||
|
||||||
|
std::holds_alternative<StateEvent<state::PowerLevels>>(e))
|
||||||
|
space_updates = true;
|
||||||
|
for (const auto &e : room.second.timeline.events)
|
||||||
|
if (std::holds_alternative<StateEvent<state::space::Child>>(e) ||
|
||||||
|
std::holds_alternative<StateEvent<state::PowerLevels>>(e))
|
||||||
|
space_updates = true;
|
||||||
|
|
||||||
|
if (space_updates)
|
||||||
|
spaces_with_updates.insert(room.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
bool room_has_space_update = false;
|
||||||
|
for (const auto &e : room.second.state.events) {
|
||||||
|
if (auto se = std::get_if<StateEvent<state::space::Parent>>(&e)) {
|
||||||
|
spaces_with_updates.insert(se->state_key);
|
||||||
|
room_has_space_update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &e : room.second.timeline.events) {
|
||||||
|
if (auto se = std::get_if<StateEvent<state::space::Parent>>(&e)) {
|
||||||
|
spaces_with_updates.insert(se->state_key);
|
||||||
|
room_has_space_update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (room_has_space_update)
|
||||||
|
rooms_with_space_updates.insert(room.first);
|
||||||
|
}
|
||||||
|
|
||||||
bool has_new_tags = false;
|
bool has_new_tags = false;
|
||||||
// Process the account_data associated with this room
|
// Process the account_data associated with this room
|
||||||
|
@ -1291,6 +1348,8 @@ Cache::saveState(const mtx::responses::Sync &res)
|
||||||
|
|
||||||
removeLeftRooms(txn, res.rooms.leave);
|
removeLeftRooms(txn, res.rooms.leave);
|
||||||
|
|
||||||
|
updateSpaces(txn, spaces_with_updates, std::move(rooms_with_space_updates));
|
||||||
|
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
|
||||||
std::map<QString, bool> readStatus;
|
std::map<QString, bool> readStatus;
|
||||||
|
@ -1339,6 +1398,7 @@ Cache::saveInvites(lmdb::txn &txn, const std::map<std::string, mtx::responses::I
|
||||||
updatedInfo.topic = getInviteRoomTopic(txn, statesdb).toStdString();
|
updatedInfo.topic = getInviteRoomTopic(txn, statesdb).toStdString();
|
||||||
updatedInfo.avatar_url =
|
updatedInfo.avatar_url =
|
||||||
getInviteRoomAvatarUrl(txn, statesdb, membersdb).toStdString();
|
getInviteRoomAvatarUrl(txn, statesdb, membersdb).toStdString();
|
||||||
|
updatedInfo.is_space = getInviteRoomIsSpace(txn, statesdb);
|
||||||
updatedInfo.is_invite = true;
|
updatedInfo.is_invite = true;
|
||||||
|
|
||||||
invitesDb_.put(txn, room.first, json(updatedInfo).dump());
|
invitesDb_.put(txn, room.first, json(updatedInfo).dump());
|
||||||
|
@ -1427,27 +1487,6 @@ Cache::roomsWithStateUpdates(const mtx::responses::Sync &res)
|
||||||
return rooms;
|
return rooms;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string>
|
|
||||||
Cache::roomsWithTagUpdates(const mtx::responses::Sync &res)
|
|
||||||
{
|
|
||||||
using namespace mtx::events;
|
|
||||||
|
|
||||||
std::vector<std::string> rooms;
|
|
||||||
for (const auto &room : res.rooms.join) {
|
|
||||||
bool hasUpdates = false;
|
|
||||||
for (const auto &evt : room.second.account_data.events) {
|
|
||||||
if (std::holds_alternative<AccountDataEvent<account_data::Tags>>(evt)) {
|
|
||||||
hasUpdates = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasUpdates)
|
|
||||||
rooms.emplace_back(room.first);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rooms;
|
|
||||||
}
|
|
||||||
|
|
||||||
RoomInfo
|
RoomInfo
|
||||||
Cache::singleRoomInfo(const std::string &room_id)
|
Cache::singleRoomInfo(const std::string &room_id)
|
||||||
{
|
{
|
||||||
|
@ -1733,6 +1772,13 @@ Cache::relatedEvents(const std::string &room_id, const std::string &event_id)
|
||||||
return related_ids;
|
return related_ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
Cache::memberCount(const std::string &room_id)
|
||||||
|
{
|
||||||
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||||
|
return getMembersDb(txn, room_id).size(txn);
|
||||||
|
}
|
||||||
|
|
||||||
QMap<QString, RoomInfo>
|
QMap<QString, RoomInfo>
|
||||||
Cache::roomInfo(bool withInvites)
|
Cache::roomInfo(bool withInvites)
|
||||||
{
|
{
|
||||||
|
@ -2337,6 +2383,29 @@ Cache::getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb)
|
||||||
return QString("1");
|
return QString("1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Cache::getRoomIsSpace(lmdb::txn &txn, lmdb::dbi &statesdb)
|
||||||
|
{
|
||||||
|
using namespace mtx::events;
|
||||||
|
using namespace mtx::events::state;
|
||||||
|
|
||||||
|
std::string_view event;
|
||||||
|
bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
try {
|
||||||
|
StateEvent<Create> msg = json::parse(event);
|
||||||
|
|
||||||
|
return msg.content.type == mtx::events::state::room_type::space;
|
||||||
|
} catch (const json::exception &e) {
|
||||||
|
nhlog::db()->warn("failed to parse m.room.create event: {}", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\"");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<mtx::events::state::CanonicalAlias>
|
std::optional<mtx::events::state::CanonicalAlias>
|
||||||
Cache::getRoomAliases(const std::string &roomid)
|
Cache::getRoomAliases(const std::string &roomid)
|
||||||
{
|
{
|
||||||
|
@ -2464,6 +2533,27 @@ Cache::getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &db)
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Cache::getInviteRoomIsSpace(lmdb::txn &txn, lmdb::dbi &db)
|
||||||
|
{
|
||||||
|
using namespace mtx::events;
|
||||||
|
using namespace mtx::events::state;
|
||||||
|
|
||||||
|
std::string_view event;
|
||||||
|
bool res = db.get(txn, to_string(mtx::events::EventType::RoomCreate), event);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
try {
|
||||||
|
StrippedEvent<Create> msg = json::parse(event);
|
||||||
|
return msg.content.type == mtx::events::state::room_type::space;
|
||||||
|
} catch (const json::exception &e) {
|
||||||
|
nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string>
|
std::vector<std::string>
|
||||||
Cache::joinedRooms()
|
Cache::joinedRooms()
|
||||||
{
|
{
|
||||||
|
@ -2506,42 +2596,6 @@ Cache::getMember(const std::string &room_id, const std::string &user_id)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<RoomSearchResult>
|
|
||||||
Cache::searchRooms(const std::string &query, std::uint8_t max_items)
|
|
||||||
{
|
|
||||||
std::multimap<int, std::pair<std::string, RoomInfo>> items;
|
|
||||||
|
|
||||||
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
|
||||||
auto cursor = lmdb::cursor::open(txn, roomsDb_);
|
|
||||||
|
|
||||||
std::string_view room_id, room_data;
|
|
||||||
while (cursor.get(room_id, room_data, MDB_NEXT)) {
|
|
||||||
RoomInfo tmp = json::parse(room_data);
|
|
||||||
|
|
||||||
const int score = utils::levenshtein_distance(
|
|
||||||
query, QString::fromStdString(tmp.name).toLower().toStdString());
|
|
||||||
items.emplace(score, std::make_pair(room_id, tmp));
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor.close();
|
|
||||||
|
|
||||||
auto end = items.begin();
|
|
||||||
|
|
||||||
if (items.size() >= max_items)
|
|
||||||
std::advance(end, max_items);
|
|
||||||
else if (items.size() > 0)
|
|
||||||
std::advance(end, items.size());
|
|
||||||
|
|
||||||
std::vector<RoomSearchResult> results;
|
|
||||||
for (auto it = items.begin(); it != end; it++) {
|
|
||||||
results.push_back(RoomSearchResult{it->second.first, it->second.second});
|
|
||||||
}
|
|
||||||
|
|
||||||
txn.commit();
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<RoomMember>
|
std::vector<RoomMember>
|
||||||
Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len)
|
Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len)
|
||||||
{
|
{
|
||||||
|
@ -2600,11 +2654,12 @@ void
|
||||||
Cache::savePendingMessage(const std::string &room_id,
|
Cache::savePendingMessage(const std::string &room_id,
|
||||||
const mtx::events::collections::TimelineEvent &message)
|
const mtx::events::collections::TimelineEvent &message)
|
||||||
{
|
{
|
||||||
auto txn = lmdb::txn::begin(env_);
|
auto txn = lmdb::txn::begin(env_);
|
||||||
|
auto eventsDb = getEventsDb(txn, room_id);
|
||||||
|
|
||||||
mtx::responses::Timeline timeline;
|
mtx::responses::Timeline timeline;
|
||||||
timeline.events.push_back(message.data);
|
timeline.events.push_back(message.data);
|
||||||
saveTimelineMessages(txn, room_id, timeline);
|
saveTimelineMessages(txn, eventsDb, room_id, timeline);
|
||||||
|
|
||||||
auto pending = getPendingMessagesDb(txn, room_id);
|
auto pending = getPendingMessagesDb(txn, room_id);
|
||||||
|
|
||||||
|
@ -2672,13 +2727,13 @@ Cache::removePendingStatus(const std::string &room_id, const std::string &txn_id
|
||||||
|
|
||||||
void
|
void
|
||||||
Cache::saveTimelineMessages(lmdb::txn &txn,
|
Cache::saveTimelineMessages(lmdb::txn &txn,
|
||||||
|
lmdb::dbi &eventsDb,
|
||||||
const std::string &room_id,
|
const std::string &room_id,
|
||||||
const mtx::responses::Timeline &res)
|
const mtx::responses::Timeline &res)
|
||||||
{
|
{
|
||||||
if (res.events.empty())
|
if (res.events.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto eventsDb = getEventsDb(txn, room_id);
|
|
||||||
auto relationsDb = getRelationsDb(txn, room_id);
|
auto relationsDb = getRelationsDb(txn, room_id);
|
||||||
|
|
||||||
auto orderDb = getEventOrderDb(txn, room_id);
|
auto orderDb = getEventOrderDb(txn, room_id);
|
||||||
|
@ -3203,6 +3258,147 @@ Cache::deleteOldData() noexcept
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Cache::updateSpaces(lmdb::txn &txn,
|
||||||
|
const std::set<std::string> &spaces_with_updates,
|
||||||
|
std::set<std::string> rooms_with_updates)
|
||||||
|
{
|
||||||
|
if (spaces_with_updates.empty() && rooms_with_updates.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const auto &space : spaces_with_updates) {
|
||||||
|
// delete old entries
|
||||||
|
{
|
||||||
|
auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_);
|
||||||
|
bool first = true;
|
||||||
|
std::string_view sp = space, space_child = "";
|
||||||
|
|
||||||
|
if (cursor.get(sp, space_child, MDB_SET)) {
|
||||||
|
while (cursor.get(
|
||||||
|
sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
|
||||||
|
first = false;
|
||||||
|
spacesParentsDb_.del(txn, space_child, space);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
spacesChildrenDb_.del(txn, space);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &event :
|
||||||
|
getStateEventsWithType<mtx::events::state::space::Child>(txn, space)) {
|
||||||
|
if (event.content.via.has_value() && event.state_key.size() > 3 &&
|
||||||
|
event.state_key.at(0) == '!') {
|
||||||
|
spacesChildrenDb_.put(txn, space, event.state_key);
|
||||||
|
spacesParentsDb_.put(txn, event.state_key, space);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto space_event_type = to_string(mtx::events::EventType::RoomPowerLevels);
|
||||||
|
|
||||||
|
for (const auto &room : rooms_with_updates) {
|
||||||
|
for (const auto &event :
|
||||||
|
getStateEventsWithType<mtx::events::state::space::Parent>(txn, room)) {
|
||||||
|
if (event.content.via.has_value() && event.state_key.size() > 3 &&
|
||||||
|
event.state_key.at(0) == '!') {
|
||||||
|
const std::string &space = event.state_key;
|
||||||
|
|
||||||
|
auto pls =
|
||||||
|
getStateEvent<mtx::events::state::PowerLevels>(txn, space);
|
||||||
|
|
||||||
|
if (!pls)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (pls->content.user_level(event.sender) >=
|
||||||
|
pls->content.state_level(space_event_type)) {
|
||||||
|
spacesChildrenDb_.put(txn, space, room);
|
||||||
|
spacesParentsDb_.put(txn, room, space);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<QString, std::optional<RoomInfo>>
|
||||||
|
Cache::spaces()
|
||||||
|
{
|
||||||
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||||
|
|
||||||
|
QMap<QString, std::optional<RoomInfo>> ret;
|
||||||
|
{
|
||||||
|
auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_);
|
||||||
|
bool first = true;
|
||||||
|
std::string_view space_id, space_child;
|
||||||
|
while (cursor.get(space_id, space_child, first ? MDB_FIRST : MDB_NEXT)) {
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
if (!space_child.empty()) {
|
||||||
|
std::string_view room_data;
|
||||||
|
if (roomsDb_.get(txn, space_id, room_data)) {
|
||||||
|
RoomInfo tmp = json::parse(std::move(room_data));
|
||||||
|
ret.insert(
|
||||||
|
QString::fromUtf8(space_id.data(), space_id.size()), tmp);
|
||||||
|
} else {
|
||||||
|
ret.insert(
|
||||||
|
QString::fromUtf8(space_id.data(), space_id.size()),
|
||||||
|
std::nullopt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string>
|
||||||
|
Cache::getParentRoomIds(const std::string &room_id)
|
||||||
|
{
|
||||||
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||||
|
|
||||||
|
std::vector<std::string> roomids;
|
||||||
|
{
|
||||||
|
auto cursor = lmdb::cursor::open(txn, spacesParentsDb_);
|
||||||
|
bool first = true;
|
||||||
|
std::string_view sp = room_id, space_parent;
|
||||||
|
if (cursor.get(sp, space_parent, MDB_SET)) {
|
||||||
|
while (cursor.get(sp, space_parent, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
if (!space_parent.empty())
|
||||||
|
roomids.emplace_back(space_parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return roomids;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string>
|
||||||
|
Cache::getChildRoomIds(const std::string &room_id)
|
||||||
|
{
|
||||||
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||||
|
|
||||||
|
std::vector<std::string> roomids;
|
||||||
|
{
|
||||||
|
auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_);
|
||||||
|
bool first = true;
|
||||||
|
std::string_view sp = room_id, space_child;
|
||||||
|
if (cursor.get(sp, space_child, MDB_SET)) {
|
||||||
|
while (cursor.get(sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
if (!space_child.empty())
|
||||||
|
roomids.emplace_back(space_child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return roomids;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<mtx::events::collections::RoomAccountDataEvents>
|
std::optional<mtx::events::collections::RoomAccountDataEvents>
|
||||||
Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id)
|
Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id)
|
||||||
{
|
{
|
||||||
|
@ -3884,6 +4080,7 @@ to_json(json &j, const RoomInfo &info)
|
||||||
j["avatar_url"] = info.avatar_url;
|
j["avatar_url"] = info.avatar_url;
|
||||||
j["version"] = info.version;
|
j["version"] = info.version;
|
||||||
j["is_invite"] = info.is_invite;
|
j["is_invite"] = info.is_invite;
|
||||||
|
j["is_space"] = info.is_space;
|
||||||
j["join_rule"] = info.join_rule;
|
j["join_rule"] = info.join_rule;
|
||||||
j["guest_access"] = info.guest_access;
|
j["guest_access"] = info.guest_access;
|
||||||
|
|
||||||
|
@ -3903,6 +4100,7 @@ from_json(const json &j, RoomInfo &info)
|
||||||
info.version = j.value(
|
info.version = j.value(
|
||||||
"version", QCoreApplication::translate("RoomInfo", "no version stored").toStdString());
|
"version", QCoreApplication::translate("RoomInfo", "no version stored").toStdString());
|
||||||
info.is_invite = j.at("is_invite");
|
info.is_invite = j.at("is_invite");
|
||||||
|
info.is_space = j.value("is_space", false);
|
||||||
info.join_rule = j.at("join_rule");
|
info.join_rule = j.at("join_rule");
|
||||||
info.guest_access = j.at("guest_access");
|
info.guest_access = j.at("guest_access");
|
||||||
|
|
||||||
|
@ -4158,12 +4356,6 @@ getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
|
||||||
return instance_->getRoomAvatarUrl(txn, statesdb, membersdb);
|
return instance_->getRoomAvatarUrl(txn, statesdb, membersdb);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString
|
|
||||||
getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb)
|
|
||||||
{
|
|
||||||
return instance_->getRoomVersion(txn, statesdb);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<RoomMember>
|
std::vector<RoomMember>
|
||||||
getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len)
|
getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len)
|
||||||
{
|
{
|
||||||
|
@ -4305,11 +4497,7 @@ roomsWithStateUpdates(const mtx::responses::Sync &res)
|
||||||
{
|
{
|
||||||
return instance_->roomsWithStateUpdates(res);
|
return instance_->roomsWithStateUpdates(res);
|
||||||
}
|
}
|
||||||
std::vector<std::string>
|
|
||||||
roomsWithTagUpdates(const mtx::responses::Sync &res)
|
|
||||||
{
|
|
||||||
return instance_->roomsWithTagUpdates(res);
|
|
||||||
}
|
|
||||||
std::map<QString, RoomInfo>
|
std::map<QString, RoomInfo>
|
||||||
getRoomInfo(const std::vector<std::string> &rooms)
|
getRoomInfo(const std::vector<std::string> &rooms)
|
||||||
{
|
{
|
||||||
|
@ -4329,12 +4517,6 @@ calculateRoomReadStatus()
|
||||||
instance_->calculateRoomReadStatus();
|
instance_->calculateRoomReadStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<RoomSearchResult>
|
|
||||||
searchRooms(const std::string &query, std::uint8_t max_items)
|
|
||||||
{
|
|
||||||
return instance_->searchRooms(query, max_items);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
markSentNotification(const std::string &event_id)
|
markSentNotification(const std::string &event_id)
|
||||||
{
|
{
|
||||||
|
|
|
@ -79,9 +79,6 @@ getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
|
||||||
//! Retrieve the room avatar's url if any.
|
//! Retrieve the room avatar's url if any.
|
||||||
QString
|
QString
|
||||||
getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
|
getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
|
||||||
//! Retrieve the version of the room if any.
|
|
||||||
QString
|
|
||||||
getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb);
|
|
||||||
|
|
||||||
//! Retrieve member info from a room.
|
//! Retrieve member info from a room.
|
||||||
std::vector<RoomMember>
|
std::vector<RoomMember>
|
||||||
|
@ -166,9 +163,6 @@ calculateRoomReadStatus(const std::string &room_id);
|
||||||
void
|
void
|
||||||
calculateRoomReadStatus();
|
calculateRoomReadStatus();
|
||||||
|
|
||||||
std::vector<RoomSearchResult>
|
|
||||||
searchRooms(const std::string &query, std::uint8_t max_items = 5);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
markSentNotification(const std::string &event_id);
|
markSentNotification(const std::string &event_id);
|
||||||
//! Removes an event from the sent notifications.
|
//! Removes an event from the sent notifications.
|
||||||
|
|
|
@ -76,6 +76,8 @@ struct RoomInfo
|
||||||
std::string version;
|
std::string version;
|
||||||
//! Whether or not the room is an invite.
|
//! Whether or not the room is an invite.
|
||||||
bool is_invite = false;
|
bool is_invite = false;
|
||||||
|
//! Wheter or not the room is a space
|
||||||
|
bool is_space = false;
|
||||||
//! Total number of members in the room.
|
//! Total number of members in the room.
|
||||||
size_t member_count = 0;
|
size_t member_count = 0;
|
||||||
//! Who can access to the room.
|
//! Who can access to the room.
|
||||||
|
|
|
@ -72,6 +72,7 @@ public:
|
||||||
std::optional<mtx::events::state::CanonicalAlias> getRoomAliases(const std::string &roomid);
|
std::optional<mtx::events::state::CanonicalAlias> getRoomAliases(const std::string &roomid);
|
||||||
QHash<QString, RoomInfo> invites();
|
QHash<QString, RoomInfo> invites();
|
||||||
std::optional<RoomInfo> invite(std::string_view roomid);
|
std::optional<RoomInfo> invite(std::string_view roomid);
|
||||||
|
QMap<QString, std::optional<RoomInfo>> spaces();
|
||||||
|
|
||||||
//! Calculate & return the name of the room.
|
//! Calculate & return the name of the room.
|
||||||
QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
|
QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
|
||||||
|
@ -84,6 +85,8 @@ public:
|
||||||
QString getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
|
QString getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
|
||||||
//! Retrieve the version of the room if any.
|
//! Retrieve the version of the room if any.
|
||||||
QString getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb);
|
QString getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb);
|
||||||
|
//! Retrieve if the room is a space
|
||||||
|
bool getRoomIsSpace(lmdb::txn &txn, lmdb::dbi &statesdb);
|
||||||
|
|
||||||
//! Get a specific state event
|
//! Get a specific state event
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
@ -98,6 +101,7 @@ public:
|
||||||
std::vector<RoomMember> getMembers(const std::string &room_id,
|
std::vector<RoomMember> getMembers(const std::string &room_id,
|
||||||
std::size_t startIndex = 0,
|
std::size_t startIndex = 0,
|
||||||
std::size_t len = 30);
|
std::size_t len = 30);
|
||||||
|
size_t memberCount(const std::string &room_id);
|
||||||
|
|
||||||
void saveState(const mtx::responses::Sync &res);
|
void saveState(const mtx::responses::Sync &res);
|
||||||
bool isInitialized();
|
bool isInitialized();
|
||||||
|
@ -146,7 +150,6 @@ public:
|
||||||
|
|
||||||
RoomInfo singleRoomInfo(const std::string &room_id);
|
RoomInfo singleRoomInfo(const std::string &room_id);
|
||||||
std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res);
|
std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res);
|
||||||
std::vector<std::string> roomsWithTagUpdates(const mtx::responses::Sync &res);
|
|
||||||
std::map<QString, RoomInfo> getRoomInfo(const std::vector<std::string> &rooms);
|
std::map<QString, RoomInfo> getRoomInfo(const std::vector<std::string> &rooms);
|
||||||
|
|
||||||
//! Calculates which the read status of a room.
|
//! Calculates which the read status of a room.
|
||||||
|
@ -154,9 +157,6 @@ public:
|
||||||
bool calculateRoomReadStatus(const std::string &room_id);
|
bool calculateRoomReadStatus(const std::string &room_id);
|
||||||
void calculateRoomReadStatus();
|
void calculateRoomReadStatus();
|
||||||
|
|
||||||
std::vector<RoomSearchResult> searchRooms(const std::string &query,
|
|
||||||
std::uint8_t max_items = 5);
|
|
||||||
|
|
||||||
void markSentNotification(const std::string &event_id);
|
void markSentNotification(const std::string &event_id);
|
||||||
//! Removes an event from the sent notifications.
|
//! Removes an event from the sent notifications.
|
||||||
void removeReadNotification(const std::string &event_id);
|
void removeReadNotification(const std::string &event_id);
|
||||||
|
@ -222,6 +222,8 @@ public:
|
||||||
void deleteOldData() noexcept;
|
void deleteOldData() noexcept;
|
||||||
//! Retrieve all saved room ids.
|
//! Retrieve all saved room ids.
|
||||||
std::vector<std::string> getRoomIds(lmdb::txn &txn);
|
std::vector<std::string> getRoomIds(lmdb::txn &txn);
|
||||||
|
std::vector<std::string> getParentRoomIds(const std::string &room_id);
|
||||||
|
std::vector<std::string> getChildRoomIds(const std::string &room_id);
|
||||||
|
|
||||||
//! Mark a room that uses e2e encryption.
|
//! Mark a room that uses e2e encryption.
|
||||||
void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
|
void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
|
||||||
|
@ -327,12 +329,14 @@ private:
|
||||||
QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
|
QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
|
||||||
QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
|
QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
|
||||||
QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
|
QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
|
||||||
|
bool getInviteRoomIsSpace(lmdb::txn &txn, lmdb::dbi &db);
|
||||||
|
|
||||||
std::optional<MemberInfo> getMember(const std::string &room_id, const std::string &user_id);
|
std::optional<MemberInfo> getMember(const std::string &room_id, const std::string &user_id);
|
||||||
|
|
||||||
std::string getLastEventId(lmdb::txn &txn, const std::string &room_id);
|
std::string getLastEventId(lmdb::txn &txn, const std::string &room_id);
|
||||||
DescInfo getLastMessageInfo(lmdb::txn &txn, const std::string &room_id);
|
DescInfo getLastMessageInfo(lmdb::txn &txn, const std::string &room_id);
|
||||||
void saveTimelineMessages(lmdb::txn &txn,
|
void saveTimelineMessages(lmdb::txn &txn,
|
||||||
|
lmdb::dbi &eventsDb,
|
||||||
const std::string &room_id,
|
const std::string &room_id,
|
||||||
const mtx::responses::Timeline &res);
|
const mtx::responses::Timeline &res);
|
||||||
|
|
||||||
|
@ -351,11 +355,12 @@ private:
|
||||||
lmdb::dbi &statesdb,
|
lmdb::dbi &statesdb,
|
||||||
lmdb::dbi &stateskeydb,
|
lmdb::dbi &stateskeydb,
|
||||||
lmdb::dbi &membersdb,
|
lmdb::dbi &membersdb,
|
||||||
|
lmdb::dbi &eventsDb,
|
||||||
const std::string &room_id,
|
const std::string &room_id,
|
||||||
const std::vector<T> &events)
|
const std::vector<T> &events)
|
||||||
{
|
{
|
||||||
for (const auto &e : events)
|
for (const auto &e : events)
|
||||||
saveStateEvent(txn, statesdb, stateskeydb, membersdb, room_id, e);
|
saveStateEvent(txn, statesdb, stateskeydb, membersdb, eventsDb, room_id, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
|
@ -363,6 +368,7 @@ private:
|
||||||
lmdb::dbi &statesdb,
|
lmdb::dbi &statesdb,
|
||||||
lmdb::dbi &stateskeydb,
|
lmdb::dbi &stateskeydb,
|
||||||
lmdb::dbi &membersdb,
|
lmdb::dbi &membersdb,
|
||||||
|
lmdb::dbi &eventsDb,
|
||||||
const std::string &room_id,
|
const std::string &room_id,
|
||||||
const T &event)
|
const T &event)
|
||||||
{
|
{
|
||||||
|
@ -399,8 +405,10 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
std::visit(
|
std::visit(
|
||||||
[&txn, &statesdb, &stateskeydb](auto e) {
|
[&txn, &statesdb, &stateskeydb, &eventsDb](auto e) {
|
||||||
if constexpr (isStateEvent(e))
|
if constexpr (isStateEvent(e)) {
|
||||||
|
eventsDb.put(txn, e.event_id, json(e).dump());
|
||||||
|
|
||||||
if (e.type != EventType::Unsupported) {
|
if (e.type != EventType::Unsupported) {
|
||||||
if (e.state_key.empty())
|
if (e.state_key.empty())
|
||||||
statesdb.put(
|
statesdb.put(
|
||||||
|
@ -415,6 +423,7 @@ private:
|
||||||
})
|
})
|
||||||
.dump());
|
.dump());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
event);
|
event);
|
||||||
}
|
}
|
||||||
|
@ -430,20 +439,22 @@ private:
|
||||||
|
|
||||||
if (room_id.empty())
|
if (room_id.empty())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
const auto typeStr = to_string(type);
|
||||||
|
|
||||||
std::string_view value;
|
std::string_view value;
|
||||||
if (state_key.empty()) {
|
if (state_key.empty()) {
|
||||||
auto db = getStatesDb(txn, room_id);
|
auto db = getStatesDb(txn, room_id);
|
||||||
if (!db.get(txn, to_string(type), value)) {
|
if (!db.get(txn, typeStr, value)) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto db = getStatesKeyDb(txn, room_id);
|
auto db = getStatesKeyDb(txn, room_id);
|
||||||
std::string d = json::object({{"key", state_key}}).dump();
|
std::string d = json::object({{"key", state_key}}).dump();
|
||||||
std::string_view data = d;
|
std::string_view data = d;
|
||||||
|
std::string_view typeStrV = typeStr;
|
||||||
|
|
||||||
auto cursor = lmdb::cursor::open(txn, db);
|
auto cursor = lmdb::cursor::open(txn, db);
|
||||||
if (!cursor.get(state_key, data, MDB_GET_BOTH))
|
if (!cursor.get(typeStrV, data, MDB_GET_BOTH))
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -463,6 +474,47 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::vector<mtx::events::StateEvent<T>> getStateEventsWithType(lmdb::txn &txn,
|
||||||
|
const std::string &room_id)
|
||||||
|
|
||||||
|
{
|
||||||
|
constexpr auto type = mtx::events::state_content_to_type<T>;
|
||||||
|
static_assert(type != mtx::events::EventType::Unsupported,
|
||||||
|
"Not a supported type in state events.");
|
||||||
|
|
||||||
|
if (room_id.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::vector<mtx::events::StateEvent<T>> events;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto db = getStatesKeyDb(txn, room_id);
|
||||||
|
auto eventsDb = getEventsDb(txn, room_id);
|
||||||
|
const auto typeStr = to_string(type);
|
||||||
|
std::string_view typeStrV = typeStr;
|
||||||
|
std::string_view data;
|
||||||
|
std::string_view value;
|
||||||
|
|
||||||
|
auto cursor = lmdb::cursor::open(txn, db);
|
||||||
|
bool first = true;
|
||||||
|
if (cursor.get(typeStrV, data, MDB_SET)) {
|
||||||
|
while (cursor.get(
|
||||||
|
typeStrV, data, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
if (eventsDb.get(txn,
|
||||||
|
json::parse(data)["id"].get<std::string>(),
|
||||||
|
value))
|
||||||
|
events.push_back(
|
||||||
|
json::parse(value)
|
||||||
|
.get<mtx::events::StateEvent<T>>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
void saveInvites(lmdb::txn &txn,
|
void saveInvites(lmdb::txn &txn,
|
||||||
const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
|
const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
|
||||||
|
|
||||||
|
@ -482,6 +534,10 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateSpaces(lmdb::txn &txn,
|
||||||
|
const std::set<std::string> &spaces_with_updates,
|
||||||
|
std::set<std::string> rooms_with_updates);
|
||||||
|
|
||||||
lmdb::dbi getPendingReceiptsDb(lmdb::txn &txn)
|
lmdb::dbi getPendingReceiptsDb(lmdb::txn &txn)
|
||||||
{
|
{
|
||||||
return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
|
return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
|
||||||
|
@ -548,8 +604,8 @@ private:
|
||||||
|
|
||||||
lmdb::dbi getStatesKeyDb(lmdb::txn &txn, const std::string &room_id)
|
lmdb::dbi getStatesKeyDb(lmdb::txn &txn, const std::string &room_id)
|
||||||
{
|
{
|
||||||
auto db =
|
auto db = lmdb::dbi::open(
|
||||||
lmdb::dbi::open(txn, std::string(room_id + "/state_by_key").c_str(), MDB_CREATE);
|
txn, std::string(room_id + "/state_by_key").c_str(), MDB_CREATE | MDB_DUPSORT);
|
||||||
lmdb::dbi_set_dupsort(txn, db, compare_state_key);
|
lmdb::dbi_set_dupsort(txn, db, compare_state_key);
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
@ -611,6 +667,7 @@ private:
|
||||||
lmdb::env env_;
|
lmdb::env env_;
|
||||||
lmdb::dbi syncStateDb_;
|
lmdb::dbi syncStateDb_;
|
||||||
lmdb::dbi roomsDb_;
|
lmdb::dbi roomsDb_;
|
||||||
|
lmdb::dbi spacesChildrenDb_, spacesParentsDb_;
|
||||||
lmdb::dbi invitesDb_;
|
lmdb::dbi invitesDb_;
|
||||||
lmdb::dbi readReceiptsDb_;
|
lmdb::dbi readReceiptsDb_;
|
||||||
lmdb::dbi notificationsDb_;
|
lmdb::dbi notificationsDb_;
|
||||||
|
|
|
@ -613,6 +613,7 @@ UserSettings::save()
|
||||||
settings.setValue("mobile_mode", mobileMode_);
|
settings.setValue("mobile_mode", mobileMode_);
|
||||||
settings.setValue("font_size", baseFontSize_);
|
settings.setValue("font_size", baseFontSize_);
|
||||||
settings.setValue("typing_notifications", typingNotifications_);
|
settings.setValue("typing_notifications", typingNotifications_);
|
||||||
|
settings.setValue("sort_by_unread", sortByImportance_);
|
||||||
settings.setValue("minor_events", sortByImportance_);
|
settings.setValue("minor_events", sortByImportance_);
|
||||||
settings.setValue("read_receipts", readReceipts_);
|
settings.setValue("read_receipts", readReceipts_);
|
||||||
settings.setValue("group_view", groupView_);
|
settings.setValue("group_view", groupView_);
|
||||||
|
|
|
@ -44,8 +44,23 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
|
||||||
case CommunitiesModel::Roles::Id:
|
case CommunitiesModel::Roles::Id:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
} else if (index.row() - 1 < tags_.size()) {
|
} else if (index.row() - 1 < spaceOrder_.size()) {
|
||||||
auto tag = tags_.at(index.row() - 1);
|
auto id = spaceOrder_.at(index.row() - 1);
|
||||||
|
switch (role) {
|
||||||
|
case CommunitiesModel::Roles::AvatarUrl:
|
||||||
|
return QString::fromStdString(spaces_.at(id).avatar_url);
|
||||||
|
case CommunitiesModel::Roles::DisplayName:
|
||||||
|
case CommunitiesModel::Roles::Tooltip:
|
||||||
|
return QString::fromStdString(spaces_.at(id).name);
|
||||||
|
case CommunitiesModel::Roles::ChildrenHidden:
|
||||||
|
return true;
|
||||||
|
case CommunitiesModel::Roles::Hidden:
|
||||||
|
return hiddentTagIds_.contains("space:" + id);
|
||||||
|
case CommunitiesModel::Roles::Id:
|
||||||
|
return "space:" + id;
|
||||||
|
}
|
||||||
|
} else if (index.row() - 1 < tags_.size() + spaceOrder_.size()) {
|
||||||
|
auto tag = tags_.at(index.row() - 1 - spaceOrder_.size());
|
||||||
if (tag == "m.favourite") {
|
if (tag == "m.favourite") {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case CommunitiesModel::Roles::AvatarUrl:
|
case CommunitiesModel::Roles::AvatarUrl:
|
||||||
|
@ -78,7 +93,6 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
|
||||||
case CommunitiesModel::Roles::AvatarUrl:
|
case CommunitiesModel::Roles::AvatarUrl:
|
||||||
return QString(":/icons/icons/ui/tag.png");
|
return QString(":/icons/icons/ui/tag.png");
|
||||||
case CommunitiesModel::Roles::DisplayName:
|
case CommunitiesModel::Roles::DisplayName:
|
||||||
return tag.mid(2);
|
|
||||||
case CommunitiesModel::Roles::Tooltip:
|
case CommunitiesModel::Roles::Tooltip:
|
||||||
return tag.mid(2);
|
return tag.mid(2);
|
||||||
}
|
}
|
||||||
|
@ -99,17 +113,27 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
|
||||||
void
|
void
|
||||||
CommunitiesModel::initializeSidebar()
|
CommunitiesModel::initializeSidebar()
|
||||||
{
|
{
|
||||||
|
beginResetModel();
|
||||||
|
tags_.clear();
|
||||||
|
spaceOrder_.clear();
|
||||||
|
spaces_.clear();
|
||||||
|
|
||||||
std::set<std::string> ts;
|
std::set<std::string> ts;
|
||||||
for (const auto &e : cache::roomInfo()) {
|
std::vector<RoomInfo> tempSpaces;
|
||||||
for (const auto &t : e.tags) {
|
auto infos = cache::roomInfo();
|
||||||
if (t.find("u.") == 0 || t.find("m." == 0)) {
|
for (auto it = infos.begin(); it != infos.end(); it++) {
|
||||||
ts.insert(t);
|
if (it.value().is_space) {
|
||||||
|
spaceOrder_.push_back(it.key());
|
||||||
|
spaces_[it.key()] = it.value();
|
||||||
|
} else {
|
||||||
|
for (const auto &t : it.value().tags) {
|
||||||
|
if (t.find("u.") == 0 || t.find("m." == 0)) {
|
||||||
|
ts.insert(t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
beginResetModel();
|
|
||||||
tags_.clear();
|
|
||||||
for (const auto &t : ts)
|
for (const auto &t : ts)
|
||||||
tags_.push_back(QString::fromStdString(t));
|
tags_.push_back(QString::fromStdString(t));
|
||||||
|
|
||||||
|
@ -143,6 +167,25 @@ CommunitiesModel::sync(const mtx::responses::Rooms &rooms)
|
||||||
mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) {
|
mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) {
|
||||||
tagsUpdated = true;
|
tagsUpdated = true;
|
||||||
}
|
}
|
||||||
|
for (const auto &e : room.state.events)
|
||||||
|
if (std::holds_alternative<
|
||||||
|
mtx::events::StateEvent<mtx::events::state::space::Child>>(e) ||
|
||||||
|
std::holds_alternative<
|
||||||
|
mtx::events::StateEvent<mtx::events::state::space::Parent>>(e)) {
|
||||||
|
tagsUpdated = true;
|
||||||
|
}
|
||||||
|
for (const auto &e : room.timeline.events)
|
||||||
|
if (std::holds_alternative<
|
||||||
|
mtx::events::StateEvent<mtx::events::state::space::Child>>(e) ||
|
||||||
|
std::holds_alternative<
|
||||||
|
mtx::events::StateEvent<mtx::events::state::space::Parent>>(e)) {
|
||||||
|
tagsUpdated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &[roomid, room] : rooms.leave) {
|
||||||
|
(void)room;
|
||||||
|
if (spaceOrder_.contains(QString::fromStdString(roomid)))
|
||||||
|
tagsUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagsUpdated)
|
if (tagsUpdated)
|
||||||
|
@ -161,6 +204,15 @@ CommunitiesModel::setCurrentTagId(QString tagId)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (tagId.startsWith("space:")) {
|
||||||
|
auto tag = tagId.mid(6);
|
||||||
|
for (const auto &t : spaceOrder_) {
|
||||||
|
if (t == tag) {
|
||||||
|
this->currentTagId_ = tagId;
|
||||||
|
emit currentTagIdChanged(currentTagId_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->currentTagId_ = "";
|
this->currentTagId_ = "";
|
||||||
|
@ -181,7 +233,13 @@ CommunitiesModel::toggleTagId(QString tagId)
|
||||||
if (tagId.startsWith("tag:")) {
|
if (tagId.startsWith("tag:")) {
|
||||||
auto idx = tags_.indexOf(tagId.mid(4));
|
auto idx = tags_.indexOf(tagId.mid(4));
|
||||||
if (idx != -1)
|
if (idx != -1)
|
||||||
emit dataChanged(index(idx), index(idx), {Hidden});
|
emit dataChanged(index(idx + 1 + spaceOrder_.size()),
|
||||||
|
index(idx + 1 + spaceOrder_.size()),
|
||||||
|
{Hidden});
|
||||||
|
} else if (tagId.startsWith("space:")) {
|
||||||
|
auto idx = spaceOrder_.indexOf(tagId.mid(6));
|
||||||
|
if (idx != -1)
|
||||||
|
emit dataChanged(index(idx + 1), index(idx + 1), {Hidden});
|
||||||
}
|
}
|
||||||
|
|
||||||
emit hiddenTagsChanged();
|
emit hiddenTagsChanged();
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
#include <mtx/responses/sync.hpp>
|
#include <mtx/responses/sync.hpp>
|
||||||
|
|
||||||
|
#include "CacheStructs.h"
|
||||||
|
|
||||||
class CommunitiesModel : public QAbstractListModel
|
class CommunitiesModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -71,4 +73,6 @@ private:
|
||||||
QStringList tags_;
|
QStringList tags_;
|
||||||
QString currentTagId_;
|
QString currentTagId_;
|
||||||
QStringList hiddentTagIds_;
|
QStringList hiddentTagIds_;
|
||||||
|
QStringList spaceOrder_;
|
||||||
|
std::map<QString, RoomInfo> spaces_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -675,6 +675,9 @@ EventStore::decryptEvent(const IdIndex &idx,
|
||||||
index.room_id,
|
index.room_id,
|
||||||
index.session_id,
|
index.session_id,
|
||||||
e.sender);
|
e.sender);
|
||||||
|
// we may not want to request keys during initial sync and such
|
||||||
|
if (suppressKeyRequests)
|
||||||
|
break;
|
||||||
// TODO: Check if this actually works and look in key backup
|
// TODO: Check if this actually works and look in key backup
|
||||||
auto copy = e;
|
auto copy = e;
|
||||||
copy.room_id = room_id_;
|
copy.room_id = room_id_;
|
||||||
|
@ -816,6 +819,18 @@ EventStore::decryptEvent(const IdIndex &idx,
|
||||||
return asCacheEntry(std::move(decryptionResult.event.value()));
|
return asCacheEntry(std::move(decryptionResult.event.value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
EventStore::enableKeyRequests(bool suppressKeyRequests_)
|
||||||
|
{
|
||||||
|
if (!suppressKeyRequests_) {
|
||||||
|
for (const auto &key : decryptedEvents_.keys())
|
||||||
|
if (key.room == this->room_id_)
|
||||||
|
decryptedEvents_.remove(key);
|
||||||
|
suppressKeyRequests = false;
|
||||||
|
} else
|
||||||
|
suppressKeyRequests = true;
|
||||||
|
}
|
||||||
|
|
||||||
mtx::events::collections::TimelineEvents *
|
mtx::events::collections::TimelineEvents *
|
||||||
EventStore::get(std::string id, std::string_view related_to, bool decrypt, bool resolve_edits)
|
EventStore::get(std::string id, std::string_view related_to, bool decrypt, bool resolve_edits)
|
||||||
{
|
{
|
||||||
|
|
|
@ -115,6 +115,7 @@ public slots:
|
||||||
void addPending(mtx::events::collections::TimelineEvents event);
|
void addPending(mtx::events::collections::TimelineEvents event);
|
||||||
void receivedSessionKey(const std::string &session_id);
|
void receivedSessionKey(const std::string &session_id);
|
||||||
void clearTimeline();
|
void clearTimeline();
|
||||||
|
void enableKeyRequests(bool suppressKeyRequests_);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id);
|
std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id);
|
||||||
|
@ -142,4 +143,5 @@ private:
|
||||||
std::string current_txn;
|
std::string current_txn;
|
||||||
int current_txn_error_count = 0;
|
int current_txn_error_count = 0;
|
||||||
bool noMoreMessages = false;
|
bool noMoreMessages = false;
|
||||||
|
bool suppressKeyRequests = true;
|
||||||
};
|
};
|
||||||
|
|
|
@ -51,6 +51,7 @@ RoomlistModel::roleNames() const
|
||||||
{IsInvite, "isInvite"},
|
{IsInvite, "isInvite"},
|
||||||
{IsSpace, "isSpace"},
|
{IsSpace, "isSpace"},
|
||||||
{Tags, "tags"},
|
{Tags, "tags"},
|
||||||
|
{ParentSpaces, "parentSpaces"},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,8 +85,9 @@ RoomlistModel::data(const QModelIndex &index, int role) const
|
||||||
case Roles::NotificationCount:
|
case Roles::NotificationCount:
|
||||||
return room->notificationCount();
|
return room->notificationCount();
|
||||||
case Roles::IsInvite:
|
case Roles::IsInvite:
|
||||||
case Roles::IsSpace:
|
|
||||||
return false;
|
return false;
|
||||||
|
case Roles::IsSpace:
|
||||||
|
return room->isSpace();
|
||||||
case Roles::Tags: {
|
case Roles::Tags: {
|
||||||
auto info = cache::singleRoomInfo(roomid.toStdString());
|
auto info = cache::singleRoomInfo(roomid.toStdString());
|
||||||
QStringList list;
|
QStringList list;
|
||||||
|
@ -93,6 +95,14 @@ RoomlistModel::data(const QModelIndex &index, int role) const
|
||||||
list.push_back(QString::fromStdString(t));
|
list.push_back(QString::fromStdString(t));
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
case Roles::ParentSpaces: {
|
||||||
|
auto parents =
|
||||||
|
cache::client()->getParentRoomIds(roomid.toStdString());
|
||||||
|
QStringList list;
|
||||||
|
for (const auto &t : parents)
|
||||||
|
list.push_back(QString::fromStdString(t));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -122,6 +132,14 @@ RoomlistModel::data(const QModelIndex &index, int role) const
|
||||||
return false;
|
return false;
|
||||||
case Roles::Tags:
|
case Roles::Tags:
|
||||||
return QStringList();
|
return QStringList();
|
||||||
|
case Roles::ParentSpaces: {
|
||||||
|
auto parents =
|
||||||
|
cache::client()->getParentRoomIds(roomid.toStdString());
|
||||||
|
QStringList list;
|
||||||
|
for (const auto &t : parents)
|
||||||
|
list.push_back(QString::fromStdString(t));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -412,7 +430,9 @@ enum NotificationImportance : short
|
||||||
AllEventsRead = 0,
|
AllEventsRead = 0,
|
||||||
NewMessage = 1,
|
NewMessage = 1,
|
||||||
NewMentions = 2,
|
NewMentions = 2,
|
||||||
Invite = 3
|
Invite = 3,
|
||||||
|
SubSpace = 4,
|
||||||
|
CurrentSpace = 5,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -422,7 +442,13 @@ FilteredRoomlistModel::calculateImportance(const QModelIndex &idx) const
|
||||||
// Returns the degree of importance of the unread messages in the room.
|
// Returns the degree of importance of the unread messages in the room.
|
||||||
// If sorting by importance is disabled in settings, this only ever
|
// If sorting by importance is disabled in settings, this only ever
|
||||||
// returns ImportanceDisabled or Invite
|
// returns ImportanceDisabled or Invite
|
||||||
if (sourceModel()->data(idx, RoomlistModel::IsInvite).toBool()) {
|
if (sourceModel()->data(idx, RoomlistModel::IsSpace).toBool()) {
|
||||||
|
if (filterType == FilterBy::Space &&
|
||||||
|
filterStr == sourceModel()->data(idx, RoomlistModel::RoomId).toString())
|
||||||
|
return CurrentSpace;
|
||||||
|
else
|
||||||
|
return SubSpace;
|
||||||
|
} else if (sourceModel()->data(idx, RoomlistModel::IsInvite).toBool()) {
|
||||||
return Invite;
|
return Invite;
|
||||||
} else if (!this->sortByImportance) {
|
} else if (!this->sortByImportance) {
|
||||||
return ImportanceDisabled;
|
return ImportanceDisabled;
|
||||||
|
@ -505,6 +531,12 @@ bool
|
||||||
FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) const
|
FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) const
|
||||||
{
|
{
|
||||||
if (filterType == FilterBy::Nothing) {
|
if (filterType == FilterBy::Nothing) {
|
||||||
|
if (sourceModel()
|
||||||
|
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
|
||||||
|
.toBool()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!hiddenTags.empty()) {
|
if (!hiddenTags.empty()) {
|
||||||
auto tags =
|
auto tags =
|
||||||
sourceModel()
|
sourceModel()
|
||||||
|
@ -516,19 +548,86 @@ FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) cons
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hiddenSpaces.empty()) {
|
||||||
|
auto parents =
|
||||||
|
sourceModel()
|
||||||
|
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
|
||||||
|
.toStringList();
|
||||||
|
for (const auto &t : parents)
|
||||||
|
if (hiddenSpaces.contains(t))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (filterType == FilterBy::Tag) {
|
} else if (filterType == FilterBy::Tag) {
|
||||||
|
if (sourceModel()
|
||||||
|
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
|
||||||
|
.toBool()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
auto tags = sourceModel()
|
auto tags = sourceModel()
|
||||||
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags)
|
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags)
|
||||||
.toStringList();
|
.toStringList();
|
||||||
|
|
||||||
if (!tags.contains(filterStr))
|
if (!tags.contains(filterStr))
|
||||||
return false;
|
return false;
|
||||||
else if (!hiddenTags.empty()) {
|
|
||||||
|
if (!hiddenTags.empty()) {
|
||||||
for (const auto &t : tags)
|
for (const auto &t : tags)
|
||||||
if (t != filterStr && hiddenTags.contains(t))
|
if (t != filterStr && hiddenTags.contains(t))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hiddenSpaces.empty()) {
|
||||||
|
auto parents =
|
||||||
|
sourceModel()
|
||||||
|
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
|
||||||
|
.toStringList();
|
||||||
|
for (const auto &t : parents)
|
||||||
|
if (hiddenSpaces.contains(t))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (filterType == FilterBy::Space) {
|
||||||
|
if (filterStr == sourceModel()
|
||||||
|
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::RoomId)
|
||||||
|
.toString())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto parents =
|
||||||
|
sourceModel()
|
||||||
|
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
|
||||||
|
.toStringList();
|
||||||
|
|
||||||
|
if (!parents.contains(filterStr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!hiddenTags.empty()) {
|
||||||
|
auto tags =
|
||||||
|
sourceModel()
|
||||||
|
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags)
|
||||||
|
.toStringList();
|
||||||
|
|
||||||
|
for (const auto &t : tags)
|
||||||
|
if (hiddenTags.contains(t))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hiddenSpaces.empty()) {
|
||||||
|
for (const auto &t : parents)
|
||||||
|
if (hiddenSpaces.contains(t))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceModel()
|
||||||
|
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
|
||||||
|
.toBool() &&
|
||||||
|
!parents.contains(filterStr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
|
@ -582,7 +681,7 @@ FilteredRoomlistModel::previousRoom()
|
||||||
if (r) {
|
if (r) {
|
||||||
int idx = roomidToIndex(r->roomId());
|
int idx = roomidToIndex(r->roomId());
|
||||||
idx--;
|
idx--;
|
||||||
if (idx > 0) {
|
if (idx >= 0) {
|
||||||
setCurrentRoom(
|
setCurrentRoom(
|
||||||
data(index(idx, 0), RoomlistModel::Roles::RoomId).toString());
|
data(index(idx, 0), RoomlistModel::Roles::RoomId).toString());
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ public:
|
||||||
IsInvite,
|
IsInvite,
|
||||||
IsSpace,
|
IsSpace,
|
||||||
Tags,
|
Tags,
|
||||||
|
ParentSpaces,
|
||||||
};
|
};
|
||||||
|
|
||||||
RoomlistModel(TimelineViewManager *parent = nullptr);
|
RoomlistModel(TimelineViewManager *parent = nullptr);
|
||||||
|
@ -134,6 +135,9 @@ public slots:
|
||||||
if (tagId.startsWith("tag:")) {
|
if (tagId.startsWith("tag:")) {
|
||||||
filterType = FilterBy::Tag;
|
filterType = FilterBy::Tag;
|
||||||
filterStr = tagId.mid(4);
|
filterStr = tagId.mid(4);
|
||||||
|
} else if (tagId.startsWith("space:")) {
|
||||||
|
filterType = FilterBy::Space;
|
||||||
|
filterStr = tagId.mid(6);
|
||||||
} else {
|
} else {
|
||||||
filterType = FilterBy::Nothing;
|
filterType = FilterBy::Nothing;
|
||||||
filterStr.clear();
|
filterStr.clear();
|
||||||
|
|
|
@ -320,6 +320,10 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
|
||||||
{
|
{
|
||||||
lastMessage_.timestamp = 0;
|
lastMessage_.timestamp = 0;
|
||||||
|
|
||||||
|
if (auto create =
|
||||||
|
cache::client()->getStateEvent<mtx::events::state::Create>(room_id.toStdString()))
|
||||||
|
this->isSpace_ = create->content.type == mtx::events::state::room_type::space;
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
this,
|
this,
|
||||||
&TimelineModel::redactionFailed,
|
&TimelineModel::redactionFailed,
|
||||||
|
@ -375,6 +379,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
|
||||||
connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) {
|
connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) {
|
||||||
this->updateFlowEventId(event_id);
|
this->updateFlowEventId(event_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
// When a message is sent, check if the current edit/reply relates to that message,
|
// When a message is sent, check if the current edit/reply relates to that message,
|
||||||
// and update the event_id so that it points to the sent message and not the pending one.
|
// and update the event_id so that it points to the sent message and not the pending one.
|
||||||
connect(&events,
|
connect(&events,
|
||||||
|
@ -391,6 +396,11 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(manager_,
|
||||||
|
&TimelineViewManager::initialSyncChanged,
|
||||||
|
&events,
|
||||||
|
&EventStore::enableKeyRequests);
|
||||||
|
|
||||||
showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent);
|
showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -770,6 +780,7 @@ TimelineModel::syncState(const mtx::responses::State &s)
|
||||||
} else if (std::holds_alternative<StateEvent<state::Member>>(e)) {
|
} else if (std::holds_alternative<StateEvent<state::Member>>(e)) {
|
||||||
emit roomAvatarUrlChanged();
|
emit roomAvatarUrlChanged();
|
||||||
emit roomNameChanged();
|
emit roomNameChanged();
|
||||||
|
emit roomMemberCountChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -826,6 +837,7 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
|
||||||
} else if (std::holds_alternative<StateEvent<state::Member>>(e)) {
|
} else if (std::holds_alternative<StateEvent<state::Member>>(e)) {
|
||||||
emit roomAvatarUrlChanged();
|
emit roomAvatarUrlChanged();
|
||||||
emit roomNameChanged();
|
emit roomNameChanged();
|
||||||
|
emit roomMemberCountChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateLastMessage();
|
updateLastMessage();
|
||||||
|
@ -1931,3 +1943,9 @@ TimelineModel::roomTopic() const
|
||||||
return utils::replaceEmoji(utils::linkifyMessage(
|
return utils::replaceEmoji(utils::linkifyMessage(
|
||||||
QString::fromStdString(info[room_id_].topic).toHtmlEscaped()));
|
QString::fromStdString(info[room_id_].topic).toHtmlEscaped()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
TimelineModel::roomMemberCount() const
|
||||||
|
{
|
||||||
|
return (int)cache::client()->memberCount(room_id_.toStdString());
|
||||||
|
}
|
||||||
|
|
|
@ -161,6 +161,8 @@ class TimelineModel : public QAbstractListModel
|
||||||
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
|
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
|
||||||
Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged)
|
Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged)
|
||||||
Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
|
Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
|
||||||
|
Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged)
|
||||||
|
Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
|
||||||
Q_PROPERTY(InputBar *input READ input CONSTANT)
|
Q_PROPERTY(InputBar *input READ input CONSTANT)
|
||||||
Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged)
|
Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged)
|
||||||
|
|
||||||
|
@ -262,6 +264,8 @@ public:
|
||||||
RelatedInfo relatedInfo(QString id);
|
RelatedInfo relatedInfo(QString id);
|
||||||
|
|
||||||
DescInfo lastMessage() const { return lastMessage_; }
|
DescInfo lastMessage() const { return lastMessage_; }
|
||||||
|
bool isSpace() const { return isSpace_; }
|
||||||
|
int roomMemberCount() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setCurrentIndex(int index);
|
void setCurrentIndex(int index);
|
||||||
|
@ -348,6 +352,7 @@ signals:
|
||||||
void roomNameChanged();
|
void roomNameChanged();
|
||||||
void roomTopicChanged();
|
void roomTopicChanged();
|
||||||
void roomAvatarUrlChanged();
|
void roomAvatarUrlChanged();
|
||||||
|
void roomMemberCountChanged();
|
||||||
void permissionsChanged();
|
void permissionsChanged();
|
||||||
void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
|
void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
|
||||||
|
|
||||||
|
@ -366,9 +371,6 @@ private:
|
||||||
|
|
||||||
QString room_id_;
|
QString room_id_;
|
||||||
|
|
||||||
bool decryptDescription = true;
|
|
||||||
bool m_paginationInProgress = false;
|
|
||||||
|
|
||||||
QString currentId, currentReadId;
|
QString currentId, currentReadId;
|
||||||
QString reply_, edit_;
|
QString reply_, edit_;
|
||||||
QString textBeforeEdit, replyBeforeEdit;
|
QString textBeforeEdit, replyBeforeEdit;
|
||||||
|
@ -388,6 +390,10 @@ private:
|
||||||
friend struct SendMessageVisitor;
|
friend struct SendMessageVisitor;
|
||||||
|
|
||||||
int notification_count = 0, highlight_count = 0;
|
int notification_count = 0, highlight_count = 0;
|
||||||
|
|
||||||
|
bool decryptDescription = true;
|
||||||
|
bool m_paginationInProgress = false;
|
||||||
|
bool isSpace_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
|
|
|
@ -47,7 +47,7 @@ Theme::paletteFromTheme(std::string_view theme)
|
||||||
darkActive.setColor(QPalette::ToolTipBase, darkActive.base().color());
|
darkActive.setColor(QPalette::ToolTipBase, darkActive.base().color());
|
||||||
darkActive.setColor(QPalette::ToolTipText, darkActive.text().color());
|
darkActive.setColor(QPalette::ToolTipText, darkActive.text().color());
|
||||||
darkActive.setColor(QPalette::Link, QColor("#38a3d8"));
|
darkActive.setColor(QPalette::Link, QColor("#38a3d8"));
|
||||||
darkActive.setColor(QPalette::ButtonText, "#727274");
|
darkActive.setColor(QPalette::ButtonText, "#828284");
|
||||||
return darkActive;
|
return darkActive;
|
||||||
} else {
|
} else {
|
||||||
return original;
|
return original;
|
||||||
|
|
1
third_party/SingleApplication-3.3.0/.github/FUNDING.yml
vendored
Normal file
1
third_party/SingleApplication-3.3.0/.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
github: itay-grudev
|
56
third_party/SingleApplication-3.3.0/.github/workflows/build-cmake.yml
vendored
Normal file
56
third_party/SingleApplication-3.3.0/.github/workflows/build-cmake.yml
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
name: "CI: Build Test"
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
qt_version: [5.12.6, 5.13.2, 5.14.0, 5.15.0, 6.0.0]
|
||||||
|
platform: [ubuntu-20.04, windows-latest, macos-latest]
|
||||||
|
include:
|
||||||
|
- qt_version: 6.0.0
|
||||||
|
additional_arguments: -D QT_DEFAULT_MAJOR_VERSION=6
|
||||||
|
- platform: ubuntu-20.04
|
||||||
|
CXXFLAGS: -Wall -Wextra -pedantic -Werror
|
||||||
|
- platform: macos-latest
|
||||||
|
CXXFLAGS: -Wall -Wextra -pedantic -Werror
|
||||||
|
- platform: windows-latest
|
||||||
|
CXXFLAGS: /W4 /WX
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
env:
|
||||||
|
CXXFLAGS: ${{ matrix.CXXFLAGS }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.3.4
|
||||||
|
|
||||||
|
- name: Install Qt
|
||||||
|
uses: jurplel/install-qt-action@v2.11.1
|
||||||
|
with:
|
||||||
|
version: ${{ matrix.qt_version }}
|
||||||
|
|
||||||
|
- name: cmake
|
||||||
|
run: cmake . ${{ matrix.additional_arguments }}
|
||||||
|
|
||||||
|
- name: cmake build
|
||||||
|
run: cmake --build .
|
||||||
|
|
||||||
|
- name: Build example - basic (cmake)
|
||||||
|
working-directory: examples/basic/
|
||||||
|
run: |
|
||||||
|
cmake . ${{ matrix.additional_arguments }}
|
||||||
|
cmake --build .
|
||||||
|
|
||||||
|
- name: Build example - calculator (cmake)
|
||||||
|
working-directory: examples/calculator/
|
||||||
|
run: |
|
||||||
|
cmake . ${{ matrix.additional_arguments }}
|
||||||
|
cmake --build .
|
||||||
|
|
||||||
|
- name: Build example - sending_arguments (cmake)
|
||||||
|
working-directory: examples/sending_arguments/
|
||||||
|
run: |
|
||||||
|
cmake . ${{ matrix.additional_arguments }}
|
||||||
|
cmake --build .
|
|
@ -3,6 +3,12 @@ Changelog
|
||||||
|
|
||||||
If by accident I have forgotten to credit someone in the CHANGELOG, email me and I will fix it.
|
If by accident I have forgotten to credit someone in the CHANGELOG, email me and I will fix it.
|
||||||
|
|
||||||
|
|
||||||
|
__3.3.0__
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Fixed message fragmentation issue causing crashes and incorrectly and inconsistently received messages. - _Nils Jeisecke_
|
||||||
|
|
||||||
__3.2.0__
|
__3.2.0__
|
||||||
---------
|
---------
|
||||||
|
|
12
third_party/SingleApplication-3.3.0/examples/basic/CMakeLists.txt
vendored
Normal file
12
third_party/SingleApplication-3.3.0/examples/basic/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
cmake_minimum_required(VERSION 3.7.0)
|
||||||
|
|
||||||
|
project(basic LANGUAGES CXX)
|
||||||
|
|
||||||
|
# SingleApplication base class
|
||||||
|
set(QAPPLICATION_CLASS QCoreApplication)
|
||||||
|
add_subdirectory(../.. SingleApplication)
|
||||||
|
|
||||||
|
add_executable(basic main.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)
|
||||||
|
|
5
third_party/SingleApplication-3.3.0/examples/basic/basic.pro
vendored
Executable file
5
third_party/SingleApplication-3.3.0/examples/basic/basic.pro
vendored
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
# Single Application implementation
|
||||||
|
include(../../singleapplication.pri)
|
||||||
|
DEFINES += QAPPLICATION_CLASS=QCoreApplication
|
||||||
|
|
||||||
|
SOURCES += main.cpp
|
10
third_party/SingleApplication-3.3.0/examples/basic/main.cpp
vendored
Executable file
10
third_party/SingleApplication-3.3.0/examples/basic/main.cpp
vendored
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
#include <singleapplication.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
SingleApplication app( argc, argv );
|
||||||
|
|
||||||
|
qWarning() << "Started a new instance";
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
21
third_party/SingleApplication-3.3.0/examples/calculator/CMakeLists.txt
vendored
Normal file
21
third_party/SingleApplication-3.3.0/examples/calculator/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
cmake_minimum_required(VERSION 3.7.0)
|
||||||
|
|
||||||
|
project(calculator LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
|
# SingleApplication base class
|
||||||
|
set(QAPPLICATION_CLASS QApplication)
|
||||||
|
add_subdirectory(../.. SingleApplication)
|
||||||
|
|
||||||
|
find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS Core REQUIRED)
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}
|
||||||
|
button.h
|
||||||
|
calculator.h
|
||||||
|
button.cpp
|
||||||
|
calculator.cpp
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)
|
73
third_party/SingleApplication-3.3.0/examples/calculator/button.cpp
vendored
Normal file
73
third_party/SingleApplication-3.3.0/examples/calculator/button.cpp
vendored
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the examples of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** BSD License Usage
|
||||||
|
** Alternatively, you may use this file under the terms of the BSD license
|
||||||
|
** as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of The Qt Company Ltd nor the names of its
|
||||||
|
** contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include <QtWidgets>
|
||||||
|
|
||||||
|
#include "button.h"
|
||||||
|
|
||||||
|
//! [0]
|
||||||
|
Button::Button(const QString &text, QWidget *parent)
|
||||||
|
: QToolButton(parent)
|
||||||
|
{
|
||||||
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||||
|
setText(text);
|
||||||
|
}
|
||||||
|
//! [0]
|
||||||
|
|
||||||
|
//! [1]
|
||||||
|
QSize Button::sizeHint() const
|
||||||
|
//! [1] //! [2]
|
||||||
|
{
|
||||||
|
QSize size = QToolButton::sizeHint();
|
||||||
|
size.rheight() += 20;
|
||||||
|
size.rwidth() = qMax(size.width(), size.height());
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
//! [2]
|
68
third_party/SingleApplication-3.3.0/examples/calculator/button.h
vendored
Normal file
68
third_party/SingleApplication-3.3.0/examples/calculator/button.h
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the examples of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** BSD License Usage
|
||||||
|
** Alternatively, you may use this file under the terms of the BSD license
|
||||||
|
** as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of The Qt Company Ltd nor the names of its
|
||||||
|
** contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef BUTTON_H
|
||||||
|
#define BUTTON_H
|
||||||
|
|
||||||
|
#include <QToolButton>
|
||||||
|
|
||||||
|
//! [0]
|
||||||
|
class Button : public QToolButton
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Button(const QString &text, QWidget *parent = 0);
|
||||||
|
|
||||||
|
QSize sizeHint() const Q_DECL_OVERRIDE;
|
||||||
|
};
|
||||||
|
//! [0]
|
||||||
|
|
||||||
|
#endif
|
406
third_party/SingleApplication-3.3.0/examples/calculator/calculator.cpp
vendored
Normal file
406
third_party/SingleApplication-3.3.0/examples/calculator/calculator.cpp
vendored
Normal file
|
@ -0,0 +1,406 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the examples of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** BSD License Usage
|
||||||
|
** Alternatively, you may use this file under the terms of the BSD license
|
||||||
|
** as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of The Qt Company Ltd nor the names of its
|
||||||
|
** contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include <QtWidgets>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "button.h"
|
||||||
|
#include "calculator.h"
|
||||||
|
|
||||||
|
//! [0]
|
||||||
|
Calculator::Calculator(QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
{
|
||||||
|
sumInMemory = 0.0;
|
||||||
|
sumSoFar = 0.0;
|
||||||
|
factorSoFar = 0.0;
|
||||||
|
waitingForOperand = true;
|
||||||
|
//! [0]
|
||||||
|
|
||||||
|
//! [1]
|
||||||
|
display = new QLineEdit("0");
|
||||||
|
//! [1] //! [2]
|
||||||
|
display->setReadOnly(true);
|
||||||
|
display->setAlignment(Qt::AlignRight);
|
||||||
|
display->setMaxLength(15);
|
||||||
|
|
||||||
|
QFont font = display->font();
|
||||||
|
font.setPointSize(font.pointSize() + 8);
|
||||||
|
display->setFont(font);
|
||||||
|
//! [2]
|
||||||
|
|
||||||
|
//! [4]
|
||||||
|
for (int i = 0; i < NumDigitButtons; ++i) {
|
||||||
|
digitButtons[i] = createButton(QString::number(i), SLOT(digitClicked()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Button *pointButton = createButton(".", SLOT(pointClicked()));
|
||||||
|
Button *changeSignButton = createButton("\302\261", SLOT(changeSignClicked()));
|
||||||
|
|
||||||
|
Button *backspaceButton = createButton("Backspace", SLOT(backspaceClicked()));
|
||||||
|
Button *clearButton = createButton("Clear", SLOT(clear()));
|
||||||
|
Button *clearAllButton = createButton("Clear All", SLOT(clearAll()));
|
||||||
|
|
||||||
|
Button *clearMemoryButton = createButton("MC", SLOT(clearMemory()));
|
||||||
|
Button *readMemoryButton = createButton("MR", SLOT(readMemory()));
|
||||||
|
Button *setMemoryButton = createButton("MS", SLOT(setMemory()));
|
||||||
|
Button *addToMemoryButton = createButton("M+", SLOT(addToMemory()));
|
||||||
|
|
||||||
|
Button *divisionButton = createButton("\303\267", SLOT(multiplicativeOperatorClicked()));
|
||||||
|
Button *timesButton = createButton("\303\227", SLOT(multiplicativeOperatorClicked()));
|
||||||
|
Button *minusButton = createButton("-", SLOT(additiveOperatorClicked()));
|
||||||
|
Button *plusButton = createButton("+", SLOT(additiveOperatorClicked()));
|
||||||
|
|
||||||
|
Button *squareRootButton = createButton("Sqrt", SLOT(unaryOperatorClicked()));
|
||||||
|
Button *powerButton = createButton("x\302\262", SLOT(unaryOperatorClicked()));
|
||||||
|
Button *reciprocalButton = createButton("1/x", SLOT(unaryOperatorClicked()));
|
||||||
|
Button *equalButton = createButton("=", SLOT(equalClicked()));
|
||||||
|
//! [4]
|
||||||
|
|
||||||
|
//! [5]
|
||||||
|
QGridLayout *mainLayout = new QGridLayout;
|
||||||
|
//! [5] //! [6]
|
||||||
|
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||||
|
mainLayout->addWidget(display, 0, 0, 1, 6);
|
||||||
|
mainLayout->addWidget(backspaceButton, 1, 0, 1, 2);
|
||||||
|
mainLayout->addWidget(clearButton, 1, 2, 1, 2);
|
||||||
|
mainLayout->addWidget(clearAllButton, 1, 4, 1, 2);
|
||||||
|
|
||||||
|
mainLayout->addWidget(clearMemoryButton, 2, 0);
|
||||||
|
mainLayout->addWidget(readMemoryButton, 3, 0);
|
||||||
|
mainLayout->addWidget(setMemoryButton, 4, 0);
|
||||||
|
mainLayout->addWidget(addToMemoryButton, 5, 0);
|
||||||
|
|
||||||
|
for (int i = 1; i < NumDigitButtons; ++i) {
|
||||||
|
int row = ((9 - i) / 3) + 2;
|
||||||
|
int column = ((i - 1) % 3) + 1;
|
||||||
|
mainLayout->addWidget(digitButtons[i], row, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
mainLayout->addWidget(digitButtons[0], 5, 1);
|
||||||
|
mainLayout->addWidget(pointButton, 5, 2);
|
||||||
|
mainLayout->addWidget(changeSignButton, 5, 3);
|
||||||
|
|
||||||
|
mainLayout->addWidget(divisionButton, 2, 4);
|
||||||
|
mainLayout->addWidget(timesButton, 3, 4);
|
||||||
|
mainLayout->addWidget(minusButton, 4, 4);
|
||||||
|
mainLayout->addWidget(plusButton, 5, 4);
|
||||||
|
|
||||||
|
mainLayout->addWidget(squareRootButton, 2, 5);
|
||||||
|
mainLayout->addWidget(powerButton, 3, 5);
|
||||||
|
mainLayout->addWidget(reciprocalButton, 4, 5);
|
||||||
|
mainLayout->addWidget(equalButton, 5, 5);
|
||||||
|
setLayout(mainLayout);
|
||||||
|
|
||||||
|
setWindowTitle("Calculator");
|
||||||
|
}
|
||||||
|
//! [6]
|
||||||
|
|
||||||
|
//! [7]
|
||||||
|
void Calculator::digitClicked()
|
||||||
|
{
|
||||||
|
Button *clickedButton = qobject_cast<Button *>(sender());
|
||||||
|
int digitValue = clickedButton->text().toInt();
|
||||||
|
if (display->text() == "0" && digitValue == 0.0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (waitingForOperand) {
|
||||||
|
display->clear();
|
||||||
|
waitingForOperand = false;
|
||||||
|
}
|
||||||
|
display->setText(display->text() + QString::number(digitValue));
|
||||||
|
}
|
||||||
|
//! [7]
|
||||||
|
|
||||||
|
//! [8]
|
||||||
|
void Calculator::unaryOperatorClicked()
|
||||||
|
//! [8] //! [9]
|
||||||
|
{
|
||||||
|
Button *clickedButton = qobject_cast<Button *>(sender());
|
||||||
|
QString clickedOperator = clickedButton->text();
|
||||||
|
double operand = display->text().toDouble();
|
||||||
|
double result = 0.0;
|
||||||
|
|
||||||
|
if (clickedOperator == "Sqrt") {
|
||||||
|
if (operand < 0.0) {
|
||||||
|
abortOperation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result = std::sqrt(operand);
|
||||||
|
} else if (clickedOperator == "x\302\262") {
|
||||||
|
result = std::pow(operand, 2.0);
|
||||||
|
} else if (clickedOperator == "1/x") {
|
||||||
|
if (operand == 0.0) {
|
||||||
|
abortOperation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result = 1.0 / operand;
|
||||||
|
}
|
||||||
|
display->setText(QString::number(result));
|
||||||
|
waitingForOperand = true;
|
||||||
|
}
|
||||||
|
//! [9]
|
||||||
|
|
||||||
|
//! [10]
|
||||||
|
void Calculator::additiveOperatorClicked()
|
||||||
|
//! [10] //! [11]
|
||||||
|
{
|
||||||
|
Button *clickedButton = qobject_cast<Button *>(sender());
|
||||||
|
QString clickedOperator = clickedButton->text();
|
||||||
|
double operand = display->text().toDouble();
|
||||||
|
|
||||||
|
//! [11] //! [12]
|
||||||
|
if (!pendingMultiplicativeOperator.isEmpty()) {
|
||||||
|
//! [12] //! [13]
|
||||||
|
if (!calculate(operand, pendingMultiplicativeOperator)) {
|
||||||
|
abortOperation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
display->setText(QString::number(factorSoFar));
|
||||||
|
operand = factorSoFar;
|
||||||
|
factorSoFar = 0.0;
|
||||||
|
pendingMultiplicativeOperator.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
//! [13] //! [14]
|
||||||
|
if (!pendingAdditiveOperator.isEmpty()) {
|
||||||
|
//! [14] //! [15]
|
||||||
|
if (!calculate(operand, pendingAdditiveOperator)) {
|
||||||
|
abortOperation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
display->setText(QString::number(sumSoFar));
|
||||||
|
} else {
|
||||||
|
sumSoFar = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! [15] //! [16]
|
||||||
|
pendingAdditiveOperator = clickedOperator;
|
||||||
|
//! [16] //! [17]
|
||||||
|
waitingForOperand = true;
|
||||||
|
}
|
||||||
|
//! [17]
|
||||||
|
|
||||||
|
//! [18]
|
||||||
|
void Calculator::multiplicativeOperatorClicked()
|
||||||
|
{
|
||||||
|
Button *clickedButton = qobject_cast<Button *>(sender());
|
||||||
|
QString clickedOperator = clickedButton->text();
|
||||||
|
double operand = display->text().toDouble();
|
||||||
|
|
||||||
|
if (!pendingMultiplicativeOperator.isEmpty()) {
|
||||||
|
if (!calculate(operand, pendingMultiplicativeOperator)) {
|
||||||
|
abortOperation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
display->setText(QString::number(factorSoFar));
|
||||||
|
} else {
|
||||||
|
factorSoFar = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingMultiplicativeOperator = clickedOperator;
|
||||||
|
waitingForOperand = true;
|
||||||
|
}
|
||||||
|
//! [18]
|
||||||
|
|
||||||
|
//! [20]
|
||||||
|
void Calculator::equalClicked()
|
||||||
|
{
|
||||||
|
double operand = display->text().toDouble();
|
||||||
|
|
||||||
|
if (!pendingMultiplicativeOperator.isEmpty()) {
|
||||||
|
if (!calculate(operand, pendingMultiplicativeOperator)) {
|
||||||
|
abortOperation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
operand = factorSoFar;
|
||||||
|
factorSoFar = 0.0;
|
||||||
|
pendingMultiplicativeOperator.clear();
|
||||||
|
}
|
||||||
|
if (!pendingAdditiveOperator.isEmpty()) {
|
||||||
|
if (!calculate(operand, pendingAdditiveOperator)) {
|
||||||
|
abortOperation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pendingAdditiveOperator.clear();
|
||||||
|
} else {
|
||||||
|
sumSoFar = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
display->setText(QString::number(sumSoFar));
|
||||||
|
sumSoFar = 0.0;
|
||||||
|
waitingForOperand = true;
|
||||||
|
}
|
||||||
|
//! [20]
|
||||||
|
|
||||||
|
//! [22]
|
||||||
|
void Calculator::pointClicked()
|
||||||
|
{
|
||||||
|
if (waitingForOperand)
|
||||||
|
display->setText("0");
|
||||||
|
if (!display->text().contains('.'))
|
||||||
|
display->setText(display->text() + ".");
|
||||||
|
waitingForOperand = false;
|
||||||
|
}
|
||||||
|
//! [22]
|
||||||
|
|
||||||
|
//! [24]
|
||||||
|
void Calculator::changeSignClicked()
|
||||||
|
{
|
||||||
|
QString text = display->text();
|
||||||
|
double value = text.toDouble();
|
||||||
|
|
||||||
|
if (value > 0.0) {
|
||||||
|
text.prepend("-");
|
||||||
|
} else if (value < 0.0) {
|
||||||
|
text.remove(0, 1);
|
||||||
|
}
|
||||||
|
display->setText(text);
|
||||||
|
}
|
||||||
|
//! [24]
|
||||||
|
|
||||||
|
//! [26]
|
||||||
|
void Calculator::backspaceClicked()
|
||||||
|
{
|
||||||
|
if (waitingForOperand)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString text = display->text();
|
||||||
|
text.chop(1);
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
text = "0";
|
||||||
|
waitingForOperand = true;
|
||||||
|
}
|
||||||
|
display->setText(text);
|
||||||
|
}
|
||||||
|
//! [26]
|
||||||
|
|
||||||
|
//! [28]
|
||||||
|
void Calculator::clear()
|
||||||
|
{
|
||||||
|
if (waitingForOperand)
|
||||||
|
return;
|
||||||
|
|
||||||
|
display->setText("0");
|
||||||
|
waitingForOperand = true;
|
||||||
|
}
|
||||||
|
//! [28]
|
||||||
|
|
||||||
|
//! [30]
|
||||||
|
void Calculator::clearAll()
|
||||||
|
{
|
||||||
|
sumSoFar = 0.0;
|
||||||
|
factorSoFar = 0.0;
|
||||||
|
pendingAdditiveOperator.clear();
|
||||||
|
pendingMultiplicativeOperator.clear();
|
||||||
|
display->setText("0");
|
||||||
|
waitingForOperand = true;
|
||||||
|
}
|
||||||
|
//! [30]
|
||||||
|
|
||||||
|
//! [32]
|
||||||
|
void Calculator::clearMemory()
|
||||||
|
{
|
||||||
|
sumInMemory = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Calculator::readMemory()
|
||||||
|
{
|
||||||
|
display->setText(QString::number(sumInMemory));
|
||||||
|
waitingForOperand = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Calculator::setMemory()
|
||||||
|
{
|
||||||
|
equalClicked();
|
||||||
|
sumInMemory = display->text().toDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Calculator::addToMemory()
|
||||||
|
{
|
||||||
|
equalClicked();
|
||||||
|
sumInMemory += display->text().toDouble();
|
||||||
|
}
|
||||||
|
//! [32]
|
||||||
|
//! [34]
|
||||||
|
Button *Calculator::createButton(const QString &text, const char *member)
|
||||||
|
{
|
||||||
|
Button *button = new Button(text);
|
||||||
|
connect(button, SIGNAL(clicked()), this, member);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
//! [34]
|
||||||
|
|
||||||
|
//! [36]
|
||||||
|
void Calculator::abortOperation()
|
||||||
|
{
|
||||||
|
clearAll();
|
||||||
|
display->setText("####");
|
||||||
|
}
|
||||||
|
//! [36]
|
||||||
|
|
||||||
|
//! [38]
|
||||||
|
bool Calculator::calculate(double rightOperand, const QString &pendingOperator)
|
||||||
|
{
|
||||||
|
if (pendingOperator == "+") {
|
||||||
|
sumSoFar += rightOperand;
|
||||||
|
} else if (pendingOperator == "-") {
|
||||||
|
sumSoFar -= rightOperand;
|
||||||
|
} else if (pendingOperator == "\303\227") {
|
||||||
|
factorSoFar *= rightOperand;
|
||||||
|
} else if (pendingOperator == "\303\267") {
|
||||||
|
if (rightOperand == 0.0)
|
||||||
|
return false;
|
||||||
|
factorSoFar /= rightOperand;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//! [38]
|
117
third_party/SingleApplication-3.3.0/examples/calculator/calculator.h
vendored
Normal file
117
third_party/SingleApplication-3.3.0/examples/calculator/calculator.h
vendored
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the examples of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** BSD License Usage
|
||||||
|
** Alternatively, you may use this file under the terms of the BSD license
|
||||||
|
** as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of The Qt Company Ltd nor the names of its
|
||||||
|
** contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef CALCULATOR_H
|
||||||
|
#define CALCULATOR_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QLineEdit;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
class Button;
|
||||||
|
|
||||||
|
//! [0]
|
||||||
|
class Calculator : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Calculator(QWidget *parent = 0);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void digitClicked();
|
||||||
|
void unaryOperatorClicked();
|
||||||
|
void additiveOperatorClicked();
|
||||||
|
void multiplicativeOperatorClicked();
|
||||||
|
void equalClicked();
|
||||||
|
void pointClicked();
|
||||||
|
void changeSignClicked();
|
||||||
|
void backspaceClicked();
|
||||||
|
void clear();
|
||||||
|
void clearAll();
|
||||||
|
void clearMemory();
|
||||||
|
void readMemory();
|
||||||
|
void setMemory();
|
||||||
|
void addToMemory();
|
||||||
|
//! [0]
|
||||||
|
|
||||||
|
//! [1]
|
||||||
|
private:
|
||||||
|
//! [1] //! [2]
|
||||||
|
Button *createButton(const QString &text, const char *member);
|
||||||
|
void abortOperation();
|
||||||
|
bool calculate(double rightOperand, const QString &pendingOperator);
|
||||||
|
//! [2]
|
||||||
|
|
||||||
|
//! [3]
|
||||||
|
double sumInMemory;
|
||||||
|
//! [3] //! [4]
|
||||||
|
double sumSoFar;
|
||||||
|
//! [4] //! [5]
|
||||||
|
double factorSoFar;
|
||||||
|
//! [5] //! [6]
|
||||||
|
QString pendingAdditiveOperator;
|
||||||
|
//! [6] //! [7]
|
||||||
|
QString pendingMultiplicativeOperator;
|
||||||
|
//! [7] //! [8]
|
||||||
|
bool waitingForOperand;
|
||||||
|
//! [8]
|
||||||
|
|
||||||
|
//! [9]
|
||||||
|
QLineEdit *display;
|
||||||
|
//! [9] //! [10]
|
||||||
|
|
||||||
|
enum { NumDigitButtons = 10 };
|
||||||
|
Button *digitButtons[NumDigitButtons];
|
||||||
|
};
|
||||||
|
//! [10]
|
||||||
|
|
||||||
|
#endif
|
11
third_party/SingleApplication-3.3.0/examples/calculator/calculator.pro
vendored
Normal file
11
third_party/SingleApplication-3.3.0/examples/calculator/calculator.pro
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
QT += widgets
|
||||||
|
|
||||||
|
HEADERS = button.h \
|
||||||
|
calculator.h
|
||||||
|
SOURCES = button.cpp \
|
||||||
|
calculator.cpp \
|
||||||
|
main.cpp
|
||||||
|
|
||||||
|
# Single Application implementation
|
||||||
|
include(../../singleapplication.pri)
|
||||||
|
DEFINES += QAPPLICATION_CLASS=QApplication
|
71
third_party/SingleApplication-3.3.0/examples/calculator/main.cpp
vendored
Normal file
71
third_party/SingleApplication-3.3.0/examples/calculator/main.cpp
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the examples of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:BSD$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** BSD License Usage
|
||||||
|
** Alternatively, you may use this file under the terms of the BSD license
|
||||||
|
** as follows:
|
||||||
|
**
|
||||||
|
** "Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions are
|
||||||
|
** met:
|
||||||
|
** * Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** * Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in
|
||||||
|
** the documentation and/or other materials provided with the
|
||||||
|
** distribution.
|
||||||
|
** * Neither the name of The Qt Company Ltd nor the names of its
|
||||||
|
** contributors may be used to endorse or promote products derived
|
||||||
|
** from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
#include <singleapplication.h>
|
||||||
|
|
||||||
|
#include "calculator.h"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
SingleApplication app(argc, argv);
|
||||||
|
|
||||||
|
Calculator calc;
|
||||||
|
|
||||||
|
QObject::connect( &app, &SingleApplication::instanceStarted, [ &calc ]() {
|
||||||
|
calc.raise();
|
||||||
|
calc.activateWindow();
|
||||||
|
});
|
||||||
|
|
||||||
|
calc.show();
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
20
third_party/SingleApplication-3.3.0/examples/sending_arguments/CMakeLists.txt
vendored
Normal file
20
third_party/SingleApplication-3.3.0/examples/sending_arguments/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
cmake_minimum_required(VERSION 3.7.0)
|
||||||
|
|
||||||
|
project(sending_arguments LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
|
# SingleApplication base class
|
||||||
|
set(QAPPLICATION_CLASS QCoreApplication)
|
||||||
|
add_subdirectory(../.. SingleApplication)
|
||||||
|
|
||||||
|
find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS Core REQUIRED)
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}
|
||||||
|
main.cpp
|
||||||
|
messagereceiver.cpp
|
||||||
|
messagereceiver.h
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)
|
28
third_party/SingleApplication-3.3.0/examples/sending_arguments/main.cpp
vendored
Executable file
28
third_party/SingleApplication-3.3.0/examples/sending_arguments/main.cpp
vendored
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
#include <singleapplication.h>
|
||||||
|
#include "messagereceiver.h"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
// Allow secondary instances
|
||||||
|
SingleApplication app( argc, argv, true );
|
||||||
|
|
||||||
|
MessageReceiver msgReceiver;
|
||||||
|
|
||||||
|
// If this is a secondary instance
|
||||||
|
if( app.isSecondary() ) {
|
||||||
|
app.sendMessage( app.arguments().join(' ').toUtf8() );
|
||||||
|
qDebug() << "App already running.";
|
||||||
|
qDebug() << "Primary instance PID: " << app.primaryPid();
|
||||||
|
qDebug() << "Primary instance user: " << app.primaryUser();
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
QObject::connect(
|
||||||
|
&app,
|
||||||
|
&SingleApplication::receivedMessage,
|
||||||
|
&msgReceiver,
|
||||||
|
&MessageReceiver::receivedMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
12
third_party/SingleApplication-3.3.0/examples/sending_arguments/messagereceiver.cpp
vendored
Normal file
12
third_party/SingleApplication-3.3.0/examples/sending_arguments/messagereceiver.cpp
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#include <QDebug>
|
||||||
|
#include "messagereceiver.h"
|
||||||
|
|
||||||
|
MessageReceiver::MessageReceiver(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageReceiver::receivedMessage(int instanceId, QByteArray message)
|
||||||
|
{
|
||||||
|
qDebug() << "Received message from instance: " << instanceId;
|
||||||
|
qDebug() << "Message Text: " << message;
|
||||||
|
}
|
15
third_party/SingleApplication-3.3.0/examples/sending_arguments/messagereceiver.h
vendored
Normal file
15
third_party/SingleApplication-3.3.0/examples/sending_arguments/messagereceiver.h
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef MESSAGERECEIVER_H
|
||||||
|
#define MESSAGERECEIVER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class MessageReceiver : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit MessageReceiver(QObject *parent = 0);
|
||||||
|
public slots:
|
||||||
|
void receivedMessage( int instanceId, QByteArray message );
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MESSAGERECEIVER_H
|
9
third_party/SingleApplication-3.3.0/examples/sending_arguments/sending_arguments.pro
vendored
Executable file
9
third_party/SingleApplication-3.3.0/examples/sending_arguments/sending_arguments.pro
vendored
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
# Single Application implementation
|
||||||
|
include(../../singleapplication.pri)
|
||||||
|
DEFINES += QAPPLICATION_CLASS=QCoreApplication
|
||||||
|
|
||||||
|
SOURCES += main.cpp \
|
||||||
|
messagereceiver.cpp
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
messagereceiver.h
|
|
@ -36,7 +36,7 @@
|
||||||
* @param options Optional flags to toggle specific behaviour
|
* @param options Optional flags to toggle specific behaviour
|
||||||
* @param timeout Maximum time blocking functions are allowed during app load
|
* @param timeout Maximum time blocking functions are allowed during app load
|
||||||
*/
|
*/
|
||||||
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, QString userData )
|
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData )
|
||||||
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
|
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
|
||||||
{
|
{
|
||||||
Q_D( SingleApplication );
|
Q_D( SingleApplication );
|
||||||
|
@ -172,9 +172,9 @@ SingleApplication::~SingleApplication()
|
||||||
* Checks if the current application instance is primary.
|
* Checks if the current application instance is primary.
|
||||||
* @return Returns true if the instance is primary, false otherwise.
|
* @return Returns true if the instance is primary, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool SingleApplication::isPrimary()
|
bool SingleApplication::isPrimary() const
|
||||||
{
|
{
|
||||||
Q_D( SingleApplication );
|
Q_D( const SingleApplication );
|
||||||
return d->server != nullptr;
|
return d->server != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,9 +182,9 @@ bool SingleApplication::isPrimary()
|
||||||
* Checks if the current application instance is secondary.
|
* Checks if the current application instance is secondary.
|
||||||
* @return Returns true if the instance is secondary, false otherwise.
|
* @return Returns true if the instance is secondary, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool SingleApplication::isSecondary()
|
bool SingleApplication::isSecondary() const
|
||||||
{
|
{
|
||||||
Q_D( SingleApplication );
|
Q_D( const SingleApplication );
|
||||||
return d->server == nullptr;
|
return d->server == nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,9 +194,9 @@ bool SingleApplication::isSecondary()
|
||||||
* only incremented afterwards.
|
* only incremented afterwards.
|
||||||
* @return Returns a unique instance id.
|
* @return Returns a unique instance id.
|
||||||
*/
|
*/
|
||||||
quint32 SingleApplication::instanceId()
|
quint32 SingleApplication::instanceId() const
|
||||||
{
|
{
|
||||||
Q_D( SingleApplication );
|
Q_D( const SingleApplication );
|
||||||
return d->instanceNumber;
|
return d->instanceNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,9 +206,9 @@ quint32 SingleApplication::instanceId()
|
||||||
* specific APIs.
|
* specific APIs.
|
||||||
* @return Returns the primary instance PID.
|
* @return Returns the primary instance PID.
|
||||||
*/
|
*/
|
||||||
qint64 SingleApplication::primaryPid()
|
qint64 SingleApplication::primaryPid() const
|
||||||
{
|
{
|
||||||
Q_D( SingleApplication );
|
Q_D( const SingleApplication );
|
||||||
return d->primaryPid();
|
return d->primaryPid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,9 +216,9 @@ qint64 SingleApplication::primaryPid()
|
||||||
* Returns the username the primary instance is running as.
|
* Returns the username the primary instance is running as.
|
||||||
* @return Returns the username the primary instance is running as.
|
* @return Returns the username the primary instance is running as.
|
||||||
*/
|
*/
|
||||||
QString SingleApplication::primaryUser()
|
QString SingleApplication::primaryUser() const
|
||||||
{
|
{
|
||||||
Q_D( SingleApplication );
|
Q_D( const SingleApplication );
|
||||||
return d->primaryUser();
|
return d->primaryUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ QString SingleApplication::primaryUser()
|
||||||
* Returns the username the current instance is running as.
|
* Returns the username the current instance is running as.
|
||||||
* @return Returns the username the current instance is running as.
|
* @return Returns the username the current instance is running as.
|
||||||
*/
|
*/
|
||||||
QString SingleApplication::currentUser()
|
QString SingleApplication::currentUser() const
|
||||||
{
|
{
|
||||||
return SingleApplicationPrivate::getUsername();
|
return SingleApplicationPrivate::getUsername();
|
||||||
}
|
}
|
||||||
|
@ -248,10 +248,7 @@ bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
|
||||||
if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) )
|
if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
d->socket->write( message );
|
return d->writeConfirmedMessage( timeout, message );
|
||||||
bool dataWritten = d->socket->waitForBytesWritten( timeout );
|
|
||||||
d->socket->flush();
|
|
||||||
return dataWritten;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -267,8 +264,8 @@ void SingleApplication::abortSafely()
|
||||||
::exit( EXIT_FAILURE );
|
::exit( EXIT_FAILURE );
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList SingleApplication::userData()
|
QStringList SingleApplication::userData() const
|
||||||
{
|
{
|
||||||
Q_D( SingleApplication );
|
Q_D( const SingleApplication );
|
||||||
return d->appData();
|
return d->appData();
|
||||||
}
|
}
|
|
@ -85,44 +85,44 @@ public:
|
||||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||||
* @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, QString userData = QString() );
|
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, const QString &userData = {} );
|
||||||
~SingleApplication() override;
|
~SingleApplication() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns if the instance is the primary instance
|
* @brief Returns if the instance is the primary instance
|
||||||
* @returns {bool}
|
* @returns {bool}
|
||||||
*/
|
*/
|
||||||
bool isPrimary();
|
bool isPrimary() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns if the instance is a secondary instance
|
* @brief Returns if the instance is a secondary instance
|
||||||
* @returns {bool}
|
* @returns {bool}
|
||||||
*/
|
*/
|
||||||
bool isSecondary();
|
bool isSecondary() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a unique identifier for the current instance
|
* @brief Returns a unique identifier for the current instance
|
||||||
* @returns {qint32}
|
* @returns {qint32}
|
||||||
*/
|
*/
|
||||||
quint32 instanceId();
|
quint32 instanceId() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the process ID (PID) of the primary instance
|
* @brief Returns the process ID (PID) of the primary instance
|
||||||
* @returns {qint64}
|
* @returns {qint64}
|
||||||
*/
|
*/
|
||||||
qint64 primaryPid();
|
qint64 primaryPid() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the username of the user running the primary instance
|
* @brief Returns the username of the user running the primary instance
|
||||||
* @returns {QString}
|
* @returns {QString}
|
||||||
*/
|
*/
|
||||||
QString primaryUser();
|
QString primaryUser() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the username of the current user
|
* @brief Returns the username of the current user
|
||||||
* @returns {QString}
|
* @returns {QString}
|
||||||
*/
|
*/
|
||||||
QString currentUser();
|
QString currentUser() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sends a message to the primary instance. Returns true on success.
|
* @brief Sends a message to the primary instance. Returns true on success.
|
||||||
|
@ -137,7 +137,7 @@ public:
|
||||||
* @brief Get the set user data.
|
* @brief Get the set user data.
|
||||||
* @returns {QStringList}
|
* @returns {QStringList}
|
||||||
*/
|
*/
|
||||||
QStringList userData();
|
QStringList userData() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void instanceStarted();
|
void instanceStarted();
|
|
@ -263,20 +263,46 @@ bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType conne
|
||||||
#endif
|
#endif
|
||||||
writeStream << checksum;
|
writeStream << checksum;
|
||||||
|
|
||||||
// The header indicates the message length that follows
|
return writeConfirmedMessage( static_cast<int>(msecs - time.elapsed()), initMsg );
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleApplicationPrivate::writeAck( QLocalSocket *sock ) {
|
||||||
|
sock->putChar('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleApplicationPrivate::writeConfirmedMessage (int msecs, const QByteArray &msg)
|
||||||
|
{
|
||||||
|
QElapsedTimer time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
|
// Frame 1: The header indicates the message length that follows
|
||||||
QByteArray header;
|
QByteArray header;
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
headerStream.setVersion(QDataStream::Qt_5_6);
|
||||||
#endif
|
#endif
|
||||||
headerStream << static_cast <quint64>( initMsg.length() );
|
headerStream << static_cast <quint64>( msg.length() );
|
||||||
|
|
||||||
socket->write( header );
|
if( ! writeConfirmedFrame( static_cast<int>(msecs - time.elapsed()), header ))
|
||||||
socket->write( initMsg );
|
return false;
|
||||||
bool result = socket->waitForBytesWritten( static_cast<int>(msecs - time.elapsed()) );
|
|
||||||
|
// Frame 2: The message
|
||||||
|
return writeConfirmedFrame( static_cast<int>(msecs - time.elapsed()), msg );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleApplicationPrivate::writeConfirmedFrame( int msecs, const QByteArray &msg )
|
||||||
|
{
|
||||||
|
socket->write( msg );
|
||||||
socket->flush();
|
socket->flush();
|
||||||
return result;
|
|
||||||
|
bool result = socket->waitForReadyRead( msecs ); // await ack byte
|
||||||
|
if (result) {
|
||||||
|
socket->read( 1 );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
quint16 SingleApplicationPrivate::blockChecksum() const
|
quint16 SingleApplicationPrivate::blockChecksum() const
|
||||||
|
@ -321,32 +347,36 @@ void SingleApplicationPrivate::slotConnectionEstablished()
|
||||||
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
||||||
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this,
|
||||||
[nextConnSocket, this](){
|
[nextConnSocket, this](){
|
||||||
auto &info = connectionMap[nextConnSocket];
|
auto &info = connectionMap[nextConnSocket];
|
||||||
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
|
this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected,
|
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||||
|
|
||||||
|
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this,
|
||||||
[nextConnSocket, this](){
|
[nextConnSocket, this](){
|
||||||
connectionMap.remove(nextConnSocket);
|
connectionMap.remove(nextConnSocket);
|
||||||
nextConnSocket->deleteLater();
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this,
|
||||||
[nextConnSocket, this](){
|
[nextConnSocket, this](){
|
||||||
auto &info = connectionMap[nextConnSocket];
|
auto &info = connectionMap[nextConnSocket];
|
||||||
switch(info.stage){
|
switch(info.stage){
|
||||||
case StageHeader:
|
case StageInitHeader:
|
||||||
readInitMessageHeader(nextConnSocket);
|
readMessageHeader( nextConnSocket, StageInitBody );
|
||||||
break;
|
break;
|
||||||
case StageBody:
|
case StageInitBody:
|
||||||
readInitMessageBody(nextConnSocket);
|
readInitMessageBody(nextConnSocket);
|
||||||
break;
|
break;
|
||||||
case StageConnected:
|
case StageConnectedHeader:
|
||||||
Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
|
readMessageHeader( nextConnSocket, StageConnectedBody );
|
||||||
|
break;
|
||||||
|
case StageConnectedBody:
|
||||||
|
this->slotDataAvailable( nextConnSocket, info.instanceId );
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -355,7 +385,7 @@ void SingleApplicationPrivate::slotConnectionEstablished()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
|
void SingleApplicationPrivate::readMessageHeader( QLocalSocket *sock, SingleApplicationPrivate::ConnectionStage nextStage )
|
||||||
{
|
{
|
||||||
if (!connectionMap.contains( sock )){
|
if (!connectionMap.contains( sock )){
|
||||||
return;
|
return;
|
||||||
|
@ -375,29 +405,35 @@ void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
|
||||||
quint64 msgLen = 0;
|
quint64 msgLen = 0;
|
||||||
headerStream >> msgLen;
|
headerStream >> msgLen;
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
ConnectionInfo &info = connectionMap[sock];
|
||||||
info.stage = StageBody;
|
info.stage = nextStage;
|
||||||
info.msgLen = msgLen;
|
info.msgLen = msgLen;
|
||||||
|
|
||||||
if ( sock->bytesAvailable() >= (qint64) msgLen ){
|
writeAck( sock );
|
||||||
readInitMessageBody( sock );
|
}
|
||||||
|
|
||||||
|
bool SingleApplicationPrivate::isFrameComplete( QLocalSocket *sock )
|
||||||
|
{
|
||||||
|
if (!connectionMap.contains( sock )){
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap[sock];
|
||||||
|
if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
|
void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
|
||||||
{
|
{
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplication);
|
||||||
|
|
||||||
if (!connectionMap.contains( sock )){
|
if( !isFrameComplete( sock ) )
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
|
||||||
if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the message body
|
// Read the message body
|
||||||
QByteArray msgBytes = sock->read(info.msgLen);
|
QByteArray msgBytes = sock->readAll();
|
||||||
QDataStream readStream(msgBytes);
|
QDataStream readStream(msgBytes);
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||||
|
@ -437,8 +473,9 @@ void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap[sock];
|
||||||
info.instanceId = instanceId;
|
info.instanceId = instanceId;
|
||||||
info.stage = StageConnected;
|
info.stage = StageConnectedHeader;
|
||||||
|
|
||||||
if( connectionType == NewInstance ||
|
if( connectionType == NewInstance ||
|
||||||
( connectionType == SecondaryInstance &&
|
( connectionType == SecondaryInstance &&
|
||||||
|
@ -447,21 +484,28 @@ void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
|
||||||
Q_EMIT q->instanceStarted();
|
Q_EMIT q->instanceStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sock->bytesAvailable() > 0){
|
writeAck( sock );
|
||||||
Q_EMIT this->slotDataAvailable( sock, instanceId );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
|
void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
|
||||||
{
|
{
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplication);
|
||||||
|
|
||||||
|
if ( !isFrameComplete( dataSocket ) )
|
||||||
|
return;
|
||||||
|
|
||||||
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
|
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
|
||||||
|
|
||||||
|
writeAck( dataSocket );
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap[dataSocket];
|
||||||
|
info.stage = StageConnectedHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
|
void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
|
||||||
{
|
{
|
||||||
if( closedSocket->bytesAvailable() > 0 )
|
if( closedSocket->bytesAvailable() > 0 )
|
||||||
Q_EMIT slotDataAvailable( closedSocket, instanceId );
|
slotDataAvailable( closedSocket, instanceId );
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::randomSleep()
|
void SingleApplicationPrivate::randomSleep()
|
||||||
|
@ -470,7 +514,7 @@ void SingleApplicationPrivate::randomSleep()
|
||||||
QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
|
QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
|
||||||
#else
|
#else
|
||||||
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
|
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
|
||||||
QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
|
QThread::msleep( qrand() % 11 + 8);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,9 +61,10 @@ public:
|
||||||
Reconnect = 3
|
Reconnect = 3
|
||||||
};
|
};
|
||||||
enum ConnectionStage : quint8 {
|
enum ConnectionStage : quint8 {
|
||||||
StageHeader = 0,
|
StageInitHeader = 0,
|
||||||
StageBody = 1,
|
StageInitBody = 1,
|
||||||
StageConnected = 2,
|
StageConnectedHeader = 2,
|
||||||
|
StageConnectedBody = 3,
|
||||||
};
|
};
|
||||||
Q_DECLARE_PUBLIC(SingleApplication)
|
Q_DECLARE_PUBLIC(SingleApplication)
|
||||||
|
|
||||||
|
@ -79,8 +80,12 @@ public:
|
||||||
quint16 blockChecksum() const;
|
quint16 blockChecksum() const;
|
||||||
qint64 primaryPid() const;
|
qint64 primaryPid() const;
|
||||||
QString primaryUser() const;
|
QString primaryUser() const;
|
||||||
void readInitMessageHeader(QLocalSocket *socket);
|
bool isFrameComplete(QLocalSocket *sock);
|
||||||
|
void readMessageHeader(QLocalSocket *socket, ConnectionStage nextStage);
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
void readInitMessageBody(QLocalSocket *socket);
|
||||||
|
void writeAck(QLocalSocket *sock);
|
||||||
|
bool writeConfirmedFrame(int msecs, const QByteArray &msg);
|
||||||
|
bool writeConfirmedMessage(int msecs, const QByteArray &msg);
|
||||||
static void randomSleep();
|
static void randomSleep();
|
||||||
void addAppData(const QString &data);
|
void addAppData(const QString &data);
|
||||||
QStringList appData() const;
|
QStringList appData() const;
|
Loading…
Reference in a new issue