diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index fd47cfb6ba..4ef120b1af 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1339,6 +1339,8 @@ PRIVATE media/view/media_view_playback_controls.h media/view/media_view_playback_progress.cpp media/view/media_view_playback_progress.h + media/view/media_view_playback_sponsored.cpp + media/view/media_view_playback_sponsored.h media/system_media_controls_manager.h media/system_media_controls_manager.cpp menu/menu_antispam_validator.cpp @@ -2071,7 +2073,7 @@ if (MSVC) /DELAYLOAD:API-MS-Win-Core-ProcessThreads-l1-1-0.dll /DELAYLOAD:API-MS-Win-Core-Synch-l1-2-0.dll # Synchronization.lib /DELAYLOAD:API-MS-Win-Core-SysInfo-l1-1-0.dll - /DELAYLOAD:API-MS-Win-Core-Timezone-l1-1-0.dll + # /DELAYLOAD:API-MS-Win-Core-Timezone-l1-1-0.dll /DELAYLOAD:API-MS-Win-Core-WinRT-l1-1-0.dll /DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll /DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll diff --git a/Telegram/Resources/animations/no_chats.tgs b/Telegram/Resources/animations/no_chats.tgs new file mode 100644 index 0000000000..a30673b48a Binary files /dev/null and b/Telegram/Resources/animations/no_chats.tgs differ diff --git a/Telegram/Resources/icons/info/edit/stickers_add.png b/Telegram/Resources/icons/menu/add.png similarity index 100% rename from Telegram/Resources/icons/info/edit/stickers_add.png rename to Telegram/Resources/icons/menu/add.png diff --git a/Telegram/Resources/icons/info/edit/stickers_add@2x.png b/Telegram/Resources/icons/menu/add@2x.png similarity index 100% rename from Telegram/Resources/icons/info/edit/stickers_add@2x.png rename to Telegram/Resources/icons/menu/add@2x.png diff --git a/Telegram/Resources/icons/info/edit/stickers_add@3x.png b/Telegram/Resources/icons/menu/add@3x.png similarity index 100% rename from Telegram/Resources/icons/info/edit/stickers_add@3x.png rename to Telegram/Resources/icons/menu/add@3x.png diff --git a/Telegram/Resources/icons/tray_monochrome.svg b/Telegram/Resources/icons/tray_monochrome.svg index f2b95fc226..eb8c0fb5a2 100644 --- a/Telegram/Resources/icons/tray_monochrome.svg +++ b/Telegram/Resources/icons/tray_monochrome.svg @@ -1,10 +1,10 @@ - - - + + + - - + + diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index caac333013..ac5ef1dec2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -434,6 +434,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dlg_new_channel_name" = "Channel name"; "lng_dlg_new_bot_name" = "Bot name"; "lng_no_chats" = "Your chats will be here"; +"lng_no_conversations" = "You have no\nconversations yet."; +"lng_no_conversations_button" = "New Message"; +"lng_no_conversations_subtitle" = "Your contacts on Telegram"; "lng_no_chats_filter" = "No chats currently belong to this folder."; "lng_no_saved_sublists" = "You can save messages from other chats here."; "lng_contacts_loading" = "Loading..."; @@ -4260,6 +4263,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_to_msg" = "Go To Message"; "lng_context_reply_msg" = "Reply"; "lng_context_quote_and_reply" = "Quote & Reply"; +"lng_context_reply_to_task" = "Reply to Task"; "lng_context_edit_msg" = "Edit"; "lng_context_add_factcheck" = "Add Fact Check"; "lng_context_edit_factcheck" = "Edit Fact Check"; @@ -4450,6 +4454,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_inline_switch_cant" = "Sorry, no way to write here :("; "lng_preview_reply_to" = "Reply to {name}"; "lng_preview_reply_to_quote" = "Reply to quote from {name}"; +"lng_preview_reply_to_task" = "Reply to task from {title}"; "lng_suggest_bar_title" = "Suggest a Post Below"; "lng_suggest_bar_text" = "Click to offer a price for publishing."; diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 9beaa522d3..cc9c2313ed 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -38,6 +38,7 @@ ../../animations/edit_peers/topics_tabs.tgs ../../animations/edit_peers/topics_list.tgs ../../animations/edit_peers/direct_messages.tgs + ../../animations/no_chats.tgs ../../animations/dice/dice_idle.tgs ../../animations/dice/dart_idle.tgs diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index d83d286216..d63d433f73 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.16.4.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index b96320c234..218aa82970 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,16,3,0 - PRODUCTVERSION 5,16,3,0 + FILEVERSION 5,16,4,0 + PRODUCTVERSION 5,16,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop" - VALUE "FileVersion", "5.16.3.0" + VALUE "FileVersion", "5.16.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "5.16.3.0" + VALUE "ProductVersion", "5.16.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index e1293d48f7..81f44604c6 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,16,3,0 - PRODUCTVERSION 5,16,3,0 + FILEVERSION 5,16,4,0 + PRODUCTVERSION 5,16,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop Updater" - VALUE "FileVersion", "5.16.3.0" + VALUE "FileVersion", "5.16.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "5.16.3.0" + VALUE "ProductVersion", "5.16.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/api/api_unread_things.cpp b/Telegram/SourceFiles/api/api_unread_things.cpp index 1945e52738..80cf5ab55f 100644 --- a/Telegram/SourceFiles/api/api_unread_things.cpp +++ b/Telegram/SourceFiles/api/api_unread_things.cpp @@ -47,10 +47,10 @@ bool UnreadThings::trackReactions(Data::Thread *thread) const { return false; } const auto &settings = AyuSettings::getInstance(); - if (peer->isChannel() && !peer->isMegagroup() && !settings.hideChannelReactions) { + if (peer->isChannel() && !peer->isMegagroup() && !settings.showChannelReactions) { return false; } - if (peer->isMegagroup() && !settings.hideGroupReactions) { + if (peer->isMegagroup() && !settings.showGroupReactions) { return false; } return peer->isUser() || peer->isChat() || peer->isMegagroup(); diff --git a/Telegram/SourceFiles/ayu/ayu_settings.cpp b/Telegram/SourceFiles/ayu/ayu_settings.cpp index f6bb469bd2..3d55d637a8 100644 --- a/Telegram/SourceFiles/ayu/ayu_settings.cpp +++ b/Telegram/SourceFiles/ayu/ayu_settings.cpp @@ -231,8 +231,8 @@ AyuGramSettings::AyuGramSettings() { disableNotificationsDelay = false; localPremium = false; - hideChannelReactions = true; - hideGroupReactions = true; + showChannelReactions = true; + showGroupReactions = true; // ~ Customization appIcon = @@ -420,11 +420,11 @@ void set_localPremium(bool val) { } void set_hideChannelReactions(bool val) { - settings->hideChannelReactions = val; + settings->showChannelReactions = val; } void set_hideGroupReactions(bool val) { - settings->hideGroupReactions = val; + settings->showGroupReactions = val; } void set_appIcon(const QString &val) { diff --git a/Telegram/SourceFiles/ayu/ayu_settings.h b/Telegram/SourceFiles/ayu/ayu_settings.h index 192e59f2ee..86f2d8c205 100644 --- a/Telegram/SourceFiles/ayu/ayu_settings.h +++ b/Telegram/SourceFiles/ayu/ayu_settings.h @@ -76,8 +76,8 @@ public: bool disableNotificationsDelay; bool localPremium; - bool hideChannelReactions; - bool hideGroupReactions; + bool showChannelReactions; + bool showGroupReactions; QString appIcon; bool simpleQuotesAndReplies; @@ -259,8 +259,8 @@ inline void to_json(nlohmann::json &nlohmann_json_j, const AyuGramSettings &nloh NLOHMANN_JSON_TO(showGhostToggleInTray) NLOHMANN_JSON_TO(showStreamerToggleInTray) NLOHMANN_JSON_TO(monoFont) - NLOHMANN_JSON_TO(hideChannelReactions) - NLOHMANN_JSON_TO(hideGroupReactions) + NLOHMANN_JSON_TO(showChannelReactions) + NLOHMANN_JSON_TO(showGroupReactions) NLOHMANN_JSON_TO(hideNotificationCounters) NLOHMANN_JSON_TO(hideNotificationBadge) NLOHMANN_JSON_TO(hideAllChatsFolder) @@ -324,8 +324,8 @@ inline void from_json(const nlohmann::json &nlohmann_json_j, AyuGramSettings &nl NLOHMANN_JSON_FROM_WITH_DEFAULT(showGhostToggleInTray) NLOHMANN_JSON_FROM_WITH_DEFAULT(showStreamerToggleInTray) NLOHMANN_JSON_FROM_WITH_DEFAULT(monoFont) - NLOHMANN_JSON_FROM_WITH_DEFAULT(hideChannelReactions) - NLOHMANN_JSON_FROM_WITH_DEFAULT(hideGroupReactions) + NLOHMANN_JSON_FROM_WITH_DEFAULT(showChannelReactions) + NLOHMANN_JSON_FROM_WITH_DEFAULT(showGroupReactions) NLOHMANN_JSON_FROM_WITH_DEFAULT(hideNotificationCounters) NLOHMANN_JSON_FROM_WITH_DEFAULT(hideNotificationBadge) NLOHMANN_JSON_FROM_WITH_DEFAULT(hideAllChatsFolder) diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp b/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp index 92f977e308..52fbb0159b 100644 --- a/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp @@ -99,8 +99,11 @@ std::pair stateName(const PeerId &id) { void ForwardState::updateBottomBar(const Main::Session &session, const PeerId *peer, const State &st) { state = st; - - session.changes().peerUpdated(session.data().peer(*peer), Data::PeerUpdate::Flag::Rights); + auto peerCopy = *peer; + crl::on_main([&, peerCopy] + { + session.changes().peerUpdated(session.data().peer(peerCopy), Data::PeerUpdate::Flag::Rights); + }); } static Ui::PreparedList prepareMedia(not_null session, @@ -111,6 +114,10 @@ static Ui::PreparedList prepareMedia(not_null session, { groupMedia.emplace_back(media); auto prepared = Ui::PreparedFile(AyuSync::filePath(session, media)); + if (prepared.path.isEmpty()) { + // otherwise will fail assertion in PrepareDetails + return prepared; + } Storage::PrepareDetails(prepared, st::sendMediaPreviewSize, PhotoSideLimit()); return prepared; }; @@ -120,7 +127,9 @@ static Ui::PreparedList prepareMedia(not_null session, const auto groupId = startItem->groupId(); Ui::PreparedList list; - list.files.emplace_back(prepare(media)); + if (auto prepared = prepare(media); !prepared.path.isEmpty()) { + list.files.emplace_back(std::move(prepared)); + } if (!groupId.value) { return list; @@ -132,7 +141,9 @@ static Ui::PreparedList prepareMedia(not_null session, break; } if (const auto nextMedia = nextItem->media()) { - list.files.emplace_back(prepare(nextMedia)); + if (auto prepared = prepare(nextMedia); !prepared.path.isEmpty()) { + list.files.emplace_back(std::move(prepared)); + } i = k; } } @@ -296,7 +307,10 @@ void forwardMessages( const auto history = action.history; const auto peer = history->peer; - history->setForwardDraft(action.replyTo.topicRootId, action.replyTo.monoforumPeerId, {}); + crl::on_main([&] + { + history->setForwardDraft(action.replyTo.topicRootId, action.replyTo.monoforumPeerId, {}); + }); std::shared_ptr state; diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp index f8958c248b..d6236e766f 100644 --- a/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp @@ -5,8 +5,9 @@ // // Copyright @Radolyn, 2025 #include "ayu_sync.h" -#include "apiwrap.h" #include "api/api_sending.h" +#include "apiwrap.h" +#include "ayu/utils/telegram_helpers.h" #include "core/application.h" #include "core/core_settings.h" #include "core/file_utilities.h" @@ -127,19 +128,22 @@ void loadDocumentSync(not_null session, DocumentData *data, not_ auto latch = std::make_shared(1); auto lifetime = std::make_shared(); - data->save(Data::FileOriginMessage(item->fullId()), filePath(session, item->media())); - - rpl::single() | rpl::then( - session->downloaderTaskFinished() - ) | rpl::filter([&] + crl::on_main([&] { - return data->status == FileDownloadFailed || fileSize(item) == data->size; - }) | rpl::start_with_next([&]() mutable - { - latch->countDown(); - base::take(lifetime)->destroy(); - }, - *lifetime); + data->save(Data::FileOriginMessage(item->fullId()), filePath(session, item->media())); + + rpl::single() | rpl::then( + session->downloaderTaskFinished() + ) | rpl::filter([&] + { + return data->status == FileDownloadFailed || fileSize(item) == data->size; + }) | rpl::start_with_next([&]() mutable + { + latch->countDown(); + base::take(lifetime)->destroy(); + }, + *lifetime); + }); latch->await(std::chrono::minutes(5)); } @@ -207,19 +211,21 @@ void loadPhotoSync(not_null session, const std::pairdownloaderTaskFinished() | rpl::filter([&] + crl::on_main([&] { - return finalCheck(); - }) | rpl::start_with_next([&]() mutable - { - saveToFiles(); - latch->countDown(); - base::take(lifetime)->destroy(); - }, - *lifetime); + session->downloaderTaskFinished() | rpl::filter([&] + { + return finalCheck(); + }) | rpl::start_with_next([&]() mutable + { + saveToFiles(); + latch->countDown(); + base::take(lifetime)->destroy(); + }, + *lifetime); + }); + latch->await(std::chrono::minutes(5)); } - - latch->await(std::chrono::minutes(5)); } void sendMessageSync(not_null session, Api::MessageToSend &message) { @@ -240,17 +246,19 @@ void waitForMsgSync(not_null session, const Api::SendAction &act auto latch = std::make_shared(1); auto lifetime = std::make_shared(); - - session->data().itemIdChanged() - | rpl::filter([&](const Data::Session::IdChange &update) - { - return action.history->peer->id == update.newId.peer; - }) | rpl::start_with_next([&] - { - latch->countDown(); - base::take(lifetime)->destroy(); - }, - *lifetime); + crl::on_main([&] + { + session->data().itemIdChanged() + | rpl::filter([&](const Data::Session::IdChange &update) + { + return action.history->peer->id == update.newId.peer; + }) | rpl::start_with_next([&] + { + latch->countDown(); + base::take(lifetime)->destroy(); + }, + *lifetime); + }); latch->await(std::chrono::minutes(2)); } @@ -260,9 +268,6 @@ void sendDocumentSync(not_null session, SendMediaType type, TextWithTags &&caption, const Api::SendAction &action) { - const auto size = group.list.files.size(); - auto latch = std::make_shared(size); - auto lifetime = std::make_shared(); auto groupId = std::make_shared(); groupId->groupId = base::RandomValue(); @@ -272,27 +277,7 @@ void sendDocumentSync(not_null session, session->api().sendFiles(std::move(lst), type, std::move(caption), groupId, action); }); - - // probably need to handle - // session->uploader().photoFailed() - // and - // session->uploader().documentFailed() - // too - - rpl::merge( - session->uploader().documentReady(), - session->uploader().photoReady() - ) | rpl::filter([&](const Storage::UploadedMedia &docOrPhoto) - { - return docOrPhoto.fullId.peer == action.history->peer->id; - }) | rpl::start_with_next([&] - { - latch->countDown(); - }, - *lifetime); - - latch->await(std::chrono::minutes(5 * size)); - base::take(lifetime)->destroy(); + waitForMsgSync(session, action); } void sendStickerSync(not_null session, diff --git a/Telegram/SourceFiles/ayu/features/messageshot/message_shot.cpp b/Telegram/SourceFiles/ayu/features/messageshot/message_shot.cpp index 50d012cafe..581103ff68 100644 --- a/Telegram/SourceFiles/ayu/features/messageshot/message_shot.cpp +++ b/Telegram/SourceFiles/ayu/features/messageshot/message_shot.cpp @@ -209,8 +209,7 @@ bool MessageShotDelegate::elementHideReply(not_null HistoryView::ElementChatMode MessageShotDelegate::elementChatMode() { using Mode = HistoryView::ElementChatMode; - // Mode::Wide; - return Mode::Default; + return Mode::Wide; } QImage removeEmptySpaceAround(const QImage &original) { diff --git a/Telegram/SourceFiles/ayu/libs/json.hpp b/Telegram/SourceFiles/ayu/libs/json.hpp index 8b72ea6539..82d69f7c5d 100644 --- a/Telegram/SourceFiles/ayu/libs/json.hpp +++ b/Telegram/SourceFiles/ayu/libs/json.hpp @@ -1,9 +1,9 @@ // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT /****************************************************************************\ @@ -34,10 +34,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -47,10 +47,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -59,20 +59,24 @@ #ifndef JSON_SKIP_LIBRARY_VERSION_CHECK #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) - #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3 + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0 #warning "Already included a different version of the library!" #endif #endif #endif #define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum) #ifndef JSON_DIAGNOSTICS #define JSON_DIAGNOSTICS 0 #endif +#ifndef JSON_DIAGNOSTIC_POSITIONS + #define JSON_DIAGNOSTIC_POSITIONS 0 +#endif + #ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 #endif @@ -83,6 +87,12 @@ #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS #endif +#if JSON_DIAGNOSTIC_POSITIONS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS +#endif + #if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp #else @@ -94,14 +104,15 @@ #endif // Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ - NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ @@ -149,10 +160,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -162,6 +173,9 @@ #include // forward_list #include // inserter, front_inserter, end #include // map +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif #include // string #include // tuple, make_tuple #include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible @@ -172,10 +186,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -192,10 +206,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -208,10 +222,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -220,10 +234,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -233,10 +247,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -320,11 +334,11 @@ NLOHMANN_JSON_NAMESPACE_END // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-FileCopyrightText: 2016 - 2021 Evan Nemerson // SPDX-License-Identifier: MIT /* Hedley - https://nemequ.github.io/hedley @@ -2384,15 +2398,20 @@ JSON_HEDLEY_DIAGNOSTIC_POP // C++ language standard detection // if the user manually specified the used c++ version this is skipped -#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) - #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +#if !defined(JSON_HAS_CPP_23) && !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus > 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202002L) + #define JSON_HAS_CPP_23 #define JSON_HAS_CPP_20 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #elif (defined(__cplusplus) && __cplusplus > 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L) + #define JSON_HAS_CPP_20 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #elif (defined(__cplusplus) && __cplusplus > 201402L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201103L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) #define JSON_HAS_CPP_14 #endif // the cpp 11 flag is always specified because it is the minimal required version @@ -2568,7 +2587,9 @@ JSON_HEDLEY_DIAGNOSTIC_POP template \ inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [e](const std::pair& ej_pair) -> bool \ @@ -2580,7 +2601,9 @@ JSON_HEDLEY_DIAGNOSTIC_POP template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [&j](const std::pair& ej_pair) -> bool \ @@ -2743,42 +2766,146 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; #define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); -#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1) : nlohmann_json_default_obj.v1; /*! @brief macro @def NLOHMANN_DEFINE_TYPE_INTRUSIVE @since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE @since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } - -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } // inspired from https://stackoverflow.com/a/26745591 -// allows to call any std function as if (e.g. with begin): +// allows calling any std function as if (e.g., with begin): // using std::begin; begin(x); // // it allows using the detected idiom to retrieve the return type @@ -2939,10 +3066,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3014,10 +3141,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3056,10 +3183,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-FileCopyrightText: 2018 The Abseil Authors // SPDX-License-Identifier: MIT @@ -3219,7 +3346,7 @@ struct static_const #endif template -inline constexpr std::array make_array(Args&& ... args) +constexpr std::array make_array(Args&& ... args) { return std::array {{static_cast(std::forward(args))...}}; } @@ -3230,27 +3357,27 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // numeric_limits +#include // char_traits +#include // tuple #include // false_type, is_constructible, is_integral, is_same, true_type #include // declval -#include // tuple -#include // char_traits // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3293,7 +3420,7 @@ struct iterator_traits template struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> - : iterator_types + : iterator_types { }; @@ -3315,10 +3442,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3335,10 +3462,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3359,10 +3486,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ @@ -3624,7 +3751,7 @@ struct char_traits : std::char_traits static constexpr int_type eof() noexcept { - return static_cast(EOF); + return static_cast(std::char_traits::eof()); } }; @@ -3648,7 +3775,7 @@ struct char_traits : std::char_traits static constexpr int_type eof() noexcept { - return static_cast(EOF); + return static_cast(std::char_traits::eof()); } }; @@ -3674,19 +3801,19 @@ struct is_default_constructible : std::is_default_constructible {}; template struct is_default_constructible> - : conjunction, is_default_constructible> {}; + : conjunction, is_default_constructible> {}; template struct is_default_constructible> - : conjunction, is_default_constructible> {}; + : conjunction, is_default_constructible> {}; template struct is_default_constructible> - : conjunction...> {}; + : conjunction...> {}; template struct is_default_constructible> - : conjunction...> {}; + : conjunction...> {}; template struct is_constructible : std::is_constructible {}; @@ -3884,8 +4011,8 @@ is_detected::value&& // special case for types like std::filesystem::path whose iterator's value_type are themselves // c.f. https://github.com/nlohmann/json/pull/3073 !std::is_same>::value&& - is_complete_type < - detected_t>::value >> +is_complete_type < +detected_t>::value >> { using value_type = range_value_t; @@ -4008,12 +4135,12 @@ using is_usable_as_key_type = typename std::conditional < template> using is_usable_as_basic_json_key_type = typename std::conditional < - is_usable_as_key_type::value - && !is_json_iterator_of::value, - std::true_type, - std::false_type >::type; + is_usable_as_key_type::value + && !is_json_iterator_of::value, + std::true_type, + std::false_type >::type; template using detect_erase_with_key_type = decltype(std::declval().erase(std::declval())); @@ -4147,7 +4274,7 @@ struct value_in_range_of_impl1 }; template -inline constexpr bool value_in_range_of(T val) +constexpr bool value_in_range_of(T val) { return value_in_range_of_impl1::test(val); } @@ -4163,7 +4290,7 @@ namespace impl { template -inline constexpr bool is_c_string() +constexpr bool is_c_string() { using TUnExt = typename std::remove_extent::type; using TUnCVExt = typename std::remove_cv::type; @@ -4191,7 +4318,7 @@ namespace impl { template -inline constexpr bool is_transparent() +constexpr bool is_transparent() { return is_detected::value; } @@ -4210,10 +4337,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -4358,6 +4485,18 @@ inline OutStringType concat(Args && ... args) NLOHMANN_JSON_NAMESPACE_END +// With -Wweak-vtables, Clang will complain about the exception classes as they +// have no out-of-line virtual method definitions and their vtable will be +// emitted in every translation unit. This issue cannot be fixed with a +// header-only library as there is no implementation file to move these +// functions to. As a result, we suppress this warning here to avoid client +// code to stumble over this. See https://github.com/nlohmann/json/issues/4087 +// for a discussion. +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wweak-vtables" +#endif + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { @@ -4452,16 +4591,34 @@ class exception : public std::exception { return concat(a, '/', detail::escape(b)); }); - return concat('(', str, ") "); + + return concat('(', str, ") ", get_byte_positions(leaf_element)); #else - static_cast(leaf_element); - return ""; + return get_byte_positions(leaf_element); #endif } private: /// an exception object as storage for error messages std::runtime_error m; +#if JSON_DIAGNOSTIC_POSITIONS + template + static std::string get_byte_positions(const BasicJsonType* leaf_element) + { + if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos)) + { + return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") "); + } + return ""; + } +#else + template + static std::string get_byte_positions(const BasicJsonType* leaf_element) + { + static_cast(leaf_element); + return ""; + } +#endif }; /// @brief exception indicating a parse error @@ -4589,6 +4746,10 @@ class other_error : public exception } // namespace detail NLOHMANN_JSON_NAMESPACE_END +#if defined(__clang__) + #pragma clang diagnostic pop +#endif + // #include // #include @@ -4596,10 +4757,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -4620,10 +4781,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -4640,7 +4801,7 @@ namespace std_fs = std::experimental::filesystem; } // namespace detail NLOHMANN_JSON_NAMESPACE_END #elif JSON_HAS_FILESYSTEM -#include +#include // NOLINT(build/c++17) NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { @@ -4670,6 +4831,24 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) n = nullptr; } +#ifdef JSON_HAS_CPP_17 +#ifndef JSON_USE_IMPLICIT_CONVERSIONS +template +void from_json(const BasicJsonType& j, std::optional& opt) +{ + if (j.is_null()) + { + opt = std::nullopt; + } + else + { + opt.emplace(j.template get()); + } +} + +#endif // JSON_USE_IMPLICIT_CONVERSIONS +#endif // JSON_HAS_CPP_17 + // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& @@ -4817,6 +4996,54 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines } } +template +auto from_json(const BasicJsonType& j, T (&arr)[N1][N2]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i1 = 0; i1 < N1; ++i1) + { + for (std::size_t i2 = 0; i2 < N2; ++i2) + { + arr[i1][i2] = j.at(i1).at(i2).template get(); + } + } +} + +template +auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i1 = 0; i1 < N1; ++i1) + { + for (std::size_t i2 = 0; i2 < N2; ++i2) + { + for (std::size_t i3 = 0; i3 < N3; ++i3) + { + arr[i1][i2][i3] = j.at(i1).at(i2).at(i3).template get(); + } + } + } +} + +template +auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3][N4]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i1 = 0; i1 < N1; ++i1) + { + for (std::size_t i2 = 0; i2 < N2; ++i2) + { + for (std::size_t i3 = 0; i3 < N3; ++i3) + { + for (std::size_t i4 = 0; i4 < N4; ++i4) + { + arr[i1][i2][i3][i4] = j.at(i1).at(i2).at(i3).at(i4).template get(); + } + } + } + } +} + template inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { @@ -4902,7 +5129,7 @@ void()) template < typename BasicJsonType, typename T, std::size_t... Idx > std::array from_json_inplace_array_impl(BasicJsonType&& j, - identity_tag> /*unused*/, index_sequence /*unused*/) + identity_tag> /*unused*/, index_sequence /*unused*/) { return { { std::forward(j).at(Idx).template get()... } }; } @@ -5006,6 +5233,12 @@ std::tuple from_json_tuple_impl_base(BasicJsonType&& j, index_sequence< return std::make_tuple(std::forward(j).at(Idx).template get()...); } +template +std::tuple<> from_json_tuple_impl_base(BasicJsonType& /*unused*/, index_sequence<> /*unused*/) +{ + return {}; +} + template < typename BasicJsonType, class A1, class A2 > std::pair from_json_tuple_impl(BasicJsonType&& j, identity_tag> /*unused*/, priority_tag<0> /*unused*/) { @@ -5091,7 +5324,12 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); } - p = *j.template get_ptr(); + const auto& s = *j.template get_ptr(); +#ifdef JSON_HAS_CPP_20 + p = std_fs::path(std::u8string_view(reinterpret_cast(s.data()), s.size())); +#else + p = std_fs::u8path(s); // accepts UTF-8 encoded std::string in C++17, deprecated in C++20 +#endif } #endif @@ -5126,14 +5364,20 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT +// #include +// JSON_HAS_CPP_17 +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif + #include // copy #include // begin, end #include // string @@ -5146,17 +5390,16 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // size_t -#include // input_iterator_tag -#include // string, to_string +#include // forward_iterator_tag #include // tuple_size, get, tuple_element #include // move @@ -5168,6 +5411,46 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t +#include // string, to_string + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +void int_to_string(StringType& target, std::size_t value) +{ + // For ADL + using std::to_string; + target = to_string(value); +} + +template +StringType to_string(std::size_t value) +{ + StringType result; + int_to_string(result, value); + return result; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + // #include @@ -5175,13 +5458,6 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { -template -void int_to_string( string_type& target, std::size_t value ) -{ - // For ADL - using std::to_string; - target = to_string(value); -} template class iteration_proxy_value { public: @@ -5189,7 +5465,7 @@ template class iteration_proxy_value using value_type = iteration_proxy_value; using pointer = value_type *; using reference = value_type &; - using iterator_category = std::input_iterator_tag; + using iterator_category = std::forward_iterator_tag; using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; private: @@ -5369,7 +5645,7 @@ namespace std #endif template class tuple_size<::nlohmann::detail::iteration_proxy_value> // NOLINT(cert-dcl58-cpp) - : public std::integral_constant {}; + : public std::integral_constant {}; template class tuple_element> // NOLINT(cert-dcl58-cpp) @@ -5390,8 +5666,6 @@ class tuple_element> inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy> = true; #endif -// #include - // #include // #include @@ -5637,6 +5911,22 @@ struct external_constructor // to_json // ///////////// +#ifdef JSON_HAS_CPP_17 +template::value, int> = 0> +void to_json(BasicJsonType& j, const std::optional& opt) +{ + if (opt.has_value()) + { + j = *opt; + } + else + { + j = nullptr; + } +} +#endif + template::value, int> = 0> inline void to_json(BasicJsonType& j, T b) noexcept @@ -5697,7 +5987,8 @@ template::type; - external_constructor::construct(j, static_cast(e)); + static constexpr value_t integral_value_t = std::is_unsigned::value ? value_t::number_unsigned : value_t::number_integer; + external_constructor::construct(j, static_cast(e)); } #endif // JSON_DISABLE_ENUM_SERIALIZATION @@ -5782,6 +6073,13 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence< j = { std::get(t)... }; } +template +inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/) +{ + using array_t = typename BasicJsonType::array_t; + j = array_t(); +} + template::value, int > = 0> inline void to_json(BasicJsonType& j, const T& t) { @@ -5792,7 +6090,12 @@ inline void to_json(BasicJsonType& j, const T& t) template inline void to_json(BasicJsonType& j, const std_fs::path& p) { - j = p.string(); +#ifdef JSON_HAS_CPP_20 + const std::u8string s = p.u8string(); + j = std::string(s.begin(), s.end()); +#else + j = p.u8string(); // returns std::string in C++17 +#endif } #endif @@ -5867,10 +6170,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -5979,10 +6282,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -6112,10 +6415,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -6132,16 +6435,19 @@ NLOHMANN_JSON_NAMESPACE_END #include // char_traits, string #include // make_pair, move #include // vector +#ifdef __cpp_lib_byteswap + #include //byteswap +#endif // #include // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -6161,6 +6467,8 @@ NLOHMANN_JSON_NAMESPACE_END #include // istream #endif // JSON_NO_IO +// #include + // #include // #include @@ -6208,6 +6516,13 @@ class file_input_adapter return std::fgetc(m_file); } + // returns the number of characters successfully read + template + std::size_t get_elements(T* dest, std::size_t count = 1) + { + return fread(dest, 1, sizeof(T) * count, m_file); + } + private: /// the file pointer to read from std::FILE* m_file; @@ -6267,6 +6582,17 @@ class input_stream_adapter return res; } + template + std::size_t get_elements(T* dest, std::size_t count = 1) + { + auto res = static_cast(sb->sgetn(reinterpret_cast(dest), static_cast(count * sizeof(T)))); + if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T))) + { + is->clear(is->rdstate() | std::ios::eofbit); + } + return res; + } + private: /// the associated input stream std::istream* is = nullptr; @@ -6298,6 +6624,26 @@ class iterator_input_adapter return char_traits::eof(); } + // for general iterators, we cannot really do something better than falling back to processing the range one-by-one + template + std::size_t get_elements(T* dest, std::size_t count = 1) + { + auto* ptr = reinterpret_cast(dest); + for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index) + { + if (JSON_HEDLEY_LIKELY(current != end)) + { + ptr[read_index] = static_cast(*current); + std::advance(current, 1); + } + else + { + return read_index; + } + } + return count * sizeof(T); + } + private: IteratorType current; IteratorType end; @@ -6461,6 +6807,13 @@ class wide_string_input_adapter return utf8_bytes[utf8_bytes_index++]; } + // parsing binary with wchar doesn't make sense, but since the parsing mode can be runtime, we need something here + template + std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1) + { + JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr)); + } + private: BaseInputAdapter base_adapter; @@ -6557,10 +6910,17 @@ typename container_input_adapter_factory_impl::container_input_adapter_factory::create(container); } +// specialization for std::string +using string_input_adapter_type = decltype(input_adapter(std::declval())); + #ifndef JSON_NO_IO // Special cases with fast paths inline file_input_adapter input_adapter(std::FILE* file) { + if (file == nullptr) + { + JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + } return file_input_adapter(file); } @@ -6587,9 +6947,13 @@ template < typename CharT, int >::type = 0 > contiguous_bytes_input_adapter input_adapter(CharT b) { + if (b == nullptr) + { + JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + } auto length = std::strlen(reinterpret_cast(b)); const auto* ptr = reinterpret_cast(b); - return input_adapter(ptr, ptr + length); + return input_adapter(ptr, ptr + length); // cppcheck-suppress[nullPointerArithmeticRedundantCheck] } template @@ -6635,742 +6999,29 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include #include // string +#include // enable_if_t #include // move #include // vector // #include -// #include - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN - -/*! -@brief SAX interface - -This class describes the SAX interface used by @ref nlohmann::json::sax_parse. -Each function is called in different situations while the input is parsed. The -boolean return value informs the parser whether to continue processing the -input. -*/ -template -struct json_sax -{ - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - - /*! - @brief a null value was read - @return whether parsing should proceed - */ - virtual bool null() = 0; - - /*! - @brief a boolean value was read - @param[in] val boolean value - @return whether parsing should proceed - */ - virtual bool boolean(bool val) = 0; - - /*! - @brief an integer number was read - @param[in] val integer value - @return whether parsing should proceed - */ - virtual bool number_integer(number_integer_t val) = 0; - - /*! - @brief an unsigned integer number was read - @param[in] val unsigned integer value - @return whether parsing should proceed - */ - virtual bool number_unsigned(number_unsigned_t val) = 0; - - /*! - @brief a floating-point number was read - @param[in] val floating-point value - @param[in] s raw token value - @return whether parsing should proceed - */ - virtual bool number_float(number_float_t val, const string_t& s) = 0; - - /*! - @brief a string value was read - @param[in] val string value - @return whether parsing should proceed - @note It is safe to move the passed string value. - */ - virtual bool string(string_t& val) = 0; - - /*! - @brief a binary value was read - @param[in] val binary value - @return whether parsing should proceed - @note It is safe to move the passed binary value. - */ - virtual bool binary(binary_t& val) = 0; - - /*! - @brief the beginning of an object was read - @param[in] elements number of object elements or -1 if unknown - @return whether parsing should proceed - @note binary formats may report the number of elements - */ - virtual bool start_object(std::size_t elements) = 0; - - /*! - @brief an object key was read - @param[in] val object key - @return whether parsing should proceed - @note It is safe to move the passed string. - */ - virtual bool key(string_t& val) = 0; - - /*! - @brief the end of an object was read - @return whether parsing should proceed - */ - virtual bool end_object() = 0; - - /*! - @brief the beginning of an array was read - @param[in] elements number of array elements or -1 if unknown - @return whether parsing should proceed - @note binary formats may report the number of elements - */ - virtual bool start_array(std::size_t elements) = 0; - - /*! - @brief the end of an array was read - @return whether parsing should proceed - */ - virtual bool end_array() = 0; - - /*! - @brief a parse error occurred - @param[in] position the position in the input where the error occurs - @param[in] last_token the last read token - @param[in] ex an exception object describing the error - @return whether parsing should proceed (must return false) - */ - virtual bool parse_error(std::size_t position, - const std::string& last_token, - const detail::exception& ex) = 0; - - json_sax() = default; - json_sax(const json_sax&) = default; - json_sax(json_sax&&) noexcept = default; - json_sax& operator=(const json_sax&) = default; - json_sax& operator=(json_sax&&) noexcept = default; - virtual ~json_sax() = default; -}; - -namespace detail -{ -/*! -@brief SAX implementation to create a JSON value from SAX events - -This class implements the @ref json_sax interface and processes the SAX events -to create a JSON value which makes it basically a DOM parser. The structure or -hierarchy of the JSON value is managed by the stack `ref_stack` which contains -a pointer to the respective array or object for each recursion depth. - -After successful parsing, the value that is passed by reference to the -constructor contains the parsed value. - -@tparam BasicJsonType the JSON type -*/ -template -class json_sax_dom_parser -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - - /*! - @param[in,out] r reference to a JSON value that is manipulated while - parsing - @param[in] allow_exceptions_ whether parse errors yield exceptions - */ - explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) - : root(r), allow_exceptions(allow_exceptions_) - {} - - // make class move-only - json_sax_dom_parser(const json_sax_dom_parser&) = delete; - json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; - json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - ~json_sax_dom_parser() = default; - - bool null() - { - handle_value(nullptr); - return true; - } - - bool boolean(bool val) - { - handle_value(val); - return true; - } - - bool number_integer(number_integer_t val) - { - handle_value(val); - return true; - } - - bool number_unsigned(number_unsigned_t val) - { - handle_value(val); - return true; - } - - bool number_float(number_float_t val, const string_t& /*unused*/) - { - handle_value(val); - return true; - } - - bool string(string_t& val) - { - handle_value(val); - return true; - } - - bool binary(binary_t& val) - { - handle_value(std::move(val)); - return true; - } - - bool start_object(std::size_t len) - { - ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); - - if (JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } - - return true; - } - - bool key(string_t& val) - { - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(ref_stack.back()->is_object()); - - // add null at given key and store the reference for later - object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val)); - return true; - } - - bool end_object() - { - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(ref_stack.back()->is_object()); - - ref_stack.back()->set_parents(); - ref_stack.pop_back(); - return true; - } - - bool start_array(std::size_t len) - { - ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); - - if (JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); - } - - return true; - } - - bool end_array() - { - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(ref_stack.back()->is_array()); - - ref_stack.back()->set_parents(); - ref_stack.pop_back(); - return true; - } - - template - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, - const Exception& ex) - { - errored = true; - static_cast(ex); - if (allow_exceptions) - { - JSON_THROW(ex); - } - return false; - } - - constexpr bool is_errored() const - { - return errored; - } - - private: - /*! - @invariant If the ref stack is empty, then the passed value will be the new - root. - @invariant If the ref stack contains a value, then it is an array or an - object to which we can add elements - */ - template - JSON_HEDLEY_RETURNS_NON_NULL - BasicJsonType* handle_value(Value&& v) - { - if (ref_stack.empty()) - { - root = BasicJsonType(std::forward(v)); - return &root; - } - - JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); - - if (ref_stack.back()->is_array()) - { - ref_stack.back()->m_data.m_value.array->emplace_back(std::forward(v)); - return &(ref_stack.back()->m_data.m_value.array->back()); - } - - JSON_ASSERT(ref_stack.back()->is_object()); - JSON_ASSERT(object_element); - *object_element = BasicJsonType(std::forward(v)); - return object_element; - } - - /// the parsed JSON value - BasicJsonType& root; - /// stack to model hierarchy of values - std::vector ref_stack {}; - /// helper to hold the reference for the next object element - BasicJsonType* object_element = nullptr; - /// whether a syntax error occurred - bool errored = false; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; -}; - -template -class json_sax_dom_callback_parser -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - using parser_callback_t = typename BasicJsonType::parser_callback_t; - using parse_event_t = typename BasicJsonType::parse_event_t; - - json_sax_dom_callback_parser(BasicJsonType& r, - const parser_callback_t cb, - const bool allow_exceptions_ = true) - : root(r), callback(cb), allow_exceptions(allow_exceptions_) - { - keep_stack.push_back(true); - } - - // make class move-only - json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; - json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; - json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - ~json_sax_dom_callback_parser() = default; - - bool null() - { - handle_value(nullptr); - return true; - } - - bool boolean(bool val) - { - handle_value(val); - return true; - } - - bool number_integer(number_integer_t val) - { - handle_value(val); - return true; - } - - bool number_unsigned(number_unsigned_t val) - { - handle_value(val); - return true; - } - - bool number_float(number_float_t val, const string_t& /*unused*/) - { - handle_value(val); - return true; - } - - bool string(string_t& val) - { - handle_value(val); - return true; - } - - bool binary(binary_t& val) - { - handle_value(std::move(val)); - return true; - } - - bool start_object(std::size_t len) - { - // check callback for object start - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); - keep_stack.push_back(keep); - - auto val = handle_value(BasicJsonType::value_t::object, true); - ref_stack.push_back(val.second); - - // check object limit - if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } - - return true; - } - - bool key(string_t& val) - { - BasicJsonType k = BasicJsonType(val); - - // check callback for key - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); - key_keep_stack.push_back(keep); - - // add discarded value at given key and store the reference for later - if (keep && ref_stack.back()) - { - object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded); - } - - return true; - } - - bool end_object() - { - if (ref_stack.back()) - { - if (!callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) - { - // discard object - *ref_stack.back() = discarded; - } - else - { - ref_stack.back()->set_parents(); - } - } - - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - - if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) - { - // remove discarded value - for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) - { - if (it->is_discarded()) - { - ref_stack.back()->erase(it); - break; - } - } - } - - return true; - } - - bool start_array(std::size_t len) - { - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); - keep_stack.push_back(keep); - - auto val = handle_value(BasicJsonType::value_t::array, true); - ref_stack.push_back(val.second); - - // check array limit - if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); - } - - return true; - } - - bool end_array() - { - bool keep = true; - - if (ref_stack.back()) - { - keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); - if (keep) - { - ref_stack.back()->set_parents(); - } - else - { - // discard array - *ref_stack.back() = discarded; - } - } - - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - - // remove discarded value - if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) - { - ref_stack.back()->m_data.m_value.array->pop_back(); - } - - return true; - } - - template - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, - const Exception& ex) - { - errored = true; - static_cast(ex); - if (allow_exceptions) - { - JSON_THROW(ex); - } - return false; - } - - constexpr bool is_errored() const - { - return errored; - } - - private: - /*! - @param[in] v value to add to the JSON value we build during parsing - @param[in] skip_callback whether we should skip calling the callback - function; this is required after start_array() and - start_object() SAX events, because otherwise we would call the - callback function with an empty array or object, respectively. - - @invariant If the ref stack is empty, then the passed value will be the new - root. - @invariant If the ref stack contains a value, then it is an array or an - object to which we can add elements - - @return pair of boolean (whether value should be kept) and pointer (to the - passed value in the ref_stack hierarchy; nullptr if not kept) - */ - template - std::pair handle_value(Value&& v, const bool skip_callback = false) - { - JSON_ASSERT(!keep_stack.empty()); - - // do not handle this value if we know it would be added to a discarded - // container - if (!keep_stack.back()) - { - return {false, nullptr}; - } - - // create value - auto value = BasicJsonType(std::forward(v)); - - // check callback - const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); - - // do not handle this value if we just learnt it shall be discarded - if (!keep) - { - return {false, nullptr}; - } - - if (ref_stack.empty()) - { - root = std::move(value); - return {true, & root}; - } - - // skip this value if we already decided to skip the parent - // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) - if (!ref_stack.back()) - { - return {false, nullptr}; - } - - // we now only expect arrays and objects - JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); - - // array - if (ref_stack.back()->is_array()) - { - ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value)); - return {true, & (ref_stack.back()->m_data.m_value.array->back())}; - } - - // object - JSON_ASSERT(ref_stack.back()->is_object()); - // check if we should store an element for the current key - JSON_ASSERT(!key_keep_stack.empty()); - const bool store_element = key_keep_stack.back(); - key_keep_stack.pop_back(); - - if (!store_element) - { - return {false, nullptr}; - } - - JSON_ASSERT(object_element); - *object_element = std::move(value); - return {true, object_element}; - } - - /// the parsed JSON value - BasicJsonType& root; - /// stack to model hierarchy of values - std::vector ref_stack {}; - /// stack to manage which values to keep - std::vector keep_stack {}; - /// stack to manage which object keys to keep - std::vector key_keep_stack {}; - /// helper to hold the reference for the next object element - BasicJsonType* object_element = nullptr; - /// whether a syntax error occurred - bool errored = false; - /// callback function - const parser_callback_t callback = nullptr; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; - /// a discarded value for the callback - BasicJsonType discarded = BasicJsonType::value_t::discarded; -}; - -template -class json_sax_acceptor -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - - bool null() - { - return true; - } - - bool boolean(bool /*unused*/) - { - return true; - } - - bool number_integer(number_integer_t /*unused*/) - { - return true; - } - - bool number_unsigned(number_unsigned_t /*unused*/) - { - return true; - } - - bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) - { - return true; - } - - bool string(string_t& /*unused*/) - { - return true; - } - - bool binary(binary_t& /*unused*/) - { - return true; - } - - bool start_object(std::size_t /*unused*/ = static_cast(-1)) - { - return true; - } - - bool key(string_t& /*unused*/) - { - return true; - } - - bool end_object() - { - return true; - } - - bool start_array(std::size_t /*unused*/ = static_cast(-1)) - { - return true; - } - - bool end_array() - { - return true; - } - - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) - { - return false; - } -}; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -8338,7 +7989,7 @@ class lexer : public lexer_base locale's decimal point is used instead of `.` to work with the locale-dependent converters. */ - token_type scan_number() // lgtm [cpp/use-of-goto] + token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated. { // reset token_buffer to store the number's bytes reset(); @@ -8420,6 +8071,7 @@ scan_number_zero: case '.': { add(decimal_point_char); + decimal_point_position = token_buffer.size() - 1; goto scan_number_decimal1; } @@ -8456,6 +8108,7 @@ scan_number_any1: case '.': { add(decimal_point_char); + decimal_point_position = token_buffer.size() - 1; goto scan_number_decimal1; } @@ -8616,7 +8269,7 @@ scan_number_done: // we are done scanning a number) unget(); - char* endptr = nullptr; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg) errno = 0; // try to parse integers first and fall back to floats @@ -8627,7 +8280,7 @@ scan_number_done: // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - if (errno == 0) + if (errno != ERANGE) { value_unsigned = static_cast(x); if (value_unsigned == x) @@ -8643,7 +8296,7 @@ scan_number_done: // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - if (errno == 0) + if (errno != ERANGE) { value_integer = static_cast(x); if (value_integer == x) @@ -8693,6 +8346,7 @@ scan_number_done: { token_buffer.clear(); token_string.clear(); + decimal_point_position = std::string::npos; token_string.push_back(char_traits::to_char_type(current)); } @@ -8801,6 +8455,11 @@ scan_number_done: /// return current string value (implicitly resets the token; useful only once) string_t& get_string() { + // translate decimal points from locale back to '.' (#4084) + if (decimal_point_char != '.' && decimal_point_position != std::string::npos) + { + token_buffer[decimal_point_position] = '.'; + } return token_buffer; } @@ -8998,6 +8657,8 @@ scan_number_done: /// the decimal point const char_int_type decimal_point_char = '.'; + /// the position of the decimal point in the input + std::size_t decimal_point_position = std::string::npos; }; } // namespace detail @@ -9005,13 +8666,986 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + +NLOHMANN_JSON_NAMESPACE_BEGIN + +/*! +@brief SAX interface + +This class describes the SAX interface used by @ref nlohmann::json::sax_parse. +Each function is called in different situations while the input is parsed. The +boolean return value informs the parser whether to continue processing the +input. +*/ +template +struct json_sax +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + /*! + @brief a null value was read + @return whether parsing should proceed + */ + virtual bool null() = 0; + + /*! + @brief a boolean value was read + @param[in] val boolean value + @return whether parsing should proceed + */ + virtual bool boolean(bool val) = 0; + + /*! + @brief an integer number was read + @param[in] val integer value + @return whether parsing should proceed + */ + virtual bool number_integer(number_integer_t val) = 0; + + /*! + @brief an unsigned integer number was read + @param[in] val unsigned integer value + @return whether parsing should proceed + */ + virtual bool number_unsigned(number_unsigned_t val) = 0; + + /*! + @brief a floating-point number was read + @param[in] val floating-point value + @param[in] s raw token value + @return whether parsing should proceed + */ + virtual bool number_float(number_float_t val, const string_t& s) = 0; + + /*! + @brief a string value was read + @param[in] val string value + @return whether parsing should proceed + @note It is safe to move the passed string value. + */ + virtual bool string(string_t& val) = 0; + + /*! + @brief a binary value was read + @param[in] val binary value + @return whether parsing should proceed + @note It is safe to move the passed binary value. + */ + virtual bool binary(binary_t& val) = 0; + + /*! + @brief the beginning of an object was read + @param[in] elements number of object elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_object(std::size_t elements) = 0; + + /*! + @brief an object key was read + @param[in] val object key + @return whether parsing should proceed + @note It is safe to move the passed string. + */ + virtual bool key(string_t& val) = 0; + + /*! + @brief the end of an object was read + @return whether parsing should proceed + */ + virtual bool end_object() = 0; + + /*! + @brief the beginning of an array was read + @param[in] elements number of array elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_array(std::size_t elements) = 0; + + /*! + @brief the end of an array was read + @return whether parsing should proceed + */ + virtual bool end_array() = 0; + + /*! + @brief a parse error occurred + @param[in] position the position in the input where the error occurs + @param[in] last_token the last read token + @param[in] ex an exception object describing the error + @return whether parsing should proceed (must return false) + */ + virtual bool parse_error(std::size_t position, + const std::string& last_token, + const detail::exception& ex) = 0; + + json_sax() = default; + json_sax(const json_sax&) = default; + json_sax(json_sax&&) noexcept = default; + json_sax& operator=(const json_sax&) = default; + json_sax& operator=(json_sax&&) noexcept = default; + virtual ~json_sax() = default; +}; + +namespace detail +{ +constexpr std::size_t unknown_size() +{ + return (std::numeric_limits::max)(); +} + +/*! +@brief SAX implementation to create a JSON value from SAX events + +This class implements the @ref json_sax interface and processes the SAX events +to create a JSON value which makes it basically a DOM parser. The structure or +hierarchy of the JSON value is managed by the stack `ref_stack` which contains +a pointer to the respective array or object for each recursion depth. + +After successful parsing, the value that is passed by reference to the +constructor contains the parsed value. + +@tparam BasicJsonType the JSON type +*/ +template +class json_sax_dom_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using lexer_t = lexer; + + /*! + @param[in,out] r reference to a JSON value that is manipulated while + parsing + @param[in] allow_exceptions_ whether parse errors yield exceptions + */ + explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr) + : root(r), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_) + {} + + // make class move-only + json_sax_dom_parser(const json_sax_dom_parser&) = delete; + json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; + json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~json_sax_dom_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } + + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } + + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } + + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } + + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); + +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the object here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + // Lexer has read the first character of the object, so + // subtract 1 from the position to get the correct start position. + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif + + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); + } + + return true; + } + + bool key(string_t& val) + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); + + // add null at given key and store the reference for later + object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val)); + return true; + } + + bool end_object() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); + +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing brace, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif + + ref_stack.back()->set_parents(); + ref_stack.pop_back(); + return true; + } + + bool start_array(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); + +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the array here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif + + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + } + + return true; + } + + bool end_array() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_array()); + +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing bracket, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif + + ref_stack.back()->set_parents(); + ref_stack.pop_back(); + return true; + } + + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + +#if JSON_DIAGNOSTIC_POSITIONS + void handle_diagnostic_positions_for_json_value(BasicJsonType& v) + { + if (m_lexer_ref) + { + // Lexer has read past the current field value, so set the end position to the current position. + // The start position will be set below based on the length of the string representation + // of the value. + v.end_position = m_lexer_ref->get_position(); + + switch (v.type()) + { + case value_t::boolean: + { + // 4 and 5 are the string length of "true" and "false" + v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5); + break; + } + + case value_t::null: + { + // 4 is the string length of "null" + v.start_position = v.end_position - 4; + break; + } + + case value_t::string: + { + // include the length of the quotes, which is 2 + v.start_position = v.end_position - v.m_data.m_value.string->size() - 2; + break; + } + + // As we handle the start and end positions for values created during parsing, + // we do not expect the following value type to be called. Regardless, set the positions + // in case this is created manually or through a different constructor. Exclude from lcov + // since the exact condition of this switch is esoteric. + // LCOV_EXCL_START + case value_t::discarded: + { + v.end_position = std::string::npos; + v.start_position = v.end_position; + break; + } + // LCOV_EXCL_STOP + case value_t::binary: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + { + v.start_position = v.end_position - m_lexer_ref->get_string().size(); + break; + } + case value_t::object: + case value_t::array: + { + // object and array are handled in start_object() and start_array() handlers + // skip setting the values here. + break; + } + default: // LCOV_EXCL_LINE + // Handle all possible types discretely, default handler should never be reached. + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE + } + } + } +#endif + + /*! + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + */ + template + JSON_HEDLEY_RETURNS_NON_NULL + BasicJsonType* handle_value(Value&& v) + { + if (ref_stack.empty()) + { + root = BasicJsonType(std::forward(v)); + +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(root); +#endif + + return &root; + } + + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->emplace_back(std::forward(v)); + +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(ref_stack.back()->m_data.m_value.array->back()); +#endif + + return &(ref_stack.back()->m_data.m_value.array->back()); + } + + JSON_ASSERT(ref_stack.back()->is_object()); + JSON_ASSERT(object_element); + *object_element = BasicJsonType(std::forward(v)); + +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(*object_element); +#endif + + return object_element; + } + + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// the lexer reference to obtain the current position + lexer_t* m_lexer_ref = nullptr; +}; + +template +class json_sax_dom_callback_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using parser_callback_t = typename BasicJsonType::parser_callback_t; + using parse_event_t = typename BasicJsonType::parse_event_t; + using lexer_t = lexer; + + json_sax_dom_callback_parser(BasicJsonType& r, + parser_callback_t cb, + const bool allow_exceptions_ = true, + lexer_t* lexer_ = nullptr) + : root(r), callback(std::move(cb)), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_) + { + keep_stack.push_back(true); + } + + // make class move-only + json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~json_sax_dom_callback_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } + + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } + + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } + + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } + + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + // check callback for object start + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::object, true); + ref_stack.push_back(val.second); + + if (ref_stack.back()) + { + +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the object here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + // Lexer has read the first character of the object, so + // subtract 1 from the position to get the correct start position. + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif + + // check object limit + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); + } + } + return true; + } + + bool key(string_t& val) + { + BasicJsonType k = BasicJsonType(val); + + // check callback for key + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); + key_keep_stack.push_back(keep); + + // add discarded value at given key and store the reference for later + if (keep && ref_stack.back()) + { + object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded); + } + + return true; + } + + bool end_object() + { + if (ref_stack.back()) + { + if (!callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) + { + // discard object + *ref_stack.back() = discarded; + +#if JSON_DIAGNOSTIC_POSITIONS + // Set start/end positions for discarded object. + handle_diagnostic_positions_for_json_value(*ref_stack.back()); +#endif + } + else + { + +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing brace, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif + + ref_stack.back()->set_parents(); + } + } + + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) + { + // remove discarded value + for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) + { + if (it->is_discarded()) + { + ref_stack.back()->erase(it); + break; + } + } + } + + return true; + } + + bool start_array(std::size_t len) + { + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::array, true); + ref_stack.push_back(val.second); + + if (ref_stack.back()) + { + +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the array here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + // Lexer has read the first character of the array, so + // subtract 1 from the position to get the correct start position. + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif + + // check array limit + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + } + } + + return true; + } + + bool end_array() + { + bool keep = true; + + if (ref_stack.back()) + { + keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); + if (keep) + { + +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing bracket, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif + + ref_stack.back()->set_parents(); + } + else + { + // discard array + *ref_stack.back() = discarded; + +#if JSON_DIAGNOSTIC_POSITIONS + // Set start/end positions for discarded array. + handle_diagnostic_positions_for_json_value(*ref_stack.back()); +#endif + } + } + + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + // remove discarded value + if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->pop_back(); + } + + return true; + } + + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + +#if JSON_DIAGNOSTIC_POSITIONS + void handle_diagnostic_positions_for_json_value(BasicJsonType& v) + { + if (m_lexer_ref) + { + // Lexer has read past the current field value, so set the end position to the current position. + // The start position will be set below based on the length of the string representation + // of the value. + v.end_position = m_lexer_ref->get_position(); + + switch (v.type()) + { + case value_t::boolean: + { + // 4 and 5 are the string length of "true" and "false" + v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5); + break; + } + + case value_t::null: + { + // 4 is the string length of "null" + v.start_position = v.end_position - 4; + break; + } + + case value_t::string: + { + // include the length of the quotes, which is 2 + v.start_position = v.end_position - v.m_data.m_value.string->size() - 2; + break; + } + + case value_t::discarded: + { + v.end_position = std::string::npos; + v.start_position = v.end_position; + break; + } + + case value_t::binary: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + { + v.start_position = v.end_position - m_lexer_ref->get_string().size(); + break; + } + + case value_t::object: + case value_t::array: + { + // object and array are handled in start_object() and start_array() handlers + // skip setting the values here. + break; + } + default: // LCOV_EXCL_LINE + // Handle all possible types discretely, default handler should never be reached. + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE + } + } + } +#endif + + /*! + @param[in] v value to add to the JSON value we build during parsing + @param[in] skip_callback whether we should skip calling the callback + function; this is required after start_array() and + start_object() SAX events, because otherwise we would call the + callback function with an empty array or object, respectively. + + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + + @return pair of boolean (whether value should be kept) and pointer (to the + passed value in the ref_stack hierarchy; nullptr if not kept) + */ + template + std::pair handle_value(Value&& v, const bool skip_callback = false) + { + JSON_ASSERT(!keep_stack.empty()); + + // do not handle this value if we know it would be added to a discarded + // container + if (!keep_stack.back()) + { + return {false, nullptr}; + } + + // create value + auto value = BasicJsonType(std::forward(v)); + +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(value); +#endif + + // check callback + const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); + + // do not handle this value if we just learnt it shall be discarded + if (!keep) + { + return {false, nullptr}; + } + + if (ref_stack.empty()) + { + root = std::move(value); + return {true, & root}; + } + + // skip this value if we already decided to skip the parent + // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) + if (!ref_stack.back()) + { + return {false, nullptr}; + } + + // we now only expect arrays and objects + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + // array + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value)); + return {true, & (ref_stack.back()->m_data.m_value.array->back())}; + } + + // object + JSON_ASSERT(ref_stack.back()->is_object()); + // check if we should store an element for the current key + JSON_ASSERT(!key_keep_stack.empty()); + const bool store_element = key_keep_stack.back(); + key_keep_stack.pop_back(); + + if (!store_element) + { + return {false, nullptr}; + } + + JSON_ASSERT(object_element); + *object_element = std::move(value); + return {true, object_element}; + } + + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// stack to manage which values to keep + std::vector keep_stack {}; // NOLINT(readability-redundant-member-init) + /// stack to manage which object keys to keep + std::vector key_keep_stack {}; // NOLINT(readability-redundant-member-init) + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// callback function + const parser_callback_t callback = nullptr; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// a discarded value for the callback + BasicJsonType discarded = BasicJsonType::value_t::discarded; + /// the lexer reference to obtain the current position + lexer_t* m_lexer_ref = nullptr; +}; + +template +class json_sax_acceptor +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + bool null() + { + return true; + } + + bool boolean(bool /*unused*/) + { + return true; + } + + bool number_integer(number_integer_t /*unused*/) + { + return true; + } + + bool number_unsigned(number_unsigned_t /*unused*/) + { + return true; + } + + bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) + { + return true; + } + + bool string(string_t& /*unused*/) + { + return true; + } + + bool binary(binary_t& /*unused*/) + { + return true; + } + + bool start_object(std::size_t /*unused*/ = detail::unknown_size()) + { + return true; + } + + bool key(string_t& /*unused*/) + { + return true; + } + + bool end_object() + { + return true; + } + + bool start_array(std::size_t /*unused*/ = detail::unknown_size()) + { + return true; + } + + bool end_array() + { + return true; + } + + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) + { + return false; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -9207,7 +9841,7 @@ static inline bool little_endianness(int num = 1) noexcept /*! @brief deserialization of CBOR, MessagePack, and UBJSON values */ -template> +template> class binary_reader { using number_integer_t = typename BasicJsonType::number_integer_t; @@ -9314,7 +9948,7 @@ class binary_reader std::int32_t document_size{}; get_number(input_format_t::bson, document_size); - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { return false; } @@ -9470,6 +10104,12 @@ class binary_reader return get_number(input_format_t::bson, value) && sax->number_integer(value); } + case 0x11: // uint64 + { + std::uint64_t value{}; + return get_number(input_format_t::bson, value) && sax->number_unsigned(value); + } + default: // anything else not supported (yet) { std::array cr{{}}; @@ -9536,7 +10176,7 @@ class binary_reader std::int32_t document_size{}; get_number(input_format_t::bson, document_size); - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { return false; } @@ -9796,7 +10436,7 @@ class binary_reader } case 0x9F: // array (indefinite length) - return get_cbor_array(static_cast(-1), tag_handler); + return get_cbor_array(detail::unknown_size(), tag_handler); // map (0x00..0x17 pairs of data items follow) case 0xA0: @@ -9850,7 +10490,7 @@ class binary_reader } case 0xBF: // map (indefinite length) - return get_cbor_object(static_cast(-1), tag_handler); + return get_cbor_object(detail::unknown_size(), tag_handler); case 0xC6: // tagged item case 0xC7: @@ -10238,7 +10878,7 @@ class binary_reader } /*! - @param[in] len the length of the array or static_cast(-1) for an + @param[in] len the length of the array or detail::unknown_size() for an array of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether array creation completed @@ -10251,7 +10891,7 @@ class binary_reader return false; } - if (len != static_cast(-1)) + if (len != detail::unknown_size()) { for (std::size_t i = 0; i < len; ++i) { @@ -10276,7 +10916,7 @@ class binary_reader } /*! - @param[in] len the length of the object or static_cast(-1) for an + @param[in] len the length of the object or detail::unknown_size() for an object of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether object creation completed @@ -10292,7 +10932,7 @@ class binary_reader if (len != 0) { string_t key; - if (len != static_cast(-1)) + if (len != detail::unknown_size()) { for (std::size_t i = 0; i < len; ++i) { @@ -11455,6 +12095,16 @@ class binary_reader case 'Z': // null return sax->null(); + case 'B': // byte + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint8_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + case 'U': { std::uint8_t number{}; @@ -11655,7 +12305,7 @@ class binary_reader return false; } - if (size_and_type.second == 'C') + if (size_and_type.second == 'C' || size_and_type.second == 'B') { size_and_type.second = 'U'; } @@ -11677,6 +12327,13 @@ class binary_reader return (sax->end_array() && sax->end_object()); } + // If BJData type marker is 'B' decode as binary + if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B') + { + binary_t result; + return get_binary(input_format, size_and_type.first, result) && sax->binary(result); + } + if (size_and_type.first != npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) @@ -11710,7 +12367,7 @@ class binary_reader } else { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { return false; } @@ -11788,7 +12445,7 @@ class binary_reader } else { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { return false; } @@ -11899,6 +12556,29 @@ class binary_reader return current = ia.get_character(); } + /*! + @brief get_to read into a primitive type + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns false instead + + @return bool, whether the read was successful + */ + template + bool get_to(T& dest, const input_format_t format, const char* context) + { + auto new_chars_read = ia.get_elements(&dest); + chars_read += new_chars_read; + if (JSON_HEDLEY_UNLIKELY(new_chars_read < sizeof(T))) + { + // in case of failure, advance position by 1 to report failing location + ++chars_read; + sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context), nullptr)); + return false; + } + return true; + } + /*! @return character read from the input after ignoring all 'N' entries */ @@ -11913,6 +12593,28 @@ class binary_reader return current; } + template + static void byte_swap(NumberType& number) + { + constexpr std::size_t sz = sizeof(number); +#ifdef __cpp_lib_byteswap + if constexpr (sz == 1) + { + return; + } + if constexpr(std::is_integral_v) + { + number = std::byteswap(number); + return; + } +#endif + auto* ptr = reinterpret_cast(&number); + for (std::size_t i = 0; i < sz / 2; ++i) + { + std::swap(ptr[i], ptr[sz - i - 1]); + } + } + /* @brief read a number from the input @@ -11931,29 +12633,16 @@ class binary_reader template bool get_number(const input_format_t format, NumberType& result) { - // step 1: read input into array with system's byte order - std::array vec{}; - for (std::size_t i = 0; i < sizeof(NumberType); ++i) + // read in the original format + + if (JSON_HEDLEY_UNLIKELY(!get_to(result, format, "number"))) { - get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number"))) - { - return false; - } - - // reverse byte order prior to conversion if necessary - if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata)) - { - vec[sizeof(NumberType) - i - 1] = static_cast(current); - } - else - { - vec[i] = static_cast(current); // LCOV_EXCL_LINE - } + return false; + } + if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata)) + { + byte_swap(result); } - - // step 2: convert array into number of type T and return - std::memcpy(&result, vec.data(), sizeof(NumberType)); return true; } @@ -12092,7 +12781,7 @@ class binary_reader } private: - static JSON_INLINE_VARIABLE constexpr std::size_t npos = static_cast(-1); + static JSON_INLINE_VARIABLE constexpr std::size_t npos = detail::unknown_size(); /// input adapter InputAdapterType ia; @@ -12118,6 +12807,7 @@ class binary_reader #define JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ \ make_array( \ + bjd_type{'B', "byte"}, \ bjd_type{'C', "char"}, \ bjd_type{'D', "double"}, \ bjd_type{'I', "int16"}, \ @@ -12160,10 +12850,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12237,10 +12927,10 @@ class parser public: /// a parser reading from an input adapter explicit parser(InputAdapterType&& adapter, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions_ = true, const bool skip_comments = false) - : callback(cb) + : callback(std::move(cb)) , m_lexer(std::move(adapter), skip_comments) , allow_exceptions(allow_exceptions_) { @@ -12262,7 +12952,7 @@ class parser { if (callback) { - json_sax_dom_callback_parser sdp(result, callback, allow_exceptions); + json_sax_dom_callback_parser sdp(result, callback, allow_exceptions, &m_lexer); sax_parse_internal(&sdp); // in strict mode, input must be completely read @@ -12290,7 +12980,7 @@ class parser } else { - json_sax_dom_parser sdp(result, allow_exceptions); + json_sax_dom_parser sdp(result, allow_exceptions, &m_lexer); sax_parse_internal(&sdp); // in strict mode, input must be completely read @@ -12362,7 +13052,7 @@ class parser { case token_type::begin_object: { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { return false; } @@ -12407,7 +13097,7 @@ class parser case token_type::begin_array: { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { return false; } @@ -12689,10 +13379,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12702,10 +13392,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12861,10 +13551,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -13331,7 +14021,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > bool operator==(const IterImpl& other) const @@ -13342,7 +14032,11 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); } - JSON_ASSERT(m_object != nullptr); + // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 + if (m_object == nullptr) + { + return true; + } switch (m_object->m_data.m_type) { @@ -13367,7 +14061,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: not equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > bool operator!=(const IterImpl& other) const @@ -13377,7 +14071,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: smaller - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ bool operator<(const iter_impl& other) const { @@ -13387,7 +14081,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); } - JSON_ASSERT(m_object != nullptr); + // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 + if (m_object == nullptr) + { + // the iterators are both value-initialized and are to be considered equal, but this function checks for smaller, so we return false + return false; + } switch (m_object->m_data.m_type) { @@ -13412,7 +14111,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: less than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ bool operator<=(const iter_impl& other) const { @@ -13421,7 +14120,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: greater than - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ bool operator>(const iter_impl& other) const { @@ -13430,7 +14129,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: greater than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) The iterator is initialized; i.e. `m_object != nullptr`, or (2) both iterators are value-initialized. */ bool operator>=(const iter_impl& other) const { @@ -13623,10 +14322,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -13758,10 +14457,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -13800,10 +14499,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -14033,7 +14732,7 @@ class json_pointer } const char* p = s.c_str(); - char* p_end = nullptr; + char* p_end = nullptr; // NOLINT(misc-const-correctness) errno = 0; // strtoull doesn't reset errno const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int) if (p == p_end // invalid input or empty string @@ -14555,7 +15254,7 @@ class json_pointer // iterate array and use index as reference string for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i) { - flatten(detail::concat(reference_string, '/', std::to_string(i)), + flatten(detail::concat(reference_string, '/', std::to_string(i)), value.m_data.m_value.array->operator[](i), result); } } @@ -14574,7 +15273,7 @@ class json_pointer // iterate object and use keys as reference string for (const auto& element : *value.m_data.m_value.object) { - flatten(detail::concat(reference_string, '/', detail::escape(element.first)), element.second, result); + flatten(detail::concat(reference_string, '/', detail::escape(element.first)), element.second, result); } } break; @@ -14795,10 +15494,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -14880,6 +15579,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + // #include // #include @@ -14887,10 +15588,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -14913,10 +15614,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -15067,6 +15768,13 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { +/// how to encode BJData +enum class bjdata_version_t +{ + draft2, + draft3, +}; + /////////////////// // binary writer // /////////////////// @@ -15651,7 +16359,7 @@ class binary_writer case value_t::binary: { // step 0: determine if the binary type has a set subtype to - // determine whether or not to use the ext or fixext types + // determine whether to use the ext or fixext types const bool use_ext = j.m_data.m_value.binary->has_subtype(); // step 1: write control byte and the byte string length @@ -15774,11 +16482,14 @@ class binary_writer @param[in] use_type whether to use '$' prefixes (optimized format) @param[in] add_prefix whether prefixes need to be used for this value @param[in] use_bjdata whether write in BJData format, default is false + @param[in] bjdata_version which BJData version to use, default is draft2 */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true, - const bool use_bjdata = false) + const bool use_bjdata = false, const bjdata_version_t bjdata_version = bjdata_version_t::draft2) { + const bool bjdata_draft3 = use_bjdata && bjdata_version == bjdata_version_t::draft3; + switch (j.type()) { case value_t::null: @@ -15868,7 +16579,7 @@ class binary_writer for (const auto& el : *j.m_data.m_value.array) { - write_ubjson(el, use_count, use_type, prefix_required, use_bjdata); + write_ubjson(el, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } if (!use_count) @@ -15886,11 +16597,11 @@ class binary_writer oa->write_character(to_char_type('[')); } - if (use_type && !j.m_data.m_value.binary->empty()) + if (use_type && (bjdata_draft3 || !j.m_data.m_value.binary->empty())) { JSON_ASSERT(use_count); oa->write_character(to_char_type('$')); - oa->write_character('U'); + oa->write_character(bjdata_draft3 ? 'B' : 'U'); } if (use_count) @@ -15909,7 +16620,7 @@ class binary_writer { for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i) { - oa->write_character(to_char_type('U')); + oa->write_character(to_char_type(bjdata_draft3 ? 'B' : 'U')); oa->write_character(j.m_data.m_value.binary->data()[i]); } } @@ -15926,7 +16637,7 @@ class binary_writer { if (use_bjdata && j.m_data.m_value.object->size() == 3 && j.m_data.m_value.object->find("_ArrayType_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArraySize_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArrayData_") != j.m_data.m_value.object->end()) { - if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) + if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type, bjdata_version)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) { break; } @@ -15970,7 +16681,7 @@ class binary_writer oa->write_characters( reinterpret_cast(el.first.c_str()), el.first.size()); - write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata); + write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } if (!use_count) @@ -16126,7 +16837,8 @@ class binary_writer } else { - JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BSON as it does not fit int64"), &j)); + write_bson_entry_header(name, 0x11 /* uint64 */); + write_number(static_cast(j.m_data.m_value.number_unsigned), true); } } @@ -16654,10 +17366,11 @@ class binary_writer /*! @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid */ - bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type) + bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type, const bjdata_version_t bjdata_version) { std::map bjdtype = {{"uint8", 'U'}, {"int8", 'i'}, {"uint16", 'u'}, {"int16", 'I'}, - {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, {"char", 'C'} + {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, + {"char", 'C'}, {"byte", 'B'} }; string_t key = "_ArrayType_"; @@ -16687,10 +17400,10 @@ class binary_writer oa->write_character('#'); key = "_ArraySize_"; - write_ubjson(value.at(key), use_count, use_type, true, true); + write_ubjson(value.at(key), use_count, use_type, true, true, bjdata_version); key = "_ArrayData_"; - if (dtype == 'U' || dtype == 'C') + if (dtype == 'U' || dtype == 'C' || dtype == 'B') { for (const auto& el : value.at(key)) { @@ -16881,11 +17594,11 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2008-2009 Björn Hoehrmann -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2008 - 2009 Björn Hoehrmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -16906,11 +17619,11 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2009 Florian Loitsch -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -17146,10 +17859,10 @@ boundaries compute_boundaries(FloatType value) // v- m- v m+ v+ const bool lower_boundary_is_closer = F == 0 && E > 1; - const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_plus = diyfp((2 * v.f) + 1, v.e - 1); const diyfp m_minus = lower_boundary_is_closer - ? diyfp(4 * v.f - 1, v.e - 2) // (B) - : diyfp(2 * v.f - 1, v.e - 1); // (A) + ? diyfp((4 * v.f) - 1, v.e - 2) // (B) + : diyfp((2 * v.f) - 1, v.e - 1); // (A) // Determine the normalized w+ = m+. const diyfp w_plus = diyfp::normalize(m_plus); @@ -17379,7 +18092,7 @@ inline cached_power get_cached_power_for_binary_exponent(int e) JSON_ASSERT(e >= -1500); JSON_ASSERT(e <= 1500); const int f = kAlpha - e - 1; - const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); + const int k = ((f * 78913) / (1 << 18)) + static_cast(f > 0); const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; JSON_ASSERT(index >= 0); @@ -17857,15 +18570,15 @@ inline char* append_exponent(char* buf, int e) } else if (k < 100) { - *buf++ = static_cast('0' + k / 10); + *buf++ = static_cast('0' + (k / 10)); k %= 10; *buf++ = static_cast('0' + k); } else { - *buf++ = static_cast('0' + k / 100); + *buf++ = static_cast('0' + (k / 100)); k %= 100; - *buf++ = static_cast('0' + k / 10); + *buf++ = static_cast('0' + (k / 10)); k %= 10; *buf++ = static_cast('0' + k); } @@ -18651,7 +19364,7 @@ class serializer @param[in] x unsigned integer number to count its digits @return number of decimal digits */ - inline unsigned int count_digits(number_unsigned_t x) noexcept + unsigned int count_digits(number_unsigned_t x) noexcept { unsigned int n_digits = 1; for (;;) @@ -18934,7 +19647,7 @@ class serializer ? (byte & 0x3fu) | (codep << 6u) : (0xFFu >> type) & (byte); - const std::size_t index = 256u + static_cast(state) * 16u + static_cast(type); + const std::size_t index = 256u + (static_cast(state) * 16u) + static_cast(type); JSON_ASSERT(index < utf8d.size()); state = utf8d[index]; return state; @@ -18960,7 +19673,7 @@ class serializer * absolute values of INT_MIN and INT_MAX are usually not the same. See * #1708 for details. */ - inline number_unsigned_t remove_sign(number_integer_t x) noexcept + number_unsigned_t remove_sign(number_integer_t x) noexcept { JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); // NOLINT(misc-redundant-expression) return static_cast(-(x + 1)) + 1; @@ -19002,10 +19715,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -19030,7 +19743,7 @@ NLOHMANN_JSON_NAMESPACE_BEGIN /// for use within nlohmann::basic_json template , class Allocator = std::allocator>> - struct ordered_map : std::vector, Allocator> + struct ordered_map : std::vector, Allocator> { using key_type = Key; using mapped_type = T; @@ -19345,7 +20058,7 @@ template , template using require_input_iter = typename std::enable_if::iterator_category, - std::input_iterator_tag>::value>::type; + std::input_iterator_tag>::value>::type; template> void insert(InputIt first, InputIt last) @@ -19416,9 +20129,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec friend class ::nlohmann::detail::binary_writer; template friend class ::nlohmann::detail::binary_reader; - template + template friend class ::nlohmann::detail::json_sax_dom_parser; - template + template friend class ::nlohmann::detail::json_sax_dom_callback_parser; friend class ::nlohmann::detail::exception; @@ -19439,7 +20152,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec ) { return ::nlohmann::detail::parser(std::move(adapter), - std::move(cb), allow_exceptions, ignore_comments); + std::move(cb), allow_exceptions, ignore_comments); } private: @@ -19472,6 +20185,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using error_handler_t = detail::error_handler_t; /// how to treat CBOR tags using cbor_tag_handler_t = detail::cbor_tag_handler_t; + /// how to encode BJData + using bjdata_version_t = detail::bjdata_version_t; /// helper type for initializer lists of basic_json values using initializer_list_t = std::initializer_list>; @@ -19551,7 +20266,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { basic_json result; - result["copyright"] = "(C) 2013-2023 Niels Lohmann"; + result["copyright"] = "(C) 2013-2025 Niels Lohmann"; result["name"] = "JSON for Modern C++"; result["url"] = "https://github.com/nlohmann/json"; result["version"]["string"] = @@ -19816,7 +20531,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec object = nullptr; // silence warning, see #821 if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.11.3", nullptr)); // LCOV_EXCL_LINE + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE } break; } @@ -20052,10 +20767,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return it; } - reference set_parent(reference j, std::size_t old_capacity = static_cast(-1)) + reference set_parent(reference j, std::size_t old_capacity = detail::unknown_size()) { #if JSON_DIAGNOSTICS - if (old_capacity != static_cast(-1)) + if (old_capacity != detail::unknown_size()) { // see https://github.com/nlohmann/json/issues/2838 JSON_ASSERT(type() == value_t::array); @@ -20135,8 +20850,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::enable_if_t < !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape) - JSONSerializer::to_json(std::declval(), - std::forward(val)))) + JSONSerializer::to_json(std::declval(), + std::forward(val)))) { JSONSerializer::to_json(*this, std::forward(val)); set_parents(); @@ -20149,6 +20864,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::enable_if_t < detail::is_basic_json::value&& !std::is_same::value, int > = 0 > basic_json(const BasicJsonType& val) +#if JSON_DIAGNOSTIC_POSITIONS + : start_position(val.start_pos()), + end_position(val.end_pos()) +#endif { using other_boolean_t = typename BasicJsonType::boolean_t; using other_number_float_t = typename BasicJsonType::number_float_t; @@ -20195,6 +20914,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } JSON_ASSERT(m_data.m_type == val.type()); + set_parents(); assert_invariant(); } @@ -20331,7 +21051,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < class InputIT, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > - basic_json(InputIT first, InputIT last) + basic_json(InputIT first, InputIT last) // NOLINT(performance-unnecessary-value-param) { JSON_ASSERT(first.m_object != nullptr); JSON_ASSERT(last.m_object != nullptr); @@ -20446,6 +21166,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(const basic_json& other) : json_base_class_t(other) +#if JSON_DIAGNOSTIC_POSITIONS + , start_position(other.start_position) + , end_position(other.end_position) +#endif { m_data.m_type = other.m_data.m_type; // check of passed value is valid @@ -20515,15 +21239,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(basic_json&& other) noexcept : json_base_class_t(std::forward(other)), - m_data(std::move(other.m_data)) + m_data(std::move(other.m_data)) // cppcheck-suppress[accessForwarded] TODO check +#if JSON_DIAGNOSTIC_POSITIONS + , start_position(other.start_position) // cppcheck-suppress[accessForwarded] TODO check + , end_position(other.end_position) // cppcheck-suppress[accessForwarded] TODO check +#endif { // check that passed value is valid - other.assert_invariant(false); + other.assert_invariant(false); // cppcheck-suppress[accessForwarded] // invalidate payload other.m_data.m_type = value_t::null; other.m_data.m_value = {}; +#if JSON_DIAGNOSTIC_POSITIONS + other.start_position = std::string::npos; + other.end_position = std::string::npos; +#endif + set_parents(); assert_invariant(); } @@ -20544,6 +21277,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using std::swap; swap(m_data.m_type, other.m_data.m_type); swap(m_data.m_value, other.m_data.m_value); + +#if JSON_DIAGNOSTIC_POSITIONS + swap(start_position, other.start_position); + swap(end_position, other.end_position); +#endif + json_base_class_t::operator=(std::move(other)); set_parents(); @@ -20765,13 +21504,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// get a pointer to the value (integer number) number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept { - return is_number_integer() ? &m_data.m_value.number_integer : nullptr; + return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr; } /// get a pointer to the value (integer number) constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept { - return is_number_integer() ? &m_data.m_value.number_integer : nullptr; + return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr; } /// get a pointer to the value (unsigned number) @@ -20906,7 +21645,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::has_from_json::value, int > = 0 > ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept( - JSONSerializer::from_json(std::declval(), std::declval()))) + JSONSerializer::from_json(std::declval(), std::declval()))) { auto ret = ValueType(); JSONSerializer::from_json(*this, ret); @@ -20948,7 +21687,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::has_non_default_from_json::value, int > = 0 > ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept( - JSONSerializer::from_json(std::declval()))) + JSONSerializer::from_json(std::declval()))) { return JSONSerializer::from_json(*this); } @@ -21098,7 +21837,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::has_from_json::value, int > = 0 > ValueType & get_to(ValueType& v) const noexcept(noexcept( - JSONSerializer::from_json(std::declval(), v))) + JSONSerializer::from_json(std::declval(), v))) { JSONSerializer::from_json(*this, v); return v; @@ -21250,7 +21989,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { // create better exception explanation JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); - } + } // cppcheck-suppress[missingReturn] } else { @@ -21273,7 +22012,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { // create better exception explanation JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); - } + } // cppcheck-suppress[missingReturn] } else { @@ -21418,7 +22157,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief access specified object element /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ - reference operator[](typename object_t::key_type key) + reference operator[](typename object_t::key_type key) // NOLINT(performance-unnecessary-value-param) { // implicitly convert null value to an empty object if (is_null()) @@ -21728,7 +22467,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < class IteratorType, detail::enable_if_t < std::is_same::value || std::is_same::value, int > = 0 > - IteratorType erase(IteratorType pos) + IteratorType erase(IteratorType pos) // NOLINT(performance-unnecessary-value-param) { // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) @@ -21798,7 +22537,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < class IteratorType, detail::enable_if_t < std::is_same::value || std::is_same::value, int > = 0 > - IteratorType erase(IteratorType first, IteratorType last) + IteratorType erase(IteratorType first, IteratorType last) // NOLINT(performance-unnecessary-value-param) { // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) @@ -22565,7 +23304,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @note: This uses std::distance to support GCC 4.8, /// see https://github.com/nlohmann/json/pull/1257 template - iterator insert_iterator(const_iterator pos, Args&& ... args) + iterator insert_iterator(const_iterator pos, Args&& ... args) // NOLINT(performance-unnecessary-value-param) { iterator result(this); JSON_ASSERT(m_data.m_value.array != nullptr); @@ -22584,7 +23323,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, const basic_json& val) + iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) @@ -22604,14 +23343,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, basic_json&& val) + iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param) { return insert(pos, val); } /// @brief inserts copies of element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) @@ -22631,7 +23370,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts range of elements into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, const_iterator first, const_iterator last) + iterator insert(const_iterator pos, const_iterator first, const_iterator last) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) @@ -22662,7 +23401,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts elements from initializer list into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, initializer_list_t ilist) + iterator insert(const_iterator pos, initializer_list_t ilist) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) @@ -22682,7 +23421,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts range of elements into object /// @sa https://json.nlohmann.me/api/basic_json/insert/ - void insert(const_iterator first, const_iterator last) + void insert(const_iterator first, const_iterator last) // NOLINT(performance-unnecessary-value-param) { // insert only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) @@ -22703,6 +23442,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + set_parents(); } /// @brief updates a JSON object from another object, overwriting existing keys @@ -22714,7 +23454,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief updates a JSON object from another object, overwriting existing keys /// @sa https://json.nlohmann.me/api/basic_json/update/ - void update(const_iterator first, const_iterator last, bool merge_objects = false) + void update(const_iterator first, const_iterator last, bool merge_objects = false) // NOLINT(performance-unnecessary-value-param) { // implicitly convert null value to an empty object if (is_null()) @@ -23315,12 +24055,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(InputType&& i, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; - parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); + parser(detail::input_adapter(std::forward(i)), std::move(cb), allow_exceptions, ignore_comments).parse(true, result); // cppcheck-suppress[accessMoved,accessForwarded] return result; } @@ -23330,24 +24070,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(IteratorType first, IteratorType last, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; - parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); + parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments).parse(true, result); // cppcheck-suppress[accessMoved] return result; } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) static basic_json parse(detail::span_input_adapter&& i, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; - parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); + parser(i.get(), std::move(cb), allow_exceptions, ignore_comments).parse(true, result); // cppcheck-suppress[accessMoved] return result; } @@ -23526,6 +24266,23 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json* m_parent = nullptr; #endif +#if JSON_DIAGNOSTIC_POSITIONS + /// the start position of the value + std::size_t start_position = std::string::npos; + /// the end position of the value + std::size_t end_position = std::string::npos; + public: + constexpr std::size_t start_pos() const noexcept + { + return start_position; + } + + constexpr std::size_t end_pos() const noexcept + { + return end_position; + } +#endif + ////////////////////////////////////////// // binary serialization/deserialization // ////////////////////////////////////////// @@ -23611,27 +24368,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static std::vector to_bjdata(const basic_json& j, const bool use_size = false, - const bool use_type = false) + const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { std::vector result; - to_bjdata(j, result, use_size, use_type); + to_bjdata(j, result, use_size, use_type, version); return result; } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BSON serialization of a given JSON value @@ -23667,9 +24427,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23683,9 +24443,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23708,10 +24468,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23724,9 +24484,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23739,9 +24499,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23762,10 +24522,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23778,9 +24538,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23793,9 +24553,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23816,10 +24576,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23832,9 +24592,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23847,9 +24607,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23862,9 +24622,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23877,9 +24637,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23900,10 +24660,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } /// @} @@ -24004,7 +24764,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // the valid JSON Patch operations enum class patch_operations {add, remove, replace, move, copy, test, invalid}; - const auto get_op = [](const std::string & op) + const auto get_op = [](const string_t& op) { if (op == "add") { @@ -24035,7 +24795,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec }; // wrapper for "add" operation; add value at ptr - const auto operation_add = [&result](json_pointer & ptr, basic_json val) + const auto operation_add = [&result](json_pointer & ptr, const basic_json & val) { // adding to the root of the target document means replacing it if (ptr.empty()) @@ -24141,15 +24901,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec for (const auto& val : json_patch) { // wrapper to get a value for an operation - const auto get_value = [&val](const std::string & op, - const std::string & member, + const auto get_value = [&val](const string_t& op, + const string_t& member, bool string_type) -> basic_json & { // find value auto it = val.m_data.m_value.object->find(member); // context-sensitive error message - const auto error_msg = (op == "op") ? "operation" : detail::concat("operation '", op, '\''); + const auto error_msg = (op == "op") ? "operation" : detail::concat("operation '", op, '\''); // NOLINT(bugprone-unused-local-non-trivial-variable) // check if desired value is present if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end())) @@ -24176,8 +24936,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // collect mandatory members - const auto op = get_value("op", "op", true).template get(); - const auto path = get_value(op, "path", true).template get(); + const auto op = get_value("op", "op", true).template get(); + const auto path = get_value(op, "path", true).template get(); json_pointer ptr(path); switch (get_op(op)) @@ -24203,7 +24963,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case patch_operations::move: { - const auto from_path = get_value("move", "from", true).template get(); + const auto from_path = get_value("move", "from", true).template get(); json_pointer from_ptr(from_path); // the "from" location must exist - use at() @@ -24220,7 +24980,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case patch_operations::copy: { - const auto from_path = get_value("copy", "from", true).template get(); + const auto from_path = get_value("copy", "from", true).template get(); const json_pointer from_ptr(from_path); // the "from" location must exist - use at() @@ -24280,7 +25040,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/diff/ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json diff(const basic_json& source, const basic_json& target, - const std::string& path = "") + const string_t& path = "") { // the patch basic_json result(value_t::array); @@ -24310,7 +25070,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec while (i < source.size() && i < target.size()) { // recursive call to compare array values at index i - auto temp_diff = diff(source[i], target[i], detail::concat(path, '/', std::to_string(i))); + auto temp_diff = diff(source[i], target[i], detail::concat(path, '/', detail::to_string(i))); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); ++i; } @@ -24327,7 +25087,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec result.insert(result.begin() + end_index, object( { {"op", "remove"}, - {"path", detail::concat(path, '/', std::to_string(i))} + {"path", detail::concat(path, '/', detail::to_string(i))} })); ++i; } @@ -24338,7 +25098,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec result.push_back( { {"op", "add"}, - {"path", detail::concat(path, "/-")}, + {"path", detail::concat(path, "/-")}, {"value", target[i]} }); ++i; @@ -24353,7 +25113,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec for (auto it = source.cbegin(); it != source.cend(); ++it) { // escape the key name to be used in a JSON patch - const auto path_key = detail::concat(path, '/', detail::escape(it.key())); + const auto path_key = detail::concat(path, '/', detail::escape(it.key())); if (target.find(it.key()) != target.end()) { @@ -24377,7 +25137,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (source.find(it.key()) == source.end()) { // found a key that is not in this -> add it - const auto path_key = detail::concat(path, '/', detail::escape(it.key())); + const auto path_key = detail::concat(path, '/', detail::escape(it.key())); result.push_back( { {"op", "add"}, {"path", path_key}, @@ -24558,10 +25318,10 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -24592,6 +25352,7 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC #undef JSON_HAS_CPP_14 #undef JSON_HAS_CPP_17 #undef JSON_HAS_CPP_20 + #undef JSON_HAS_CPP_23 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_THREE_WAY_COMPARISON @@ -24603,10 +25364,10 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT diff --git a/Telegram/SourceFiles/ayu/libs/sqlite/sqlite3.c b/Telegram/SourceFiles/ayu/libs/sqlite/sqlite3.c index 58dc0e9208..0b071b2b6c 100644 --- a/Telegram/SourceFiles/ayu/libs/sqlite/sqlite3.c +++ b/Telegram/SourceFiles/ayu/libs/sqlite/sqlite3.c @@ -1,6 +1,6 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.50.0. By combining all the individual C code files into this +** version 3.50.2. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -18,7 +18,7 @@ ** separate file. This file contains only code for the core SQLite library. ** ** The content in this amalgamation comes from Fossil check-in -** dfc790f998f450d9c35e3ba1c8c89c17466c with changes in files: +** 2af157d77fb1304a74176eaee7fbc7c7e932 with changes in files: ** ** */ @@ -465,9 +465,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.50.0" -#define SQLITE_VERSION_NUMBER 3050000 -#define SQLITE_SOURCE_ID "2025-05-29 14:26:00 dfc790f998f450d9c35e3ba1c8c89c17466cb559f87b0239e4aab9d34e28f742" +#define SQLITE_VERSION "3.50.2" +#define SQLITE_VERSION_NUMBER 3050002 +#define SQLITE_SOURCE_ID "2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -4398,7 +4398,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*); ** ** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of ** database filename D with corresponding journal file J and WAL file W and -** with N URI parameters key/values pairs in the array P. The result from +** an array P of N URI Key/Value pairs. The result from ** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that ** is safe to pass to routines like: **
    @@ -5079,7 +5079,7 @@ typedef struct sqlite3_context sqlite3_context; ** METHOD: sqlite3_stmt ** ** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants, -** literals may be replaced by a [parameter] that matches one of following +** literals may be replaced by a [parameter] that matches one of the following ** templates: ** **
      @@ -5124,7 +5124,7 @@ typedef struct sqlite3_context sqlite3_context; ** ** [[byte-order determination rules]] ^The byte-order of ** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF) -** found in first character, which is removed, or in the absence of a BOM +** found in the first character, which is removed, or in the absence of a BOM ** the byte order is the native byte order of the host ** machine for sqlite3_bind_text16() or the byte order specified in ** the 6th parameter for sqlite3_bind_text64().)^ @@ -5144,7 +5144,7 @@ typedef struct sqlite3_context sqlite3_context; ** or sqlite3_bind_text16() or sqlite3_bind_text64() then ** that parameter must be the byte offset ** where the NUL terminator would occur assuming the string were NUL -** terminated. If any NUL characters occurs at byte offsets less than +** terminated. If any NUL characters occur at byte offsets less than ** the value of the fourth parameter then the resulting string value will ** contain embedded NULs. The result of expressions involving strings ** with embedded NULs is undefined. @@ -5356,7 +5356,7 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); ** METHOD: sqlite3_stmt ** ** ^These routines provide a means to determine the database, table, and -** table column that is the origin of a particular result column in +** table column that is the origin of a particular result column in a ** [SELECT] statement. ** ^The name of the database or table or column can be returned as ** either a UTF-8 or UTF-16 string. ^The _database_ routines return @@ -5925,8 +5925,8 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** ** For best security, the [SQLITE_DIRECTONLY] flag is recommended for ** all application-defined SQL functions that do not need to be -** used inside of triggers, view, CHECK constraints, or other elements of -** the database schema. This flags is especially recommended for SQL +** used inside of triggers, views, CHECK constraints, or other elements of +** the database schema. This flag is especially recommended for SQL ** functions that have side effects or reveal internal application state. ** Without this flag, an attacker might be able to modify the schema of ** a database file to include invocations of the function with parameters @@ -5957,7 +5957,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** [user-defined window functions|available here]. ** ** ^(If the final parameter to sqlite3_create_function_v2() or -** sqlite3_create_window_function() is not NULL, then it is destructor for +** sqlite3_create_window_function() is not NULL, then it is the destructor for ** the application data pointer. The destructor is invoked when the function ** is deleted, either by being overloaded or when the database connection ** closes.)^ ^The destructor is also invoked if the call to @@ -6357,7 +6357,7 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); ** METHOD: sqlite3_value ** ** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value] -** object D and returns a pointer to that copy. ^The [sqlite3_value] returned +** object V and returns a pointer to that copy. ^The [sqlite3_value] returned ** is a [protected sqlite3_value] object even if the input is not. ** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a ** memory allocation fails. ^If V is a [pointer value], then the result @@ -6395,7 +6395,7 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*); ** allocation error occurs. ** ** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is -** determined by the N parameter on first successful call. Changing the +** determined by the N parameter on the first successful call. Changing the ** value of N in any subsequent call to sqlite3_aggregate_context() within ** the same aggregate function instance will not resize the memory ** allocation.)^ Within the xFinal callback, it is customary to set @@ -6557,7 +6557,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi ** ** Security Warning: These interfaces should not be exposed in scripting ** languages or in other circumstances where it might be possible for an -** an attacker to invoke them. Any agent that can invoke these interfaces +** attacker to invoke them. Any agent that can invoke these interfaces ** can probably also take control of the process. ** ** Database connection client data is only available for SQLite @@ -6671,7 +6671,7 @@ typedef void (*sqlite3_destructor_type)(void*); ** pointed to by the 2nd parameter are taken as the application-defined ** function result. If the 3rd parameter is non-negative, then it ** must be the byte offset into the string where the NUL terminator would -** appear if the string where NUL terminated. If any NUL characters occur +** appear if the string were NUL terminated. If any NUL characters occur ** in the string at a byte offset that is less than the value of the 3rd ** parameter, then the resulting string will contain embedded NULs and the ** result of expressions operating on strings with embedded NULs is undefined. @@ -6729,7 +6729,7 @@ typedef void (*sqlite3_destructor_type)(void*); ** string and preferably a string literal. The sqlite3_result_pointer() ** routine is part of the [pointer passing interface] added for SQLite 3.20.0. ** -** If these routines are called from within the different thread +** If these routines are called from within a different thread ** than the one containing the application-defined function that received ** the [sqlite3_context] pointer, the results are undefined. */ @@ -7135,7 +7135,7 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); ** METHOD: sqlite3 ** ** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name -** for the N-th database on database connection D, or a NULL pointer of N is +** for the N-th database on database connection D, or a NULL pointer if N is ** out of range. An N value of 0 means the main database file. An N of 1 is ** the "temp" schema. Larger values of N correspond to various ATTACH-ed ** databases. @@ -7230,7 +7230,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema); **
      The SQLITE_TXN_READ state means that the database is currently ** in a read transaction. Content has been read from the database file ** but nothing in the database file has changed. The transaction state -** will advanced to SQLITE_TXN_WRITE if any changes occur and there are +** will be advanced to SQLITE_TXN_WRITE if any changes occur and there are ** no other conflicting concurrent write transactions. The transaction ** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or ** [COMMIT].
      @@ -7239,7 +7239,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema); **
      The SQLITE_TXN_WRITE state means that the database is currently ** in a write transaction. Content has been written to the database file ** but has not yet committed. The transaction state will change to -** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].
      +** SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT]. */ #define SQLITE_TXN_NONE 0 #define SQLITE_TXN_READ 1 @@ -7520,7 +7520,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); ** CAPI3REF: Impose A Limit On Heap Size ** ** These interfaces impose limits on the amount of heap memory that will be -** by all database connections within a single process. +** used by all database connections within a single process. ** ** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the ** soft limit on the amount of heap memory that may be allocated by SQLite. @@ -7578,7 +7578,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); **
    )^ ** ** The circumstances under which SQLite will enforce the heap limits may -** changes in future releases of SQLite. +** change in future releases of SQLite. */ SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N); @@ -7693,8 +7693,8 @@ SQLITE_API int sqlite3_table_column_metadata( ** ^The entry point is zProc. ** ^(zProc may be 0, in which case SQLite will try to come up with an ** entry point name on its own. It first tries "sqlite3_extension_init". -** If that does not work, it constructs a name "sqlite3_X_init" where the -** X is consists of the lower-case equivalent of all ASCII alphabetic +** If that does not work, it constructs a name "sqlite3_X_init" where +** X consists of the lower-case equivalent of all ASCII alphabetic ** characters in the filename from the last "/" to the first following ** "." and omitting any initial "lib".)^ ** ^The sqlite3_load_extension() interface returns @@ -7765,7 +7765,7 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); ** ^(Even though the function prototype shows that xEntryPoint() takes ** no arguments and returns void, SQLite invokes xEntryPoint() with three ** arguments and expects an integer result as if the signature of the -** entry point where as follows: +** entry point were as follows: ** **
     **    int xEntryPoint(
    @@ -7929,7 +7929,7 @@ struct sqlite3_module {
     ** virtual table and might not be checked again by the byte code.)^ ^(The
     ** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
     ** is left in its default setting of false, the constraint will always be
    -** checked separately in byte code.  If the omit flag is change to true, then
    +** checked separately in byte code.  If the omit flag is changed to true, then
     ** the constraint may or may not be checked in byte code.  In other words,
     ** when the omit flag is true there is no guarantee that the constraint will
     ** not be checked again using byte code.)^
    @@ -7955,7 +7955,7 @@ struct sqlite3_module {
     ** The xBestIndex method may optionally populate the idxFlags field with a
     ** mask of SQLITE_INDEX_SCAN_* flags. One such flag is
     ** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN]
    -** output to show the idxNum has hex instead of as decimal.  Another flag is
    +** output to show the idxNum as hex instead of as decimal.  Another flag is
     ** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will
     ** return at most one row.
     **
    @@ -8096,7 +8096,7 @@ struct sqlite3_index_info {
     ** the implementation of the [virtual table module].   ^The fourth
     ** parameter is an arbitrary client data pointer that is passed through
     ** into the [xCreate] and [xConnect] methods of the virtual table module
    -** when a new virtual table is be being created or reinitialized.
    +** when a new virtual table is being created or reinitialized.
     **
     ** ^The sqlite3_create_module_v2() interface has a fifth parameter which
     ** is a pointer to a destructor for the pClientData.  ^SQLite will
    @@ -8261,7 +8261,7 @@ typedef struct sqlite3_blob sqlite3_blob;
     ** in *ppBlob. Otherwise an [error code] is returned and, unless the error
     ** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
     ** the API is not misused, it is always safe to call [sqlite3_blob_close()]
    -** on *ppBlob after this function it returns.
    +** on *ppBlob after this function returns.
     **
     ** This function fails with SQLITE_ERROR if any of the following are true:
     ** 
      @@ -8381,7 +8381,7 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *); ** ** ^Returns the size in bytes of the BLOB accessible via the ** successfully opened [BLOB handle] in its only argument. ^The -** incremental blob I/O routines can only read or overwriting existing +** incremental blob I/O routines can only read or overwrite existing ** blob content; they cannot change the size of a blob. ** ** This routine only works on a [BLOB handle] which has been created @@ -8531,7 +8531,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** ^The sqlite3_mutex_alloc() routine allocates a new ** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc() ** routine returns NULL if it is unable to allocate the requested -** mutex. The argument to sqlite3_mutex_alloc() must one of these +** mutex. The argument to sqlite3_mutex_alloc() must be one of these ** integer constants: ** **
        @@ -8764,7 +8764,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); ** CAPI3REF: Retrieve the mutex for a database connection ** METHOD: sqlite3 ** -** ^This interface returns a pointer the [sqlite3_mutex] object that +** ^This interface returns a pointer to the [sqlite3_mutex] object that ** serializes access to the [database connection] given in the argument ** when the [threading mode] is Serialized. ** ^If the [threading mode] is Single-thread or Multi-thread then this @@ -8887,7 +8887,7 @@ SQLITE_API int sqlite3_test_control(int op, ...); ** CAPI3REF: SQL Keyword Checking ** ** These routines provide access to the set of SQL language keywords -** recognized by SQLite. Applications can uses these routines to determine +** recognized by SQLite. Applications can use these routines to determine ** whether or not a specific identifier needs to be escaped (for example, ** by enclosing in double-quotes) so as not to confuse the parser. ** @@ -9055,7 +9055,7 @@ SQLITE_API void sqlite3_str_reset(sqlite3_str*); ** content of the dynamic string under construction in X. The value ** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X ** and might be freed or altered by any subsequent method on the same -** [sqlite3_str] object. Applications must not used the pointer returned +** [sqlite3_str] object. Applications must not use the pointer returned by ** [sqlite3_str_value(X)] after any subsequent method call on the same ** object. ^Applications may change the content of the string returned ** by [sqlite3_str_value(X)] as long as they do not write into any bytes @@ -9141,7 +9141,7 @@ SQLITE_API int sqlite3_status64( ** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE] ** buffer and where forced to overflow to [sqlite3_malloc()]. The ** returned value includes allocations that overflowed because they -** where too large (they were larger than the "sz" parameter to +** were too large (they were larger than the "sz" parameter to ** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because ** no space was left in the page cache.)^ ** @@ -9225,28 +9225,29 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(
        SQLITE_DBSTATUS_LOOKASIDE_HIT
        **
        This parameter returns the number of malloc attempts that were ** satisfied using lookaside memory. Only the high-water value is meaningful; -** the current value is always zero.)^ +** the current value is always zero.
        )^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]] ** ^(
        SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE
        -**
        This parameter returns the number malloc attempts that might have +**
        This parameter returns the number of malloc attempts that might have ** been satisfied using lookaside memory but failed due to the amount of ** memory requested being larger than the lookaside slot size. ** Only the high-water value is meaningful; -** the current value is always zero.)^ +** the current value is always zero.
        )^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]] ** ^(
        SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL
        -**
        This parameter returns the number malloc attempts that might have +**
        This parameter returns the number of malloc attempts that might have ** been satisfied using lookaside memory but failed due to all lookaside ** memory already being in use. ** Only the high-water value is meaningful; -** the current value is always zero.)^ +** the current value is always zero.
        )^ ** ** [[SQLITE_DBSTATUS_CACHE_USED]] ^(
        SQLITE_DBSTATUS_CACHE_USED
        **
        This parameter returns the approximate number of bytes of heap ** memory used by all pager caches associated with the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0. +**
        ** ** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] ** ^(
        SQLITE_DBSTATUS_CACHE_USED_SHARED
        @@ -9255,10 +9256,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** memory used by that pager cache is divided evenly between the attached ** connections.)^ In other words, if none of the pager caches associated ** with the database connection are shared, this request returns the same -** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are +** value as DBSTATUS_CACHE_USED. Or, if one or more of the pager caches are ** shared, the value returned by this call will be smaller than that returned ** by DBSTATUS_CACHE_USED. ^The highwater mark associated with -** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0. +** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0. ** ** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(
        SQLITE_DBSTATUS_SCHEMA_USED
        **
        This parameter returns the approximate number of bytes of heap @@ -9268,6 +9269,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** schema memory is shared with other database connections due to ** [shared cache mode] being enabled. ** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0. +**
        ** ** [[SQLITE_DBSTATUS_STMT_USED]] ^(
        SQLITE_DBSTATUS_STMT_USED
        **
        This parameter returns the approximate number of bytes of heap @@ -9304,7 +9306,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** been written to disk in the middle of a transaction due to the page ** cache overflowing. Transactions are more efficient if they are written ** to disk all at once. When pages spill mid-transaction, that introduces -** additional overhead. This parameter can be used help identify +** additional overhead. This parameter can be used to help identify ** inefficiencies that can be resolved by increasing the cache size. **
        ** @@ -9784,7 +9786,7 @@ typedef struct sqlite3_backup sqlite3_backup; ** external process or via a database connection other than the one being ** used by the backup operation, then the backup will be automatically ** restarted by the next call to sqlite3_backup_step(). ^If the source -** database is modified by the using the same database connection as is used +** database is modified by using the same database connection as is used ** by the backup operation, then the backup database is automatically ** updated at the same time. ** @@ -9801,7 +9803,7 @@ typedef struct sqlite3_backup sqlite3_backup; ** and may not be used following a call to sqlite3_backup_finish(). ** ** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no -** sqlite3_backup_step() errors occurred, regardless or whether or not +** sqlite3_backup_step() errors occurred, regardless of whether or not ** sqlite3_backup_step() completed. ** ^If an out-of-memory condition or IO error occurred during any prior ** sqlite3_backup_step() call on the same [sqlite3_backup] object, then @@ -10871,7 +10873,7 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); ** METHOD: sqlite3 ** ** ^If a write-transaction is open on [database connection] D when the -** [sqlite3_db_cacheflush(D)] interface invoked, any dirty +** [sqlite3_db_cacheflush(D)] interface is invoked, any dirty ** pages in the pager-cache that are not currently in use are written out ** to disk. A dirty page may be in use if a database cursor created by an ** active SQL statement is reading from it, or if it is page 1 of a database @@ -15442,8 +15444,8 @@ typedef INT16_TYPE LogEst; ** assuming n is a signed integer type. UMXV(n) is similar for unsigned ** integer types. */ -#define SMXV(n) ((((i64)1)<<(sizeof(n)-1))-1) -#define UMXV(n) ((((i64)1)<<(sizeof(n)))-1) +#define SMXV(n) ((((i64)1)<<(sizeof(n)*8-1))-1) +#define UMXV(n) ((((i64)1)<<(sizeof(n)*8))-1) /* ** Round up a number to the next larger multiple of 8. This is used @@ -18703,6 +18705,7 @@ struct CollSeq { #define SQLITE_AFF_INTEGER 0x44 /* 'D' */ #define SQLITE_AFF_REAL 0x45 /* 'E' */ #define SQLITE_AFF_FLEXNUM 0x46 /* 'F' */ +#define SQLITE_AFF_DEFER 0x58 /* 'X' - defer computation until later */ #define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC) @@ -19253,7 +19256,7 @@ struct AggInfo { ** from source tables rather than from accumulators */ u8 useSortingIdx; /* In direct mode, reference the sorting index rather ** than the source table */ - u16 nSortingColumn; /* Number of columns in the sorting index */ + u32 nSortingColumn; /* Number of columns in the sorting index */ int sortingIdx; /* Cursor number of the sorting index */ int sortingIdxPTab; /* Cursor number of pseudo-table */ int iFirstReg; /* First register in range for aCol[] and aFunc[] */ @@ -19262,8 +19265,8 @@ struct AggInfo { Table *pTab; /* Source table */ Expr *pCExpr; /* The original expression */ int iTable; /* Cursor number of the source table */ - i16 iColumn; /* Column number within the source table */ - i16 iSorterColumn; /* Column number in the sorting index */ + int iColumn; /* Column number within the source table */ + int iSorterColumn; /* Column number in the sorting index */ } *aCol; int nColumn; /* Number of used entries in aCol[] */ int nAccumulator; /* Number of columns that show through to the output. @@ -43874,21 +43877,20 @@ static int unixShmLock( /* Check that, if this to be a blocking lock, no locks that occur later ** in the following list than the lock being obtained are already held: ** - ** 1. Checkpointer lock (ofst==1). - ** 2. Write lock (ofst==0). - ** 3. Read locks (ofst>=3 && ofst=3 && ofstexclMask|p->sharedMask); assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || ( - (ofst!=2) /* not RECOVER */ + (ofst!=2 || lockMask==0) && (ofst!=1 || lockMask==0 || lockMask==2) && (ofst!=0 || lockMask<3) && (ofst<3 || lockMask<(1<=3 && ofst=3 && ofstexclMask|p->sharedMask); assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || ( - (ofst!=2) /* not RECOVER */ + (ofst!=2 || lockMask==0) && (ofst!=1 || lockMask==0 || lockMask==2) && (ofst!=0 || lockMask<3) && (ofst<3 || lockMask<(1<u.aHash, sizeof(p->u.aHash)); memset(p->u.apSub, 0, sizeof(p->u.apSub)); - p->iDivisor = (p->iSize + BITVEC_NPTR - 1)/BITVEC_NPTR; + p->iDivisor = p->iSize/BITVEC_NPTR; + if( (p->iSize%BITVEC_NPTR)!=0 ) p->iDivisor++; + if( p->iDivisoriDivisor = BITVEC_NBIT; rc = sqlite3BitvecSet(p, i); for(j=0; jfd, pPager->zWal, pPager->exclusiveMode, pPager->journalSizeLimit, &pPager->pWal ); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( rc==SQLITE_OK ){ + sqlite3WalDb(pPager->pWal, pPager->dbWal); + } +#endif } pagerFixMaplimit(pPager); @@ -65750,6 +65765,7 @@ SQLITE_PRIVATE int sqlite3PagerWalWriteLock(Pager *pPager, int bLock){ ** blocking locks are required. */ SQLITE_PRIVATE void sqlite3PagerWalDb(Pager *pPager, sqlite3 *db){ + pPager->dbWal = db; if( pagerUseWal(pPager) ){ sqlite3WalDb(pPager->pWal, db); } @@ -68923,7 +68939,6 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){ rc = walIndexReadHdr(pWal, pChanged); } #ifdef SQLITE_ENABLE_SETLK_TIMEOUT - walDisableBlocking(pWal); if( rc==SQLITE_BUSY_TIMEOUT ){ rc = SQLITE_BUSY; *pCnt |= WAL_RETRY_BLOCKED_MASK; @@ -68938,6 +68953,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){ ** WAL_RETRY this routine will be called again and will probably be ** right on the second iteration. */ + (void)walEnableBlocking(pWal); if( pWal->apWiData[0]==0 ){ /* This branch is taken when the xShmMap() method returns SQLITE_BUSY. ** We assume this is a transient condition, so return WAL_RETRY. The @@ -68954,6 +68970,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){ rc = SQLITE_BUSY_RECOVERY; } } + walDisableBlocking(pWal); if( rc!=SQLITE_OK ){ return rc; } @@ -69641,6 +69658,7 @@ SQLITE_PRIVATE int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *p if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal); } SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + pWal->iReCksum = 0; } return rc; } @@ -69688,6 +69706,9 @@ SQLITE_PRIVATE int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){ walCleanupHash(pWal); } SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + if( pWal->iReCksum>pWal->hdr.mxFrame ){ + pWal->iReCksum = 0; + } } return rc; @@ -75228,6 +75249,13 @@ static SQLITE_NOINLINE int btreeBeginTrans( (void)sqlite3PagerWalWriteLock(pPager, 0); unlockBtreeIfUnused(pBt); } +#if defined(SQLITE_ENABLE_SETLK_TIMEOUT) + if( rc==SQLITE_BUSY_TIMEOUT ){ + /* If a blocking lock timed out, break out of the loop here so that + ** the busy-handler is not invoked. */ + break; + } +#endif }while( (rc&0xFF)==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE && btreeInvokeBusyHandler(pBt) ); sqlite3PagerWalDb(pPager, 0); @@ -105039,7 +105067,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( assert( pCsr->eCurType==CURTYPE_SORTER ); assert( sizeof(KeyInfo) + UMXV(pCsr->pKeyInfo->nKeyField)*sizeof(CollSeq*) < 0x7fffffff ); - szKeyInfo = SZ_KEYINFO(pCsr->pKeyInfo->nKeyField+1); + szKeyInfo = SZ_KEYINFO(pCsr->pKeyInfo->nKeyField); sz = SZ_VDBESORTER(nWorker+1); pSorter = (VdbeSorter*)sqlite3DbMallocZero(db, sz + szKeyInfo); @@ -110389,7 +110417,9 @@ SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){ pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr ); } - if( op==TK_VECTOR ){ + if( op==TK_VECTOR + || (op==TK_FUNCTION && pExpr->affExpr==SQLITE_AFF_DEFER) + ){ assert( ExprUseXList(pExpr) ); return sqlite3ExprAffinity(pExpr->x.pList->a[0].pExpr); } @@ -110582,7 +110612,9 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ p = p->pLeft; continue; } - if( op==TK_VECTOR ){ + if( op==TK_VECTOR + || (op==TK_FUNCTION && p->affExpr==SQLITE_AFF_DEFER) + ){ assert( ExprUseXList(p) ); p = p->x.pList->a[0].pExpr; continue; @@ -117322,7 +117354,9 @@ static void findOrCreateAggInfoColumn( ){ struct AggInfo_col *pCol; int k; + int mxTerm = pParse->db->aLimit[SQLITE_LIMIT_COLUMN]; + assert( mxTerm <= SMXV(i16) ); assert( pAggInfo->iFirstReg==0 ); pCol = pAggInfo->aCol; for(k=0; knColumn; k++, pCol++){ @@ -117340,6 +117374,10 @@ static void findOrCreateAggInfoColumn( assert( pParse->db->mallocFailed ); return; } + if( k>mxTerm ){ + sqlite3ErrorMsg(pParse, "more than %d aggregate terms", mxTerm); + k = mxTerm; + } pCol = &pAggInfo->aCol[k]; assert( ExprUseYTab(pExpr) ); pCol->pTab = pExpr->y.pTab; @@ -117373,6 +117411,7 @@ fix_up_expr: if( pExpr->op==TK_COLUMN ){ pExpr->op = TK_AGG_COLUMN; } + assert( k <= SMXV(pExpr->iAgg) ); pExpr->iAgg = (i16)k; } @@ -117457,13 +117496,19 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ ** function that is already in the pAggInfo structure */ struct AggInfo_func *pItem = pAggInfo->aFunc; + int mxTerm = pParse->db->aLimit[SQLITE_LIMIT_COLUMN]; + assert( mxTerm <= SMXV(i16) ); for(i=0; inFunc; i++, pItem++){ if( NEVER(pItem->pFExpr==pExpr) ) break; if( sqlite3ExprCompare(0, pItem->pFExpr, pExpr, -1)==0 ){ break; } } - if( i>=pAggInfo->nFunc ){ + if( i>mxTerm ){ + sqlite3ErrorMsg(pParse, "more than %d aggregate terms", mxTerm); + i = mxTerm; + assert( inFunc ); + }else if( i>=pAggInfo->nFunc ){ /* pExpr is original. Make a new entry in pAggInfo->aFunc[] */ u8 enc = ENC(pParse->db); @@ -117517,6 +117562,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ */ assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); ExprSetVVAProperty(pExpr, EP_NoReduce); + assert( i <= SMXV(pExpr->iAgg) ); pExpr->iAgg = (i16)i; pExpr->pAggInfo = pAggInfo; return WRC_Prune; @@ -131975,7 +132021,7 @@ static void concatFuncCore( int nSep, const char *zSep ){ - i64 j, k, n = 0; + i64 j, n = 0; int i; char *z; for(i=0; i0 ){ + if( sqlite3_value_type(argv[i])!=SQLITE_NULL ){ + int k = sqlite3_value_bytes(argv[i]); const char *v = (const char*)sqlite3_value_text(argv[i]); if( v!=0 ){ if( j>0 && nSep>0 ){ @@ -145364,7 +145410,7 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){ } pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol); sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol); - if( (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + if( (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 && pParse->nErr==0 ){ /* This branch runs if the query contains one or more RIGHT or FULL ** JOINs. If only a single table on the left side of this join ** contains the zName column, then this branch is a no-op. @@ -145380,6 +145426,8 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){ */ ExprList *pFuncArgs = 0; /* Arguments to the coalesce() */ static const Token tkCoalesce = { "coalesce", 8 }; + assert( pE1!=0 ); + ExprSetProperty(pE1, EP_CanBeNull); while( tableAndColumnIndex(pSrc, iLeft+1, i, zName, &iLeft, &iLeftCol, pRight->fg.isSynthUsing)!=0 ){ if( pSrc->a[iLeft].fg.isUsing==0 @@ -145396,7 +145444,13 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){ if( pFuncArgs ){ pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1); pE1 = sqlite3ExprFunction(pParse, pFuncArgs, &tkCoalesce, 0); + if( pE1 ){ + pE1->affExpr = SQLITE_AFF_DEFER; + } } + }else if( (pSrc->a[i+1].fg.jointype & JT_LEFT)!=0 && pParse->nErr==0 ){ + assert( pE1!=0 ); + ExprSetProperty(pE1, EP_CanBeNull); } pE2 = sqlite3CreateColumnExpr(db, pSrc, i+1, iRightCol); sqlite3SrcItemColumnUsed(pRight, iRightCol); @@ -149004,9 +149058,9 @@ static int compoundHasDifferentAffinities(Select *p){ ** from 2015-02-09.) ** ** (3) If the subquery is the right operand of a LEFT JOIN then -** (3a) the subquery may not be a join and -** (3b) the FROM clause of the subquery may not contain a virtual -** table and +** (3a) the subquery may not be a join +** (**) Was (3b): "the FROM clause of the subquery may not contain +** a virtual table" ** (**) Was: "The outer query may not have a GROUP BY." This case ** is now managed correctly ** (3d) the outer query may not be DISTINCT. @@ -149222,7 +149276,7 @@ static int flattenSubquery( */ if( (pSubitem->fg.jointype & (JT_OUTER|JT_LTORJ))!=0 ){ if( pSubSrc->nSrc>1 /* (3a) */ - || IsVirtual(pSubSrc->a[0].pSTab) /* (3b) */ + /**** || IsVirtual(pSubSrc->a[0].pSTab) (3b)-omitted */ || (p->selFlags & SF_Distinct)!=0 /* (3d) */ || (pSubitem->fg.jointype & JT_RIGHT)!=0 /* (26) */ ){ @@ -161722,12 +161776,13 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( if( pLevel->iLeftJoin==0 ){ /* If a partial index is driving the loop, try to eliminate WHERE clause ** terms from the query that must be true due to the WHERE clause of - ** the partial index. + ** the partial index. This optimization does not work on an outer join, + ** as shown by: ** - ** 2019-11-02 ticket 623eff57e76d45f6: This optimization does not work - ** for a LEFT JOIN. + ** 2019-11-02 ticket 623eff57e76d45f6 (LEFT JOIN) + ** 2025-05-29 forum post 7dee41d32506c4ae (RIGHT JOIN) */ - if( pIdx->pPartIdxWhere ){ + if( pIdx->pPartIdxWhere && pLevel->pRJ==0 ){ whereApplyPartialIndexConstraints(pIdx->pPartIdxWhere, iCur, pWC); } }else{ @@ -163400,30 +163455,42 @@ static void exprAnalyzeOrTerm( ** 1. The SQLITE_Transitive optimization must be enabled ** 2. Must be either an == or an IS operator ** 3. Not originating in the ON clause of an OUTER JOIN -** 4. The affinities of A and B must be compatible -** 5a. Both operands use the same collating sequence OR -** 5b. The overall collating sequence is BINARY +** 4. The operator is not IS or else the query does not contain RIGHT JOIN +** 5. The affinities of A and B must be compatible +** 6a. Both operands use the same collating sequence OR +** 6b. The overall collating sequence is BINARY ** If this routine returns TRUE, that means that the RHS can be substituted ** for the LHS anyplace else in the WHERE clause where the LHS column occurs. ** This is an optimization. No harm comes from returning 0. But if 1 is ** returned when it should not be, then incorrect answers might result. */ -static int termIsEquivalence(Parse *pParse, Expr *pExpr){ +static int termIsEquivalence(Parse *pParse, Expr *pExpr, SrcList *pSrc){ char aff1, aff2; CollSeq *pColl; - if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0; - if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0; - if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; + if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0; /* (1) */ + if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0; /* (2) */ + if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* (3) */ + assert( pSrc!=0 ); + if( pExpr->op==TK_IS + && pSrc->nSrc + && (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 + ){ + return 0; /* (4) */ + } aff1 = sqlite3ExprAffinity(pExpr->pLeft); aff2 = sqlite3ExprAffinity(pExpr->pRight); if( aff1!=aff2 && (!sqlite3IsNumericAffinity(aff1) || !sqlite3IsNumericAffinity(aff2)) ){ - return 0; + return 0; /* (5) */ } pColl = sqlite3ExprCompareCollSeq(pParse, pExpr); - if( sqlite3IsBinary(pColl) ) return 1; - return sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight); + if( !sqlite3IsBinary(pColl) + && !sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight) + ){ + return 0; /* (6) */ + } + return 1; } /* @@ -163688,8 +163755,8 @@ static void exprAnalyze( if( op==TK_IS ) pNew->wtFlags |= TERM_IS; pTerm = &pWC->a[idxTerm]; pTerm->wtFlags |= TERM_COPIED; - - if( termIsEquivalence(pParse, pDup) ){ + assert( pWInfo->pTabList!=0 ); + if( termIsEquivalence(pParse, pDup, pWInfo->pTabList) ){ pTerm->eOperator |= WO_EQUIV; eExtraOp = WO_EQUIV; } @@ -184391,6 +184458,7 @@ SQLITE_API int sqlite3_setlk_timeout(sqlite3 *db, int ms, int flags){ #endif if( ms<-1 ) return SQLITE_RANGE; #ifdef SQLITE_ENABLE_SETLK_TIMEOUT + sqlite3_mutex_enter(db->mutex); db->setlkTimeout = ms; db->setlkFlags = flags; sqlite3BtreeEnterAll(db); @@ -184402,6 +184470,7 @@ SQLITE_API int sqlite3_setlk_timeout(sqlite3 *db, int ms, int flags){ } } sqlite3BtreeLeaveAll(db); + sqlite3_mutex_leave(db->mutex); #endif #if !defined(SQLITE_ENABLE_API_ARMOR) && !defined(SQLITE_ENABLE_SETLK_TIMEOUT) UNUSED_PARAMETER(db); @@ -209021,8 +209090,10 @@ static int jsonBlobChangePayloadSize( nExtra = 1; }else if( szType==13 ){ nExtra = 2; - }else{ + }else if( szType==14 ){ nExtra = 4; + }else{ + nExtra = 8; } if( szPayload<=11 ){ nNeeded = 0; @@ -213407,6 +213478,8 @@ SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3 *db){ #endif SQLITE_PRIVATE int sqlite3GetToken(const unsigned char*,int*); /* In the SQLite core */ +/* #include */ + /* ** If building separately, we will need some setup that is normally ** found in sqliteInt.h @@ -235449,6 +235522,7 @@ SQLITE_EXTENSION_INIT1 /* #include */ /* #include */ +/* #include */ #ifndef SQLITE_AMALGAMATION @@ -257192,7 +257266,7 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2025-05-29 14:26:00 dfc790f998f450d9c35e3ba1c8c89c17466cb559f87b0239e4aab9d34e28f742", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079", -1, SQLITE_TRANSIENT); } /* @@ -258007,6 +258081,7 @@ static int fts5StorageDeleteFromIndex( for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ if( pConfig->abUnindexed[iCol-1]==0 ){ sqlite3_value *pVal = 0; + sqlite3_value *pFree = 0; const char *pText = 0; int nText = 0; const char *pLoc = 0; @@ -258023,11 +258098,22 @@ static int fts5StorageDeleteFromIndex( if( pConfig->bLocale && sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); }else{ - pText = (const char*)sqlite3_value_text(pVal); - nText = sqlite3_value_bytes(pVal); - if( pConfig->bLocale && pSeek ){ - pLoc = (const char*)sqlite3_column_text(pSeek, iCol + pConfig->nCol); - nLoc = sqlite3_column_bytes(pSeek, iCol + pConfig->nCol); + if( sqlite3_value_type(pVal)!=SQLITE_TEXT ){ + /* Make a copy of the value to work with. This is because the call + ** to sqlite3_value_text() below forces the type of the value to + ** SQLITE_TEXT, and we may need to use it again later. */ + pFree = pVal = sqlite3_value_dup(pVal); + if( pVal==0 ){ + rc = SQLITE_NOMEM; + } + } + if( rc==SQLITE_OK ){ + pText = (const char*)sqlite3_value_text(pVal); + nText = sqlite3_value_bytes(pVal); + if( pConfig->bLocale && pSeek ){ + pLoc = (const char*)sqlite3_column_text(pSeek, iCol+pConfig->nCol); + nLoc = sqlite3_column_bytes(pSeek, iCol + pConfig->nCol); + } } } @@ -258043,6 +258129,7 @@ static int fts5StorageDeleteFromIndex( } sqlite3Fts5ClearLocale(pConfig); } + sqlite3_value_free(pFree); } } if( rc==SQLITE_OK && p->nTotalRow<1 ){ diff --git a/Telegram/SourceFiles/ayu/libs/sqlite/sqlite3.h b/Telegram/SourceFiles/ayu/libs/sqlite/sqlite3.h index f61a148575..f56dd8d86a 100644 --- a/Telegram/SourceFiles/ayu/libs/sqlite/sqlite3.h +++ b/Telegram/SourceFiles/ayu/libs/sqlite/sqlite3.h @@ -146,9 +146,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.50.0" -#define SQLITE_VERSION_NUMBER 3050000 -#define SQLITE_SOURCE_ID "2025-05-29 14:26:00 dfc790f998f450d9c35e3ba1c8c89c17466cb559f87b0239e4aab9d34e28f742" +#define SQLITE_VERSION "3.50.2" +#define SQLITE_VERSION_NUMBER 3050002 +#define SQLITE_SOURCE_ID "2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -4079,7 +4079,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*); ** ** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of ** database filename D with corresponding journal file J and WAL file W and -** with N URI parameters key/values pairs in the array P. The result from +** an array P of N URI Key/Value pairs. The result from ** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that ** is safe to pass to routines like: **
          @@ -4760,7 +4760,7 @@ typedef struct sqlite3_context sqlite3_context; ** METHOD: sqlite3_stmt ** ** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants, -** literals may be replaced by a [parameter] that matches one of following +** literals may be replaced by a [parameter] that matches one of the following ** templates: ** **
            @@ -4805,7 +4805,7 @@ typedef struct sqlite3_context sqlite3_context; ** ** [[byte-order determination rules]] ^The byte-order of ** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF) -** found in first character, which is removed, or in the absence of a BOM +** found in the first character, which is removed, or in the absence of a BOM ** the byte order is the native byte order of the host ** machine for sqlite3_bind_text16() or the byte order specified in ** the 6th parameter for sqlite3_bind_text64().)^ @@ -4825,7 +4825,7 @@ typedef struct sqlite3_context sqlite3_context; ** or sqlite3_bind_text16() or sqlite3_bind_text64() then ** that parameter must be the byte offset ** where the NUL terminator would occur assuming the string were NUL -** terminated. If any NUL characters occurs at byte offsets less than +** terminated. If any NUL characters occur at byte offsets less than ** the value of the fourth parameter then the resulting string value will ** contain embedded NULs. The result of expressions involving strings ** with embedded NULs is undefined. @@ -5037,7 +5037,7 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); ** METHOD: sqlite3_stmt ** ** ^These routines provide a means to determine the database, table, and -** table column that is the origin of a particular result column in +** table column that is the origin of a particular result column in a ** [SELECT] statement. ** ^The name of the database or table or column can be returned as ** either a UTF-8 or UTF-16 string. ^The _database_ routines return @@ -5606,8 +5606,8 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** ** For best security, the [SQLITE_DIRECTONLY] flag is recommended for ** all application-defined SQL functions that do not need to be -** used inside of triggers, view, CHECK constraints, or other elements of -** the database schema. This flags is especially recommended for SQL +** used inside of triggers, views, CHECK constraints, or other elements of +** the database schema. This flag is especially recommended for SQL ** functions that have side effects or reveal internal application state. ** Without this flag, an attacker might be able to modify the schema of ** a database file to include invocations of the function with parameters @@ -5638,7 +5638,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** [user-defined window functions|available here]. ** ** ^(If the final parameter to sqlite3_create_function_v2() or -** sqlite3_create_window_function() is not NULL, then it is destructor for +** sqlite3_create_window_function() is not NULL, then it is the destructor for ** the application data pointer. The destructor is invoked when the function ** is deleted, either by being overloaded or when the database connection ** closes.)^ ^The destructor is also invoked if the call to @@ -6038,7 +6038,7 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); ** METHOD: sqlite3_value ** ** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value] -** object D and returns a pointer to that copy. ^The [sqlite3_value] returned +** object V and returns a pointer to that copy. ^The [sqlite3_value] returned ** is a [protected sqlite3_value] object even if the input is not. ** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a ** memory allocation fails. ^If V is a [pointer value], then the result @@ -6076,7 +6076,7 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*); ** allocation error occurs. ** ** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is -** determined by the N parameter on first successful call. Changing the +** determined by the N parameter on the first successful call. Changing the ** value of N in any subsequent call to sqlite3_aggregate_context() within ** the same aggregate function instance will not resize the memory ** allocation.)^ Within the xFinal callback, it is customary to set @@ -6238,7 +6238,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi ** ** Security Warning: These interfaces should not be exposed in scripting ** languages or in other circumstances where it might be possible for an -** an attacker to invoke them. Any agent that can invoke these interfaces +** attacker to invoke them. Any agent that can invoke these interfaces ** can probably also take control of the process. ** ** Database connection client data is only available for SQLite @@ -6352,7 +6352,7 @@ typedef void (*sqlite3_destructor_type)(void*); ** pointed to by the 2nd parameter are taken as the application-defined ** function result. If the 3rd parameter is non-negative, then it ** must be the byte offset into the string where the NUL terminator would -** appear if the string where NUL terminated. If any NUL characters occur +** appear if the string were NUL terminated. If any NUL characters occur ** in the string at a byte offset that is less than the value of the 3rd ** parameter, then the resulting string will contain embedded NULs and the ** result of expressions operating on strings with embedded NULs is undefined. @@ -6410,7 +6410,7 @@ typedef void (*sqlite3_destructor_type)(void*); ** string and preferably a string literal. The sqlite3_result_pointer() ** routine is part of the [pointer passing interface] added for SQLite 3.20.0. ** -** If these routines are called from within the different thread +** If these routines are called from within a different thread ** than the one containing the application-defined function that received ** the [sqlite3_context] pointer, the results are undefined. */ @@ -6816,7 +6816,7 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); ** METHOD: sqlite3 ** ** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name -** for the N-th database on database connection D, or a NULL pointer of N is +** for the N-th database on database connection D, or a NULL pointer if N is ** out of range. An N value of 0 means the main database file. An N of 1 is ** the "temp" schema. Larger values of N correspond to various ATTACH-ed ** databases. @@ -6911,7 +6911,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema); **
            The SQLITE_TXN_READ state means that the database is currently ** in a read transaction. Content has been read from the database file ** but nothing in the database file has changed. The transaction state -** will advanced to SQLITE_TXN_WRITE if any changes occur and there are +** will be advanced to SQLITE_TXN_WRITE if any changes occur and there are ** no other conflicting concurrent write transactions. The transaction ** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or ** [COMMIT].
            @@ -6920,7 +6920,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema); **
            The SQLITE_TXN_WRITE state means that the database is currently ** in a write transaction. Content has been written to the database file ** but has not yet committed. The transaction state will change to -** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].
            +** SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT]. */ #define SQLITE_TXN_NONE 0 #define SQLITE_TXN_READ 1 @@ -7201,7 +7201,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); ** CAPI3REF: Impose A Limit On Heap Size ** ** These interfaces impose limits on the amount of heap memory that will be -** by all database connections within a single process. +** used by all database connections within a single process. ** ** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the ** soft limit on the amount of heap memory that may be allocated by SQLite. @@ -7259,7 +7259,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); **
          )^ ** ** The circumstances under which SQLite will enforce the heap limits may -** changes in future releases of SQLite. +** change in future releases of SQLite. */ SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N); @@ -7374,8 +7374,8 @@ SQLITE_API int sqlite3_table_column_metadata( ** ^The entry point is zProc. ** ^(zProc may be 0, in which case SQLite will try to come up with an ** entry point name on its own. It first tries "sqlite3_extension_init". -** If that does not work, it constructs a name "sqlite3_X_init" where the -** X is consists of the lower-case equivalent of all ASCII alphabetic +** If that does not work, it constructs a name "sqlite3_X_init" where +** X consists of the lower-case equivalent of all ASCII alphabetic ** characters in the filename from the last "/" to the first following ** "." and omitting any initial "lib".)^ ** ^The sqlite3_load_extension() interface returns @@ -7446,7 +7446,7 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); ** ^(Even though the function prototype shows that xEntryPoint() takes ** no arguments and returns void, SQLite invokes xEntryPoint() with three ** arguments and expects an integer result as if the signature of the -** entry point where as follows: +** entry point were as follows: ** **
           **    int xEntryPoint(
          @@ -7610,7 +7610,7 @@ struct sqlite3_module {
           ** virtual table and might not be checked again by the byte code.)^ ^(The
           ** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
           ** is left in its default setting of false, the constraint will always be
          -** checked separately in byte code.  If the omit flag is change to true, then
          +** checked separately in byte code.  If the omit flag is changed to true, then
           ** the constraint may or may not be checked in byte code.  In other words,
           ** when the omit flag is true there is no guarantee that the constraint will
           ** not be checked again using byte code.)^
          @@ -7636,7 +7636,7 @@ struct sqlite3_module {
           ** The xBestIndex method may optionally populate the idxFlags field with a
           ** mask of SQLITE_INDEX_SCAN_* flags. One such flag is
           ** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN]
          -** output to show the idxNum has hex instead of as decimal.  Another flag is
          +** output to show the idxNum as hex instead of as decimal.  Another flag is
           ** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will
           ** return at most one row.
           **
          @@ -7777,7 +7777,7 @@ struct sqlite3_index_info {
           ** the implementation of the [virtual table module].   ^The fourth
           ** parameter is an arbitrary client data pointer that is passed through
           ** into the [xCreate] and [xConnect] methods of the virtual table module
          -** when a new virtual table is be being created or reinitialized.
          +** when a new virtual table is being created or reinitialized.
           **
           ** ^The sqlite3_create_module_v2() interface has a fifth parameter which
           ** is a pointer to a destructor for the pClientData.  ^SQLite will
          @@ -7942,7 +7942,7 @@ typedef struct sqlite3_blob sqlite3_blob;
           ** in *ppBlob. Otherwise an [error code] is returned and, unless the error
           ** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
           ** the API is not misused, it is always safe to call [sqlite3_blob_close()]
          -** on *ppBlob after this function it returns.
          +** on *ppBlob after this function returns.
           **
           ** This function fails with SQLITE_ERROR if any of the following are true:
           ** 
            @@ -8062,7 +8062,7 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *); ** ** ^Returns the size in bytes of the BLOB accessible via the ** successfully opened [BLOB handle] in its only argument. ^The -** incremental blob I/O routines can only read or overwriting existing +** incremental blob I/O routines can only read or overwrite existing ** blob content; they cannot change the size of a blob. ** ** This routine only works on a [BLOB handle] which has been created @@ -8212,7 +8212,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** ^The sqlite3_mutex_alloc() routine allocates a new ** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc() ** routine returns NULL if it is unable to allocate the requested -** mutex. The argument to sqlite3_mutex_alloc() must one of these +** mutex. The argument to sqlite3_mutex_alloc() must be one of these ** integer constants: ** **
              @@ -8445,7 +8445,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); ** CAPI3REF: Retrieve the mutex for a database connection ** METHOD: sqlite3 ** -** ^This interface returns a pointer the [sqlite3_mutex] object that +** ^This interface returns a pointer to the [sqlite3_mutex] object that ** serializes access to the [database connection] given in the argument ** when the [threading mode] is Serialized. ** ^If the [threading mode] is Single-thread or Multi-thread then this @@ -8568,7 +8568,7 @@ SQLITE_API int sqlite3_test_control(int op, ...); ** CAPI3REF: SQL Keyword Checking ** ** These routines provide access to the set of SQL language keywords -** recognized by SQLite. Applications can uses these routines to determine +** recognized by SQLite. Applications can use these routines to determine ** whether or not a specific identifier needs to be escaped (for example, ** by enclosing in double-quotes) so as not to confuse the parser. ** @@ -8736,7 +8736,7 @@ SQLITE_API void sqlite3_str_reset(sqlite3_str*); ** content of the dynamic string under construction in X. The value ** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X ** and might be freed or altered by any subsequent method on the same -** [sqlite3_str] object. Applications must not used the pointer returned +** [sqlite3_str] object. Applications must not use the pointer returned by ** [sqlite3_str_value(X)] after any subsequent method call on the same ** object. ^Applications may change the content of the string returned ** by [sqlite3_str_value(X)] as long as they do not write into any bytes @@ -8822,7 +8822,7 @@ SQLITE_API int sqlite3_status64( ** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE] ** buffer and where forced to overflow to [sqlite3_malloc()]. The ** returned value includes allocations that overflowed because they -** where too large (they were larger than the "sz" parameter to +** were too large (they were larger than the "sz" parameter to ** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because ** no space was left in the page cache.)^ ** @@ -8906,28 +8906,29 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(
              SQLITE_DBSTATUS_LOOKASIDE_HIT
              **
              This parameter returns the number of malloc attempts that were ** satisfied using lookaside memory. Only the high-water value is meaningful; -** the current value is always zero.)^ +** the current value is always zero.
              )^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]] ** ^(
              SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE
              -**
              This parameter returns the number malloc attempts that might have +**
              This parameter returns the number of malloc attempts that might have ** been satisfied using lookaside memory but failed due to the amount of ** memory requested being larger than the lookaside slot size. ** Only the high-water value is meaningful; -** the current value is always zero.)^ +** the current value is always zero.
              )^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]] ** ^(
              SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL
              -**
              This parameter returns the number malloc attempts that might have +**
              This parameter returns the number of malloc attempts that might have ** been satisfied using lookaside memory but failed due to all lookaside ** memory already being in use. ** Only the high-water value is meaningful; -** the current value is always zero.)^ +** the current value is always zero.
              )^ ** ** [[SQLITE_DBSTATUS_CACHE_USED]] ^(
              SQLITE_DBSTATUS_CACHE_USED
              **
              This parameter returns the approximate number of bytes of heap ** memory used by all pager caches associated with the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0. +**
              ** ** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] ** ^(
              SQLITE_DBSTATUS_CACHE_USED_SHARED
              @@ -8936,10 +8937,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** memory used by that pager cache is divided evenly between the attached ** connections.)^ In other words, if none of the pager caches associated ** with the database connection are shared, this request returns the same -** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are +** value as DBSTATUS_CACHE_USED. Or, if one or more of the pager caches are ** shared, the value returned by this call will be smaller than that returned ** by DBSTATUS_CACHE_USED. ^The highwater mark associated with -** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0. +** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0. ** ** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(
              SQLITE_DBSTATUS_SCHEMA_USED
              **
              This parameter returns the approximate number of bytes of heap @@ -8949,6 +8950,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** schema memory is shared with other database connections due to ** [shared cache mode] being enabled. ** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0. +**
              ** ** [[SQLITE_DBSTATUS_STMT_USED]] ^(
              SQLITE_DBSTATUS_STMT_USED
              **
              This parameter returns the approximate number of bytes of heap @@ -8985,7 +8987,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** been written to disk in the middle of a transaction due to the page ** cache overflowing. Transactions are more efficient if they are written ** to disk all at once. When pages spill mid-transaction, that introduces -** additional overhead. This parameter can be used help identify +** additional overhead. This parameter can be used to help identify ** inefficiencies that can be resolved by increasing the cache size. **
              ** @@ -9465,7 +9467,7 @@ typedef struct sqlite3_backup sqlite3_backup; ** external process or via a database connection other than the one being ** used by the backup operation, then the backup will be automatically ** restarted by the next call to sqlite3_backup_step(). ^If the source -** database is modified by the using the same database connection as is used +** database is modified by using the same database connection as is used ** by the backup operation, then the backup database is automatically ** updated at the same time. ** @@ -9482,7 +9484,7 @@ typedef struct sqlite3_backup sqlite3_backup; ** and may not be used following a call to sqlite3_backup_finish(). ** ** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no -** sqlite3_backup_step() errors occurred, regardless or whether or not +** sqlite3_backup_step() errors occurred, regardless of whether or not ** sqlite3_backup_step() completed. ** ^If an out-of-memory condition or IO error occurred during any prior ** sqlite3_backup_step() call on the same [sqlite3_backup] object, then @@ -10552,7 +10554,7 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); ** METHOD: sqlite3 ** ** ^If a write-transaction is open on [database connection] D when the -** [sqlite3_db_cacheflush(D)] interface invoked, any dirty +** [sqlite3_db_cacheflush(D)] interface is invoked, any dirty ** pages in the pager-cache that are not currently in use are written out ** to disk. A dirty page may be in use if a database cursor created by an ** active SQL statement is reading from it, or if it is page 1 of a database diff --git a/Telegram/SourceFiles/ayu/ui/message_history/history_item.cpp b/Telegram/SourceFiles/ayu/ui/message_history/history_item.cpp index 8770f059af..2a56ea23d1 100644 --- a/Telegram/SourceFiles/ayu/ui/message_history/history_item.cpp +++ b/Telegram/SourceFiles/ayu/ui/message_history/history_item.cpp @@ -9,6 +9,7 @@ #include "api/api_chat_participants.h" #include "api/api_text_entities.h" +#include "ayu/utils/ayu_mapper.h" #include "ayu/ui/message_history/history_inner.h" #include "base/unixtime.h" #include "core/application.h" @@ -118,7 +119,10 @@ void GenerateItems( }; const auto text = QString::fromStdString(message.text); - addSimpleTextMessage(Ui::Text::WithEntities(text)); + auto textAndEntities = Ui::Text::WithEntities(text); + const auto entities = AyuMapper::deserializeTextWithEntities(message.textEntities); + textAndEntities.entities = Api::EntitiesFromMTP(&history->session(), entities.v); + addSimpleTextMessage(std::move(textAndEntities)); } } // namespace MessageHistory diff --git a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp index 298bbb667d..e6c05e1ffb 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp +++ b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp @@ -855,12 +855,12 @@ void SetupQoLToggles(not_null container) { tr::ayu_HideChannelReactions(), st::settingsButtonNoIcon )->toggleOn( - rpl::single(!settings->hideChannelReactions) + rpl::single(!settings->showChannelReactions) )->toggledValue( ) | rpl::filter( [=](bool enabled) { - return (!enabled != settings->hideChannelReactions); + return (!enabled != settings->showChannelReactions); }) | start_with_next( [=](bool enabled) { @@ -874,12 +874,12 @@ void SetupQoLToggles(not_null container) { tr::ayu_HideGroupReactions(), st::settingsButtonNoIcon )->toggleOn( - rpl::single(!settings->hideGroupReactions) + rpl::single(!settings->showGroupReactions) )->toggledValue( ) | rpl::filter( [=](bool enabled) { - return (!enabled != settings->hideGroupReactions); + return (!enabled != settings->showGroupReactions); }) | start_with_next( [=](bool enabled) { diff --git a/Telegram/SourceFiles/ayu/utils/ayu_mapper.cpp b/Telegram/SourceFiles/ayu/utils/ayu_mapper.cpp index 2c1fc99c4c..08936d9d46 100644 --- a/Telegram/SourceFiles/ayu/utils/ayu_mapper.cpp +++ b/Telegram/SourceFiles/ayu/utils/ayu_mapper.cpp @@ -6,9 +6,14 @@ // Copyright @Radolyn, 2025 #include "ayu_mapper.h" +#include "apiwrap.h" +#include "api/api_text_entities.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" +#include "main/main_session.h" +#include "mtproto/connection_abstract.h" +#include "mtproto/details/mtproto_dump_to_text.h" namespace AyuMapper { @@ -39,17 +44,56 @@ constexpr auto kMessageFlagHasTTL = 0x02000000; constexpr auto kMessageFlagInvertMedia = 0x08000000; constexpr auto kMessageFlagHasSavedPeer = 0x10000000; +template +std::vector serializeObject(MTPObject object) { + mtpBuffer buffer; + object.write(buffer); + + const auto from = reinterpret_cast(buffer.data()); + const auto end = from + buffer.size() * sizeof(mtpPrime); + + std::vector entities(from, end); + return entities; +} + +template +MTPObject deserializeObject(std::vector serialized) { + gsl::span span(serialized.data(), serialized.size()); + + auto from = reinterpret_cast(span.data()); + const auto end = from + span.size() / sizeof(mtpPrime); + + MTPObject data; + if (!data.read(from, end)) { + LOG(("AyuMapper: Failed to deserialize object")); + } + return data; +} + std::pair> serializeTextWithEntities(not_null item) { if (item->emptyText()) { return std::make_pair("", std::vector()); } - auto textWithEntities = item->originalText(); - std::vector entities; // todo: implement writing to buffer + + + std::vector entities; + if (!textWithEntities.entities.empty()) { + const auto mtpEntities = Api::EntitiesToMTP( + &item->history()->session(), + textWithEntities.entities, + Api::ConvertOption::WithLocal); + + entities = serializeObject(mtpEntities); + } return std::make_pair(textWithEntities.text.toStdString(), entities); } +MTPVector deserializeTextWithEntities(std::vector serialized) { + return deserializeObject>(serialized); +} + int mapItemFlagsToMTPFlags(not_null item) { int flags = 0; diff --git a/Telegram/SourceFiles/ayu/utils/ayu_mapper.h b/Telegram/SourceFiles/ayu/utils/ayu_mapper.h index 9d6646bdfe..8bd8872d8f 100644 --- a/Telegram/SourceFiles/ayu/utils/ayu_mapper.h +++ b/Telegram/SourceFiles/ayu/utils/ayu_mapper.h @@ -8,7 +8,14 @@ namespace AyuMapper { +template +[[nodiscard]] MTPObject deserializeObject(std::vector serialized); + +template +[[nodiscard]] std::vector serializeObject(MTPObject object); + std::pair> serializeTextWithEntities(not_null item); +[[nodiscard]] MTPVector deserializeTextWithEntities(std::vector serialized); int mapItemFlagsToMTPFlags(not_null item); } // namespace AyuMapper diff --git a/Telegram/SourceFiles/ayu/utils/rc_manager.cpp b/Telegram/SourceFiles/ayu/utils/rc_manager.cpp index 2d392c975d..634dadaeae 100644 --- a/Telegram/SourceFiles/ayu/utils/rc_manager.cpp +++ b/Telegram/SourceFiles/ayu/utils/rc_manager.cpp @@ -14,19 +14,21 @@ std::unordered_set default_developers = { 963080346, 1282540315, 1374434073, 168769611, - 1773117711, 5330087923, 666154369, 139303278, + 1773117711, 5330087923, 139303278, 1752394339, 668557709, 1348136086, 6288255532, 7453676178, + 880708503, 2135966128, 7818249287, // ------------------------------------------- 778327202, 238292700, 1795176335, 6247153446, - 1752394339, 7745305003, 1183312839, 497855299, - 623054735 + 1183312839, 497855299 }; std::unordered_set default_channels = { 1233768168, 1524581881, 1571726392, 1632728092, 1172503281, 1877362358, 1905581924, 1794457129, 1434550607, 1947958814, 1815864846, 2130395384, - 1976430343, 1754537498, 1725670701, + 1976430343, 1754537498, 1725670701, 2401498637, + 2685666919, 2562664432, 2564770112, 2331068091, + 1559501352, 2641258043 }; void RCManager::start() { diff --git a/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp b/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp index 21b1213079..d4c73423ad 100644 --- a/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp +++ b/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp @@ -320,6 +320,22 @@ QString formatDateTime(const QDateTime &date) { return datePart + getLocalizedAt() + timePart; } +QString formatMessageTime(const QTime &time) { + const auto &settings = AyuSettings::getInstance(); + + const auto format = + settings.showMessageSeconds + ? (QLocale().timeFormat(QLocale::ShortFormat).contains("AP") + ? "h:mm:ss AP" + : "HH:mm:ss") + : QLocale().timeFormat(QLocale::ShortFormat); + + return QLocale().toString( + time, + format + ); +} + int getMediaSizeBytes(not_null message) { if (!message->media()) { return -1; diff --git a/Telegram/SourceFiles/ayu/utils/telegram_helpers.h b/Telegram/SourceFiles/ayu/utils/telegram_helpers.h index 9c000466c0..7c48d181a2 100644 --- a/Telegram/SourceFiles/ayu/utils/telegram_helpers.h +++ b/Telegram/SourceFiles/ayu/utils/telegram_helpers.h @@ -37,6 +37,7 @@ void readHistory(not_null message); QString formatTTL(int time); QString formatDateTime(const QDateTime &date); +QString formatMessageTime(const QTime &time); QString getDCName(int dc); diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index e5833d5f85..898c049d66 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -111,6 +111,13 @@ void AddProxyFromClipboard( QGuiApplication::clipboard()->text()); const auto isSingle = maybeUrls.size() == 1; + enum class Result { + Success, + Failed, + Unsupported, + Invalid, + }; + const auto proceedUrl = [=](const auto &local) { const auto command = base::StringViewMid( local, @@ -146,6 +153,11 @@ void AddProxyFromClipboard( match->captured(1), qthelp::UrlParamNameTransform::ToLower); const auto proxy = ProxyDataFromFields(type, fields); + if (!proxy) { + return (proxy.status() == ProxyData::Status::Unsupported) + ? Result::Unsupported + : Result::Invalid; + } const auto contains = controller->contains(proxy); const auto toast = (contains ? tr::lng_proxy_add_from_clipboard_existing_toast @@ -158,19 +170,29 @@ void AddProxyFromClipboard( } break; } - return true; + return Result::Success; } - return false; + return Result::Failed; }; - auto success = false; + auto success = Result::Failed; for (const auto &maybeUrl : maybeUrls) { - success |= proceedUrl(Core::TryConvertUrlToLocal(maybeUrl)); + const auto result = proceedUrl(Core::TryConvertUrlToLocal(maybeUrl)); + if (success != Result::Success) { + success = result; + } } - if (!success) { - show->showToast( - tr::lng_proxy_add_from_clipboard_failed_toast(tr::now)); + if (success != Result::Success) { + if (success == Result::Failed) { + show->showToast( + tr::lng_proxy_add_from_clipboard_failed_toast(tr::now)); + } else { + show->showBox(Ui::MakeInformBox( + (success == Result::Unsupported + ? tr::lng_proxy_unsupported(tr::now) + : tr::lng_proxy_invalid(tr::now)))); + } } } diff --git a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp index 071b9dd684..bf8c08c3ea 100644 --- a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp @@ -361,9 +361,14 @@ void CreateModerateMessagesBox( }); } if (allCanBan) { - auto ownedWrap = object_ptr>( - inner, - object_ptr(inner)); + const auto peer = items.front()->history()->peer; + auto ownedWrap = peer->isMonoforum() + ? nullptr + : object_ptr>( + inner, + object_ptr(inner)); + auto computeRestrictions = Fn(); + const auto wrap = ownedWrap.data(); Ui::AddSkip(inner); Ui::AddSkip(inner); @@ -371,7 +376,9 @@ void CreateModerateMessagesBox( object_ptr( box, rpl::conditional( - ownedWrap->toggledValue(), + (ownedWrap + ? ownedWrap->toggledValue() + : rpl::single(false) | rpl::type_erased()), tr::lng_restrict_user( lt_count, rpl::single(participants.size()) | tr::to_count()), @@ -390,136 +397,141 @@ void CreateModerateMessagesBox( Ui::AddSkip(inner); Ui::AddSkip(inner); - const auto wrap = inner->add(std::move(ownedWrap)); - const auto container = wrap->entity(); - wrap->toggle(false, anim::type::instant); + if (ownedWrap) { + inner->add(std::move(ownedWrap)); - const auto session = &participants.front()->session(); - const auto emojiMargin = QMargins( - -st::moderateBoxExpandInnerSkip, - -st::moderateBoxExpandInnerSkip / 2, - 0, - 0); - const auto emojiUp = Ui::Text::SingleCustomEmoji( - session->data().customEmojiManager().registerInternalEmoji( - st::moderateBoxExpandIcon, - emojiMargin, - false)); - const auto emojiDown = Ui::Text::SingleCustomEmoji( - session->data().customEmojiManager().registerInternalEmoji( - st::moderateBoxExpandIconDown, - emojiMargin, - false)); + const auto container = wrap->entity(); + wrap->toggle(false, anim::type::instant); - auto label = object_ptr( - inner, - QString(), - st::moderateBoxDividerLabel); - const auto raw = label.data(); + const auto session = &participants.front()->session(); + const auto emojiMargin = QMargins( + -st::moderateBoxExpandInnerSkip, + -st::moderateBoxExpandInnerSkip / 2, + 0, + 0); + const auto emojiUp = Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::moderateBoxExpandIcon, + emojiMargin, + false)); + const auto emojiDown = Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::moderateBoxExpandIconDown, + emojiMargin, + false)); - auto &lifetime = wrap->lifetime(); - const auto scrollLifetime = lifetime.make_state(); - label->setClickHandlerFilter([=]( - const ClickHandlerPtr &handler, - Qt::MouseButton button) { - if (button != Qt::LeftButton) { - return false; - } - wrap->toggle(!wrap->toggled(), anim::type::normal); - { - inner->heightValue() | rpl::start_with_next([=] { - if (!wrap->animating()) { - scrollLifetime->destroy(); - Ui::PostponeCall(crl::guard(box, [=] { + auto label = object_ptr( + inner, + QString(), + st::moderateBoxDividerLabel); + const auto raw = label.data(); + + auto &lifetime = wrap->lifetime(); + const auto scrollLifetime = lifetime.make_state(); + label->setClickHandlerFilter([=]( + const ClickHandlerPtr &handler, + Qt::MouseButton button) { + if (button != Qt::LeftButton) { + return false; + } + wrap->toggle(!wrap->toggled(), anim::type::normal); + { + inner->heightValue() | rpl::start_with_next([=] { + if (!wrap->animating()) { + scrollLifetime->destroy(); + Ui::PostponeCall(crl::guard(box, [=] { + box->scrollToY(std::numeric_limits::max()); + })); + } else { box->scrollToY(std::numeric_limits::max()); - })); - } else { - box->scrollToY(std::numeric_limits::max()); - } - }, *scrollLifetime); - } - return true; - }); - wrap->toggledValue( - ) | rpl::map([isSingle, emojiUp, emojiDown](bool toggled) { - return ((toggled && isSingle) - ? tr::lng_restrict_user_part - : (toggled && !isSingle) - ? tr::lng_restrict_users_part - : isSingle - ? tr::lng_restrict_user_full - : tr::lng_restrict_users_full)( - lt_emoji, - rpl::single(toggled ? emojiUp : emojiDown), - Ui::Text::WithEntities); - }) | rpl::flatten_latest( - ) | rpl::start_with_next([=](const TextWithEntities &text) { - raw->setMarkedText( - Ui::Text::Link(text, u"internal:"_q), - Core::TextContext({ .session = session })); - }, label->lifetime()); + } + }, *scrollLifetime); + } + return true; + }); + wrap->toggledValue( + ) | rpl::map([isSingle, emojiUp, emojiDown](bool toggled) { + return ((toggled && isSingle) + ? tr::lng_restrict_user_part + : (toggled && !isSingle) + ? tr::lng_restrict_users_part + : isSingle + ? tr::lng_restrict_user_full + : tr::lng_restrict_users_full)( + lt_emoji, + rpl::single(toggled ? emojiUp : emojiDown), + Ui::Text::WithEntities); + }) | rpl::flatten_latest( + ) | rpl::start_with_next([=](const TextWithEntities &text) { + raw->setMarkedText( + Ui::Text::Link(text, u"internal:"_q), + Core::TextContext({ .session = session })); + }, label->lifetime()); - Ui::AddSkip(inner); - inner->add(object_ptr( - inner, - std::move(label), - st::defaultBoxDividerLabelPadding, - RectPart::Top | RectPart::Bottom)); + Ui::AddSkip(inner); + inner->add(object_ptr( + inner, + std::move(label), + st::defaultBoxDividerLabelPadding, + RectPart::Top | RectPart::Bottom)); - using Flag = ChatRestriction; - using Flags = ChatRestrictions; - const auto peer = items.front()->history()->peer; - const auto chat = peer->asChat(); - const auto channel = peer->asChannel(); - const auto defaultRestrictions = chat - ? chat->defaultRestrictions() - : channel->defaultRestrictions(); - const auto prepareFlags = FixDependentRestrictions( - defaultRestrictions - | ((channel && channel->isPublic()) - ? (Flag::ChangeInfo | Flag::PinMessages) - : Flags(0))); - const auto disabledMessages = [&] { - auto result = base::flat_map(); - { - const auto disabled = FixDependentRestrictions( - defaultRestrictions - | ((channel && channel->isPublic()) - ? (Flag::ChangeInfo | Flag::PinMessages) - : Flags(0))); - result.emplace( - disabled, - tr::lng_rights_restriction_for_all(tr::now)); - } - return result; - }(); + using Flag = ChatRestriction; + using Flags = ChatRestrictions; + const auto chat = peer->asChat(); + const auto channel = peer->asChannel(); + const auto defaultRestrictions = chat + ? chat->defaultRestrictions() + : channel->defaultRestrictions(); + const auto prepareFlags = FixDependentRestrictions( + defaultRestrictions + | ((channel && channel->isPublic()) + ? (Flag::ChangeInfo | Flag::PinMessages) + : Flags(0))); + const auto disabledMessages = [&] { + auto result = base::flat_map(); + { + const auto disabled = FixDependentRestrictions( + defaultRestrictions + | ((channel && channel->isPublic()) + ? (Flag::ChangeInfo | Flag::PinMessages) + : Flags(0))); + result.emplace( + disabled, + tr::lng_rights_restriction_for_all(tr::now)); + } + return result; + }(); - Ui::AddSubsectionTitle( - inner, - rpl::conditional( - rpl::single(isSingle), - tr::lng_restrict_users_part_single_header(), - tr::lng_restrict_users_part_header( - lt_count, - rpl::single(participants.size()) | tr::to_count()))); - auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions( - box, - prepareFlags, - disabledMessages, - { .isForum = peer->isForum() }); - std::move(changes) | rpl::start_with_next([=] { - ban->setChecked(true); - }, ban->lifetime()); - Ui::AddSkip(container); - Ui::AddDivider(container); - Ui::AddSkip(container); - container->add(std::move(checkboxes)); + Ui::AddSubsectionTitle( + inner, + rpl::conditional( + rpl::single(isSingle), + tr::lng_restrict_users_part_single_header(), + tr::lng_restrict_users_part_header( + lt_count, + rpl::single(participants.size()) | tr::to_count()))); + auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions( + box, + prepareFlags, + disabledMessages, + { .isForum = peer->isForum() }); + computeRestrictions = getRestrictions; + std::move(changes) | rpl::start_with_next([=] { + ban->setChecked(true); + }, ban->lifetime()); + Ui::AddSkip(container); + Ui::AddDivider(container); + Ui::AddSkip(container); + container->add(std::move(checkboxes)); + } // Handle confirmation manually. confirms->events() | rpl::start_with_next([=] { if (ban->checked() && controller->collectRequests) { - const auto kick = !wrap->toggled(); - const auto restrictions = getRestrictions(); + const auto kick = !wrap || !wrap->toggled(); + const auto restrictions = computeRestrictions + ? computeRestrictions() + : ChatRestrictions(); const auto request = [=]( not_null peer, not_null channel) { @@ -532,10 +544,15 @@ void CreateModerateMessagesBox( nullptr, nullptr); } else { - channel->session().api().chatParticipants().kick( - channel, - peer, - { channel->restrictions(), 0 }); + const auto block = channel->isMonoforum() + ? channel->monoforumBroadcast() + : channel.get(); + if (block) { + block->session().api().chatParticipants().kick( + block, + peer, + { block->restrictions(), 0 }); + } } }; sequentiallyRequest(request, controller->collectRequests()); diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 64ce31f976..5ba5079c4f 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -127,6 +127,7 @@ constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000); constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000); constexpr auto kResaleGiftsPerPage = 50; constexpr auto kFiltersCount = 4; +constexpr auto kResellPriceCacheLifetime = 60 * crl::time(1000); using namespace HistoryView; using namespace Info::PeerGifts; @@ -220,6 +221,33 @@ struct GiftDetails { bool byStars = false; }; +struct SessionResalePrices { + explicit SessionResalePrices(not_null session) + : api(std::make_unique(session->user())) { + } + + std::unique_ptr api; + base::flat_map prices; + std::vector> waiting; + rpl::lifetime requestLifetime; + crl::time lastReceived = 0; +}; + +[[nodiscard]] not_null ResalePrices( + not_null session) { + static auto result = base::flat_map< + not_null, + std::unique_ptr>(); + if (const auto i = result.find(session); i != end(result)) { + return i->second.get(); + } + const auto i = result.emplace( + session, + std::make_unique(session)).first; + session->lifetime().add([session] { result.remove(session); }); + return i->second.get(); +} + class PeerRow final : public PeerListRow { public: using PeerListRow::PeerListRow; @@ -4381,6 +4409,55 @@ void ShowUniqueGiftWearBox( })); } +void PreloadUniqueGiftResellPrices(not_null session) { + const auto entry = ResalePrices(session); + const auto now = crl::now(); + const auto makeRequest = entry->prices.empty() + || (now - entry->lastReceived >= kResellPriceCacheLifetime); + if (!makeRequest || entry->requestLifetime) { + return; + } + const auto finish = [=] { + entry->requestLifetime.destroy(); + entry->lastReceived = crl::now(); + for (const auto &callback : base::take(entry->waiting)) { + callback(); + } + }; + entry->requestLifetime = entry->api->requestStarGifts( + ) | rpl::start_with_error_done(finish, [=] { + const auto &gifts = entry->api->starGifts(); + entry->prices.reserve(gifts.size()); + for (auto &gift : gifts) { + if (!gift.resellTitle.isEmpty() && gift.starsResellMin > 0) { + entry->prices[gift.resellTitle] = gift.starsResellMin; + } + } + finish(); + }); +} + +void InvokeWithUniqueGiftResellPrice( + not_null session, + const QString &title, + Fn callback) { + PreloadUniqueGiftResellPrices(session); + + const auto finish = [=] { + const auto entry = ResalePrices(session); + Assert(entry->lastReceived != 0); + + const auto i = entry->prices.find(title); + callback((i != end(entry->prices)) ? i->second : 0); + }; + const auto entry = ResalePrices(session); + if (entry->lastReceived) { + finish(); + } else { + entry->waiting.push_back(finish); + } +} + void UpdateGiftSellPrice( std::shared_ptr show, std::shared_ptr unique, @@ -4422,6 +4499,132 @@ void UpdateGiftSellPrice( }).send(); } +void UniqueGiftSellBox( + not_null box, + std::shared_ptr show, + std::shared_ptr unique, + Data::SavedStarGiftId savedId, + int price, + Settings::GiftWearBoxStyleOverride st) { + box->setTitle(tr::lng_gift_sell_title()); + box->setStyle(st.box ? *st.box : st::upgradeGiftBox); + box->setWidth(st::boxWideWidth); + + box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] { + box->closeBox(); + }); + const auto priceNow = unique->starsForResale; + const auto name = Data::UniqueGiftName(*unique); + const auto slug = unique->slug; + + const auto session = &show->session(); + AddSubsectionTitle( + box->verticalLayout(), + tr::lng_gift_sell_placeholder(), + (st::boxRowPadding - QMargins( + st::defaultSubsectionTitlePadding.left(), + 0, + st::defaultSubsectionTitlePadding.right(), + st::defaultSubsectionTitlePadding.bottom()))); + const auto &appConfig = session->appConfig(); + const auto limit = appConfig.giftResalePriceMax(); + const auto minimal = appConfig.giftResalePriceMin(); + const auto thousandths = appConfig.giftResaleReceiveThousandths(); + const auto wrap = box->addRow(object_ptr( + box, + st::editTagField.heightMin)); + auto owned = object_ptr( + wrap, + st::editTagField, + rpl::single(QString()), + QString::number(priceNow ? priceNow : price ? price : minimal), + limit); + const auto field = owned.data(); + wrap->widthValue() | rpl::start_with_next([=](int width) { + field->move(0, 0); + field->resize(width, field->height()); + wrap->resize(width, field->height()); + }, wrap->lifetime()); + field->paintRequest() | rpl::start_with_next([=](QRect clip) { + auto p = QPainter(field); + st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width()); + }, field->lifetime()); + field->selectAll(); + box->setFocusCallback([=] { + field->setFocusFast(); + }); + + const auto errors = box->lifetime().make_state< + rpl::event_stream<> + >(); + auto goods = rpl::merge( + rpl::single(rpl::empty) | rpl::map_to(true), + base::qt_signal_producer( + field, + &Ui::NumberInput::changed + ) | rpl::map_to(true), + errors->events() | rpl::map_to(false) + ) | rpl::start_spawning(box->lifetime()); + auto text = rpl::duplicate(goods) | rpl::map([=](bool good) { + const auto value = field->getLastText().toInt(); + const auto receive = (int64(value) * thousandths) / 1000; + return !good + ? tr::lng_gift_sell_min_price( + tr::now, + lt_count, + minimal, + Ui::Text::RichLangValue) + : (value >= minimal) + ? tr::lng_gift_sell_amount( + tr::now, + lt_count, + receive, + Ui::Text::RichLangValue) + : tr::lng_gift_sell_about( + tr::now, + lt_percent, + TextWithEntities{ u"%1%"_q.arg(thousandths / 10.) }, + Ui::Text::RichLangValue); + }); + const auto details = box->addRow(object_ptr( + box, + std::move(text) | rpl::after_next([=] { + box->verticalLayout()->resizeToWidth(box->width()); + }), + st::boxLabel)); + Ui::AddSkip(box->verticalLayout()); + + rpl::duplicate(goods) | rpl::start_with_next([=](bool good) { + details->setTextColorOverride( + good ? st::windowSubTextFg->c : st::boxTextFgError->c); + }, details->lifetime()); + + QObject::connect(field, &NumberInput::submitted, [=] { + const auto count = field->getLastText().toInt(); + if (count < minimal) { + field->showError(); + errors->fire({}); + return; + } + box->closeBox(); + UpdateGiftSellPrice(show, unique, savedId, count); + }); + const auto button = box->addButton(priceNow + ? tr::lng_gift_sell_update() + : tr::lng_gift_sell_put(), [=] { field->submitted({}); }); + rpl::combine( + box->widthValue(), + button->widthValue() + ) | rpl::start_with_next([=](int outer, int inner) { + const auto padding = st::giftBox.buttonPadding; + const auto wanted = outer - padding.left() - padding.right(); + if (inner != wanted) { + button->resizeToWidth(wanted); + button->moveToLeft(padding.left(), padding.top()); + } + }, box->lifetime()); +} + void ShowUniqueGiftSellBox( std::shared_ptr show, std::shared_ptr unique, @@ -4430,125 +4633,11 @@ void ShowUniqueGiftSellBox( if (ShowResaleGiftLater(show, unique)) { return; } - show->show(Box([=](not_null box) { - box->setTitle(tr::lng_gift_sell_title()); - box->setStyle(st.box ? *st.box : st::upgradeGiftBox); - box->setWidth(st::boxWideWidth); - - box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] { - box->closeBox(); - }); - const auto priceNow = unique->starsForResale; - const auto name = Data::UniqueGiftName(*unique); - const auto slug = unique->slug; - - const auto session = &show->session(); - AddSubsectionTitle( - box->verticalLayout(), - tr::lng_gift_sell_placeholder(), - (st::boxRowPadding - QMargins( - st::defaultSubsectionTitlePadding.left(), - 0, - st::defaultSubsectionTitlePadding.right(), - st::defaultSubsectionTitlePadding.bottom()))); - const auto &appConfig = session->appConfig(); - const auto limit = appConfig.giftResalePriceMax(); - const auto minimal = appConfig.giftResalePriceMin(); - const auto thousandths = appConfig.giftResaleReceiveThousandths(); - const auto wrap = box->addRow(object_ptr( - box, - st::editTagField.heightMin)); - auto owned = object_ptr( - wrap, - st::editTagField, - rpl::single(QString()), - QString::number(priceNow ? priceNow : minimal), - limit); - const auto field = owned.data(); - wrap->widthValue() | rpl::start_with_next([=](int width) { - field->move(0, 0); - field->resize(width, field->height()); - wrap->resize(width, field->height()); - }, wrap->lifetime()); - field->paintRequest() | rpl::start_with_next([=](QRect clip) { - auto p = QPainter(field); - st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width()); - }, field->lifetime()); - field->selectAll(); - box->setFocusCallback([=] { - field->setFocusFast(); - }); - - const auto errors = box->lifetime().make_state< - rpl::event_stream<> - >(); - auto goods = rpl::merge( - rpl::single(rpl::empty) | rpl::map_to(true), - base::qt_signal_producer( - field, - &Ui::NumberInput::changed - ) | rpl::map_to(true), - errors->events() | rpl::map_to(false) - ) | rpl::start_spawning(box->lifetime()); - auto text = rpl::duplicate(goods) | rpl::map([=](bool good) { - const auto value = field->getLastText().toInt(); - const auto receive = (int64(value) * thousandths) / 1000; - return !good - ? tr::lng_gift_sell_min_price( - tr::now, - lt_count, - minimal, - Ui::Text::RichLangValue) - : (value >= minimal) - ? tr::lng_gift_sell_amount( - tr::now, - lt_count, - receive, - Ui::Text::RichLangValue) - : tr::lng_gift_sell_about( - tr::now, - lt_percent, - TextWithEntities{ u"%1%"_q.arg(thousandths / 10.) }, - Ui::Text::RichLangValue); - }); - const auto details = box->addRow(object_ptr( - box, - std::move(text) | rpl::after_next([=] { - box->verticalLayout()->resizeToWidth(box->width()); - }), - st::boxLabel)); - Ui::AddSkip(box->verticalLayout()); - - rpl::duplicate(goods) | rpl::start_with_next([=](bool good) { - details->setTextColorOverride( - good ? st::windowSubTextFg->c : st::boxTextFgError->c); - }, details->lifetime()); - - QObject::connect(field, &NumberInput::submitted, [=] { - const auto count = field->getLastText().toInt(); - if (count < minimal) { - field->showError(); - errors->fire({}); - return; - } - box->closeBox(); - UpdateGiftSellPrice(show, unique, savedId, count); - }); - const auto button = box->addButton(priceNow - ? tr::lng_gift_sell_update() - : tr::lng_gift_sell_put(), [=] { field->submitted({}); }); - rpl::combine( - box->widthValue(), - button->widthValue() - ) | rpl::start_with_next([=](int outer, int inner) { - const auto padding = st::giftBox.buttonPadding; - const auto wanted = outer - padding.left() - padding.right(); - if (inner != wanted) { - button->resizeToWidth(wanted); - button->moveToLeft(padding.left(), padding.top()); - } - }, box->lifetime()); - })); + const auto session = &show->session(); + const auto &title = unique->title; + InvokeWithUniqueGiftResellPrice(session, title, [=](int price) { + show->show(Box(UniqueGiftSellBox, show, unique, savedId, price, st)); + }); } void GiftReleasedByHandler(not_null peer) { diff --git a/Telegram/SourceFiles/boxes/star_gift_box.h b/Telegram/SourceFiles/boxes/star_gift_box.h index 78ee14a028..b3df5b5b87 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_box.h @@ -21,6 +21,7 @@ class SavedStarGiftId; } // namespace Data namespace Main { +class Session; class SessionShow; } // namespace Main @@ -71,6 +72,8 @@ void ShowUniqueGiftWearBox( const Data::UniqueGift &gift, Settings::GiftWearBoxStyleOverride st); +void PreloadUniqueGiftResellPrices(not_null session); + void UpdateGiftSellPrice( std::shared_ptr show, std::shared_ptr unique, diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index 02db10e63e..3375799084 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -768,12 +768,14 @@ void StickerSetBox::updateButtons() { &st::menuIconManage); }); }(); - const auto addPackOwner = [=](const std::shared_ptr> &menu) + const auto addPackIdActions = [=](const std::shared_ptr> &menu) { if (type == Data::StickersType::Stickers || type == Data::StickersType::Emoji) { + const auto &settings = AyuSettings::getInstance(); const auto weak = Ui::MakeWeak(this); const auto session = _session; - const auto innerId = _inner->setId() >> 32; + const auto setId = _inner->setId(); + const auto innerId = setId >> 32; (*menu)->addAction( tr::ayu_MessageDetailsPackOwnerPC(tr::now), @@ -816,6 +818,26 @@ void StickerSetBox::updateButtons() { }); }, &st::menuIconProfile); + + if (settings.showPeerId != 0) { + (*menu)->addAction( + tr::ayu_ContextCopyID(tr::now), + [weak, session, setId] + { + if (!weak) { + return; + } + + const auto strongInner = weak.data(); + if (!strongInner) { + return; + } + + QGuiApplication::clipboard()->setText(QString::number(setId)); + strongInner->showToast(tr::ayu_IDCopiedToast(tr::now)); + }, + &st::menuIconCopy); + } } }; if (_inner->notInstalled()) { @@ -865,7 +887,7 @@ void StickerSetBox::updateButtons() { : tr::lng_stickers_share_pack)(tr::now), [=] { share(); closeBox(); }, &st::menuIconShare); - addPackOwner(menu); + addPackIdActions(menu); (*menu)->popup(QCursor::pos()); return true; }); @@ -918,7 +940,7 @@ void StickerSetBox::updateButtons() { archive, &st::menuIconArchive); } - addPackOwner(menu); + addPackIdActions(menu); (*menu)->popup(QCursor::pos()); return true; }); @@ -1486,6 +1508,16 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) { QGuiApplication::clipboard()->setMimeData(data.release()); } }, &st::menuIconCopy); + + const auto &settings = AyuSettings::getInstance(); + if (settings.showPeerId != 0) { + _menu->addAction(tr::ayu_ContextCopyID(tr::now), + [=] + { + QGuiApplication::clipboard()->setText(QString::number(_pack[index]->id)); + }, + &st::menuIconCopy); + } } } else if (details.type != SendMenu::Type::Disabled) { const auto document = _pack[index]; diff --git a/Telegram/SourceFiles/boxes/transfer_gift_box.cpp b/Telegram/SourceFiles/boxes/transfer_gift_box.cpp index 6566f68703..d94c9d01e7 100644 --- a/Telegram/SourceFiles/boxes/transfer_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/transfer_gift_box.cpp @@ -425,8 +425,6 @@ void TransferGift( Data::SavedStarGiftId savedId, Fn done, bool skipPaymentForm = false) { - Expects(to->isUser()); - const auto session = &window->session(); const auto weak = base::make_weak(window); auto formDone = [=]( diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index 43295e1965..520b1282a1 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -17,6 +17,7 @@ constexpr auto kSendReactionEmojiProperty = 0x04; constexpr auto kReactionsCountEmojiProperty = 0x05; constexpr auto kDocumentFilenameTooltipProperty = 0x06; constexpr auto kPhoneNumberLinkProperty = 0x07; +constexpr auto kTodoListItemIdProperty = 0x08; namespace Ui { class Show; diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 424c724925..9c475823cd 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppFile = "AyuGram"_cs; -constexpr auto AppVersion = 5016003; -constexpr auto AppVersionStr = "5.16.3"; +constexpr auto AppVersion = 5016004; +constexpr auto AppVersionStr = "5.16.4"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/data/components/sponsored_messages.cpp b/Telegram/SourceFiles/data/components/sponsored_messages.cpp index a0d308f814..a8965927ca 100644 --- a/Telegram/SourceFiles/data/components/sponsored_messages.cpp +++ b/Telegram/SourceFiles/data/components/sponsored_messages.cpp @@ -32,8 +32,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { namespace { +constexpr auto kMs = crl::time(1000); constexpr auto kRequestTimeLimit = 5 * 60 * crl::time(1000); +const auto kFlaggedPreload = ((MediaPreload*)quintptr(0x01)); + [[nodiscard]] bool TooEarlyForRequest(crl::time received) { return (received > 0) && (received + kRequestTimeLimit > crl::now()); } @@ -77,17 +80,21 @@ void SponsoredMessages::clear() { void SponsoredMessages::clearOldRequests() { const auto now = crl::now(); - while (true) { - const auto i = ranges::find_if(_requests, [&](const auto &value) { - const auto &request = value.second; - return !request.requestId - && (request.lastReceived + kRequestTimeLimit <= now); - }); - if (i == end(_requests)) { - break; + const auto clear = [&](auto &requests) { + while (true) { + const auto i = ranges::find_if(requests, [&](const auto &value) { + const auto &request = value.second; + return !request.requestId + && (request.lastReceived + kRequestTimeLimit <= now); + }); + if (i == end(requests)) { + break; + } + requests.erase(i); } - _requests.erase(i); - } + }; + clear(_requests); + clear(_requestsForVideo); } SponsoredMessages::AppendResult SponsoredMessages::append( @@ -241,6 +248,16 @@ bool SponsoredMessages::canHaveFor(not_null history) const { return false; } +bool SponsoredMessages::canHaveFor(not_null item) const { + const auto &settings = AyuSettings::getInstance(); + if (settings.disableAds) { + return false; + } + + return item->history()->peer->isBroadcast() + && item->isRegular(); +} + bool SponsoredMessages::isTopBarFor(not_null history) const { const auto &settings = AyuSettings::getInstance(); if (settings.disableAds) { @@ -291,6 +308,78 @@ void SponsoredMessages::request(not_null history, Fn done) { }).send(); } +void SponsoredMessages::requestForVideo( + not_null item, + Fn done) { + Expects(done != nullptr); + + if (!canHaveFor(item)) { + done({}); + return; + } + const auto peer = item->history()->peer; + auto &request = _requestsForVideo[peer]; + if (TooEarlyForRequest(request.lastReceived)) { + auto prepared = prepareForVideo(peer); + if (prepared.list.empty() + || prepared.state.itemIndex < prepared.list.size() + || prepared.state.leftTillShow > 0) { + done(std::move(prepared)); + return; + } + } + request.callbacks.push_back(std::move(done)); + if (request.requestId) { + return; + } + { + const auto it = _dataForVideo.find(peer); + if (it != end(_dataForVideo)) { + auto &list = it->second; + // Don't rebuild currently displayed messages. + const auto proj = [](const Entry &e) { + return e.item != nullptr; + }; + if (ranges::any_of(list.entries, proj)) { + return; + } + } + } + const auto finish = [=] { + const auto i = _requestsForVideo.find(peer); + if (i != end(_requestsForVideo)) { + for (const auto &callback : base::take(i->second.callbacks)) { + callback(prepareForVideo(peer)); + } + } + }; + using Flag = MTPmessages_GetSponsoredMessages::Flag; + request.requestId = _session->api().request( + MTPmessages_GetSponsoredMessages( + MTP_flags(Flag::f_msg_id), + peer->input, + MTP_int(item->id.bare)) + ).done([=](const MTPmessages_sponsoredMessages &result) { + parseForVideo(peer, result); + finish(); + }).fail([=] { + _requestsForVideo.remove(peer); + finish(); + }).send(); +} + +void SponsoredMessages::updateForVideo( + FullMsgId itemId, + SponsoredForVideoState state) { + if (state.initial()) { + return; + } + const auto i = _dataForVideo.find(_session->data().peer(itemId.peer)); + if (i != end(_dataForVideo)) { + i->second.state = state; + } +} + void SponsoredMessages::parse( not_null history, const MTPmessages_sponsoredMessages &list) { @@ -306,12 +395,9 @@ void SponsoredMessages::parse( _session->data().processChats(data.vchats()); const auto &messages = data.vmessages().v; - auto &list = _data.emplace(history, List()).first->second; + auto &list = _data.emplace(history).first->second; list.entries.clear(); list.received = crl::now(); - for (const auto &message : messages) { - append(history, list, message); - } if (const auto postsBetween = data.vposts_between()) { list.postsBetween = postsBetween->v; list.state = State::InjectToMiddle; @@ -320,10 +406,66 @@ void SponsoredMessages::parse( ? State::AppendToEnd : State::AppendToTopBar; } + for (const auto &message : messages) { + append([=] { + return &_data[history].entries; + }, history, message); + } }, [](const MTPDmessages_sponsoredMessagesEmpty &) { }); } +void SponsoredMessages::parseForVideo( + not_null peer, + const MTPmessages_sponsoredMessages &list) { + auto &request = _requestsForVideo[peer]; + request.lastReceived = crl::now(); + request.requestId = 0; + if (!_clearTimer.isActive()) { + _clearTimer.callOnce(kRequestTimeLimit * 2); + } + + list.match([&](const MTPDmessages_sponsoredMessages &data) { + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + + const auto history = _session->data().history(peer); + const auto &messages = data.vmessages().v; + auto &list = _dataForVideo.emplace(peer).first->second; + list.entries.clear(); + list.received = crl::now(); + list.startDelay = data.vstart_delay().value_or_empty() * kMs; + list.betweenDelay = data.vbetween_delay().value_or_empty() * kMs; + for (const auto &message : messages) { + append([=] { + return &_dataForVideo[peer].entries; + }, history, message); + } + }, [](const MTPDmessages_sponsoredMessagesEmpty &) { + }); +} + +SponsoredForVideo SponsoredMessages::prepareForVideo( + not_null peer) { + const auto &settings = AyuSettings::getInstance(); + if (settings.disableAds) { + return {}; + } + + const auto i = _dataForVideo.find(peer); + if (i == end(_dataForVideo) || i->second.entries.empty()) { + return {}; + } + return SponsoredForVideo{ + .list = i->second.entries | ranges::views::transform( + &Entry::sponsored + ) | ranges::to_vector, + .startDelay = i->second.startDelay, + .betweenDelay = i->second.betweenDelay, + .state = i->second.state, + }; +} + FullMsgId SponsoredMessages::fillTopBar( not_null history, not_null widget) { @@ -373,8 +515,8 @@ rpl::producer<> SponsoredMessages::itemRemoved(const FullMsgId &fullId) { } void SponsoredMessages::append( + Fn*>()> entries, not_null history, - List &list, const MTPSponsoredMessage &message) { const auto &data = message.data(); const auto randomId = data.vrandom_id().v; @@ -385,14 +527,14 @@ void SponsoredMessages::append( data.vmedia()->match([&](const MTPDmessageMediaPhoto &media) { if (const auto tlPhoto = media.vphoto()) { tlPhoto->match([&](const MTPDphoto &data) { - mediaPhoto = history->owner().processPhoto(data); + mediaPhoto = _session->data().processPhoto(data); }, [](const MTPDphotoEmpty &) { }); } }, [&](const MTPDmessageMediaDocument &media) { if (const auto tlDocument = media.vdocument()) { tlDocument->match([&](const MTPDdocument &data) { - const auto d = history->owner().processDocument( + const auto d = _session->data().processDocument( data, media.valt_documents()); if (d->isVideoFile() @@ -413,7 +555,7 @@ void SponsoredMessages::append( .link = qs(data.vurl()), .buttonText = qs(data.vbutton_text()), .photoId = data.vphoto() - ? history->session().data().processPhoto(*data.vphoto())->id + ? _session->data().processPhoto(*data.vphoto())->id : PhotoId(0), .mediaPhotoId = (mediaPhoto ? mediaPhoto->id : 0), .mediaDocumentId = (mediaDocument ? mediaDocument->id : 0), @@ -449,25 +591,24 @@ void SponsoredMessages::append( .link = from.link, .sponsorInfo = std::move(sponsorInfo), .additionalInfo = std::move(additionalInfo), + .durationMin = data.vmin_display_duration().value_or_empty() * kMs, + .durationMax = data.vmax_display_duration().value_or_empty() * kMs, }; - list.entries.push_back({ - .sponsored = std::move(sharedMessage), - }); - auto &entry = list.entries.back(); - const auto itemId = entry.itemFullId = FullMsgId( + const auto itemId = FullMsgId( history->peer->id, _session->data().nextLocalMessageId()); + const auto list = entries(); + list->push_back({ + .itemFullId = itemId, + .sponsored = std::move(sharedMessage), + }); + auto &entry = list->back(); const auto fileOrigin = FileOrigin(); // No way to refresh in ads. - static const auto kFlaggedPreload = ((MediaPreload*)quintptr(0x01)); const auto preloaded = [=] { - const auto i = _data.find(history); - if (i == end(_data)) { - return; - } - auto &entries = i->second.entries; - const auto j = ranges::find(entries, itemId, &Entry::itemFullId); - if (j == end(entries)) { + const auto list = entries(); + const auto j = ranges::find(*list, itemId, &Entry::itemFullId); + if (j == end(*list)) { return; } auto &entry = *j; @@ -565,7 +706,11 @@ SponsoredMessages::Details SponsoredMessages::lookupDetails( if (!entryPtr) { return {}; } - const auto &data = entryPtr->sponsored; + return lookupDetails(entryPtr->sponsored); +} + +SponsoredMessages::Details SponsoredMessages::lookupDetails( + const SponsoredMessage &data) const { return { .info = Prepare(data), .link = data.link, diff --git a/Telegram/SourceFiles/data/components/sponsored_messages.h b/Telegram/SourceFiles/data/components/sponsored_messages.h index 6c875c081c..b2a17ad0cb 100644 --- a/Telegram/SourceFiles/data/components/sponsored_messages.h +++ b/Telegram/SourceFiles/data/components/sponsored_messages.h @@ -67,10 +67,12 @@ struct SponsoredMessage { QByteArray randomId; SponsoredFrom from; TextWithEntities textWithEntities; - History *history = nullptr; + not_null history; QString link; TextWithEntities sponsorInfo; TextWithEntities additionalInfo; + crl::time durationMin = 0; + crl::time durationMax = 0; }; struct SponsoredMessageDetails { @@ -92,6 +94,23 @@ struct SponsoredReportAction { Fn)> callback; }; +struct SponsoredForVideoState { + int itemIndex = 0; + crl::time leftTillShow = 0; + + [[nodiscard]] bool initial() const { + return !itemIndex && !leftTillShow; + } +}; + +struct SponsoredForVideo { + std::vector list; + crl::time startDelay = 0; + crl::time betweenDelay = 0; + + SponsoredForVideoState state; +}; + class SponsoredMessages final { public: enum class AppendResult { @@ -111,10 +130,18 @@ public: ~SponsoredMessages(); [[nodiscard]] bool canHaveFor(not_null history) const; + [[nodiscard]] bool canHaveFor(not_null item) const; [[nodiscard]] bool isTopBarFor(not_null history) const; void request(not_null history, Fn done); + void requestForVideo( + not_null item, + Fn done); + void updateForVideo( + FullMsgId itemId, + SponsoredForVideoState state); void clearItems(not_null history); [[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const; + [[nodiscard]] Details lookupDetails(const SponsoredMessage &data) const; [[nodiscard]] Details lookupDetails( const Api::SponsoredSearchResult &data) const; void clicked(const FullMsgId &fullId, bool isMedia, bool isFullscreen); @@ -166,18 +193,35 @@ private: int postsBetween = 0; State state = State::None; }; + struct ListForVideo { + std::vector entries; + crl::time received = 0; + crl::time startDelay = 0; + crl::time betweenDelay = 0; + SponsoredForVideoState state; + }; struct Request { mtpRequestId requestId = 0; crl::time lastReceived = 0; }; + struct RequestForVideo { + std::vector> callbacks; + mtpRequestId requestId = 0; + crl::time lastReceived = 0; + }; void parse( not_null history, const MTPmessages_sponsoredMessages &list); + void parseForVideo( + not_null peer, + const MTPmessages_sponsoredMessages &list); void append( + Fn*>()> entries, not_null history, - List &list, const MTPSponsoredMessage &message); + [[nodiscard]] SponsoredForVideo prepareForVideo( + not_null peer); void clearOldRequests(); const Entry *find(const FullMsgId &fullId) const; @@ -189,6 +233,9 @@ private: base::flat_map, Request> _requests; base::flat_map _viewRequests; + base::flat_map, ListForVideo> _dataForVideo; + base::flat_map, RequestForVideo> _requestsForVideo; + rpl::event_stream _itemRemoved; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp index e18a18e7d3..964082f2cc 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -72,7 +72,7 @@ namespace { | (data.is_send_audios() ? Flag::SendMusic : Flag()) | (data.is_send_voices() ? Flag::SendVoiceMessages : Flag()) | (data.is_send_docs() ? Flag::SendFiles : Flag()) - | (data.is_send_messages() ? Flag::SendOther : Flag()) + | (data.is_send_plain() ? Flag::SendOther : Flag()) | (data.is_embed_links() ? Flag::EmbedLinks : Flag()) | (data.is_change_info() ? Flag::ChangeInfo : Flag()) | (data.is_invite_users() ? Flag::AddParticipants : Flag()) @@ -142,7 +142,7 @@ MTPChatBannedRights RestrictionsToMTP(ChatRestrictionsInfo info) { | ((flags & R::SendMusic) ? Flag::f_send_audios : Flag()) | ((flags & R::SendVoiceMessages) ? Flag::f_send_voices : Flag()) | ((flags & R::SendFiles) ? Flag::f_send_docs : Flag()) - | ((flags & R::SendOther) ? Flag::f_send_messages : Flag()) + | ((flags & R::SendOther) ? Flag::f_send_plain : Flag()) | ((flags & R::EmbedLinks) ? Flag::f_embed_links : Flag()) | ((flags & R::ChangeInfo) ? Flag::f_change_info : Flag()) | ((flags & R::AddParticipants) ? Flag::f_invite_users : Flag()) diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index eb85f931b1..2ebbc0dfdf 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -92,7 +92,8 @@ MTPInputReplyTo ReplyToForMTP( : Flag()) | (quoteEntities.v.isEmpty() ? Flag() - : Flag::f_quote_entities)), + : Flag::f_quote_entities) + | (replyTo.todoItemId ? Flag::f_todo_item_id : Flag())), MTP_int(replyTo.messageId ? replyTo.messageId.msg : 0), MTP_int(replyTo.topicRootId), (external @@ -103,7 +104,8 @@ MTPInputReplyTo ReplyToForMTP( MTP_int(replyTo.quoteOffset), (replyToMonoforumPeerId ? history->owner().peer(replyToMonoforumPeerId)->input - : MTPInputPeer())); + : MTPInputPeer()), + MTP_int(replyTo.todoItemId)); } else if (history->peer->amMonoforumAdmin() && replyTo.monoforumPeerId) { const auto replyToMonoforumPeer = replyTo.monoforumPeerId diff --git a/Telegram/SourceFiles/data/data_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h index bee8324194..3627fd4d0c 100644 --- a/Telegram/SourceFiles/data/data_msg_id.h +++ b/Telegram/SourceFiles/data/data_msg_id.h @@ -172,6 +172,19 @@ inline QDebug operator<<(QDebug debug, const FullMsgId &fullMsgId) { Q_DECLARE_METATYPE(FullMsgId); +struct MessageHighlightId { + TextWithEntities quote; + int quoteOffset = 0; + int todoItemId = 0; + + [[nodiscard]] bool empty() const { + return quote.empty() && !todoItemId; + } + [[nodiscard]] friend inline bool operator==( + const MessageHighlightId &a, + const MessageHighlightId &b) = default; +}; + struct FullReplyTo { FullMsgId messageId; TextWithEntities quote; @@ -179,7 +192,11 @@ struct FullReplyTo { MsgId topicRootId = 0; PeerId monoforumPeerId = 0; int quoteOffset = 0; + int todoItemId = 0; + [[nodiscard]] MessageHighlightId highlight() const { + return { quote, quoteOffset, todoItemId }; + } [[nodiscard]] bool replying() const { return messageId || (storyId && storyId.peer); } diff --git a/Telegram/SourceFiles/data/data_saved_messages.cpp b/Telegram/SourceFiles/data/data_saved_messages.cpp index 1cae8972db..a7ee009257 100644 --- a/Telegram/SourceFiles/data/data_saved_messages.cpp +++ b/Telegram/SourceFiles/data/data_saved_messages.cpp @@ -96,17 +96,6 @@ Thread *SavedMessages::activeSubsectionThread() const { return _activeSubsectionSublist; } -Dialogs::UnreadState SavedMessages::unreadStateWithParentMuted() const { - auto result = _chatsList.unreadState(); - if (_owningHistory->muted()) { - result.chatsMuted = result.chats; - result.marksMuted = result.marks; - result.messagesMuted = result.messages; - result.reactionsMuted = result.reactions; - } - return result; -} - SavedMessages::~SavedMessages() { clear(); } @@ -458,6 +447,9 @@ void SavedMessages::applySublistDeleted(not_null sublistPeer) { if (ranges::contains(_lastSublists, not_null(raw))) { reorderLastSublists(); } + if (_activeSubsectionSublist == raw) { + _activeSubsectionSublist = nullptr; + } _sublistDestroyed.fire(raw); session().changes().sublistUpdated( diff --git a/Telegram/SourceFiles/data/data_saved_messages.h b/Telegram/SourceFiles/data/data_saved_messages.h index 65f0345c7b..fe77fbb232 100644 --- a/Telegram/SourceFiles/data/data_saved_messages.h +++ b/Telegram/SourceFiles/data/data_saved_messages.h @@ -84,8 +84,6 @@ public: void saveActiveSubsectionThread(not_null thread); Thread *activeSubsectionThread() const; - [[nodiscard]] Dialogs::UnreadState unreadStateWithParentMuted() const; - [[nodiscard]] rpl::lifetime &lifetime(); private: diff --git a/Telegram/SourceFiles/data/data_saved_sublist.cpp b/Telegram/SourceFiles/data/data_saved_sublist.cpp index 64ef0886df..7eab828dd4 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.cpp +++ b/Telegram/SourceFiles/data/data_saved_sublist.cpp @@ -217,7 +217,28 @@ void SavedSublist::applyItemRemoved(MsgId id) { if (const auto chatListItem = _chatListMessage.value_or(nullptr)) { if (chatListItem->id == id) { _chatListMessage = std::nullopt; - requestChatListMessage(); + crl::on_main(this, [=] { + // We didn't yet update _list here. + if (_chatListMessage.has_value()) { + return; + } else if (_skippedAfter == 0) { + if (!_list.empty()) { + applyMaybeLast(owner().message( + owningHistory()->peer, + _list.front())); + return; + } else if (_skippedBefore == 0) { + setLastServerMessage(nullptr); + updateChatListExistence(); + return; + } + } + if (_parent->parentChat()) { + requestChatListMessage(); + } else { + loadAround(0); + } + }); } } } @@ -1110,6 +1131,10 @@ void SavedSublist::loadAround(MsgId id) { _list.clear(); if (processMessagesIsEmpty(result)) { _fullCount = _skippedBefore = _skippedAfter = 0; + if (!_parent->parentChat() && !_chatListMessage) { + setLastServerMessage(nullptr); + updateChatListExistence(); + } } else if (id) { Assert(!_list.empty()); if (_list.front() <= id) { @@ -1117,6 +1142,11 @@ void SavedSublist::loadAround(MsgId id) { } else if (_list.back() >= id) { _skippedBefore = 0; } + } else if (!_parent->parentChat() && !_chatListMessage) { + Assert(!_list.empty()); + applyMaybeLast(owner().message( + owningHistory()->peer, + _list.front())); } checkReadTillEnd(); }).fail([=](const MTP::Error &error) { diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index ef8d55c959..f72efbc1ee 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "apiwrap.h" #include "core/application.h" +#include "data/components/top_peers.h" #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_document.h" @@ -425,10 +426,7 @@ void Stories::parseAndApply(const MTPPeerStories &stories) { }; if (result.peer->isSelf() || (result.peer->isChannel() && result.peer->asChannel()->amIn()) - || (result.peer->isUser() - && (result.peer->asUser()->isBot() - || result.peer->asUser()->isContact())) - || result.peer->isServiceUser()) { + || result.peer->isUser()) { const auto hidden = result.peer->hasStoriesHidden(); using List = StorySourcesList; add(hidden ? List::Hidden : List::NotHidden); @@ -1197,7 +1195,11 @@ void Stories::toggleHidden( bool hidden, std::shared_ptr show) { const auto peer = _owner->peer(peerId); - const auto justRemove = peer->isServiceUser() && hidden; + const auto byHints = peer->isUser() + && !peer->asUser()->isBot() + && !peer->asUser()->isContact() + && !peer->asUser()->isServiceUser(); + const auto justRemove = (byHints || peer->isServiceUser()) && hidden; if (peer->hasStoriesHidden() != hidden) { if (!justRemove) { peer->setStoriesHidden(hidden); @@ -1206,6 +1208,9 @@ void Stories::toggleHidden( peer->input, MTP_bool(hidden) )).send(); + if (byHints) { + peer->session().topPeers().remove(peer); + } } const auto name = peer->shortName(); diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 6780fefa54..9436d4e7c9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -244,6 +244,17 @@ dialogsEmptyLabel: FlatLabel(defaultFlatLabel) { align: align(top); textFg: windowSubTextFg; } +dialogEmptyButton: RoundButton(defaultActiveButton) { +} +dialogEmptyButtonSkip: 12px; +dialogEmptyButtonLabel: FlatLabel(defaultFlatLabel) { + style: TextStyle(defaultTextStyle) { + font: font(boxFontSize semibold); + } + minWidth: 32px; + align: align(top); + textFg: windowFg; +} dialogsMenuToggle: IconButton { width: 40px; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index db2dcd57aa..19894fd4c0 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/text/text_options.h" #include "ui/dynamic_thumbnails.h" +#include "ui/vertical_list.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/ui_utility.h" @@ -58,8 +59,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/options.h" #include "lang/lang_keys.h" #include "lottie/lottie_icon.h" -#include "mainwindow.h" -#include "mainwidget.h" +#include "settings/settings_common.h" #include "storage/storage_account.h" #include "apiwrap.h" #include "main/main_session.h" @@ -80,6 +80,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_filters.h" #include "base/qt/qt_common_adapters.h" #include "styles/style_dialogs.h" +#include "styles/style_boxes.h" #include "styles/style_chat.h" // popupMenuExpandedSeparator #include "styles/style_chat_helpers.h" #include "styles/style_color_indices.h" @@ -3081,6 +3082,11 @@ void InnerWidget::clearSelection() { } void InnerWidget::fillSupportSearchMenu(not_null menu) { + const auto globalSearch = (_searchState.tab == ChatSearchTab::MyMessages) + || (_searchState.tab == ChatSearchTab::PublicPosts); + if (!globalSearch && _searchState.inChat) { + return; + } const auto all = session().settings().supportAllSearchResults(); const auto text = all ? "Only one from chat" : "Show all messages"; menu->addAction(text, [=] { @@ -3091,9 +3097,11 @@ void InnerWidget::fillSupportSearchMenu(not_null menu) { void InnerWidget::fillArchiveSearchMenu(not_null menu) { const auto folder = session().data().folderLoaded(Data::Folder::kId); + const auto globalSearch = (_searchState.tab == ChatSearchTab::MyMessages) + || (_searchState.tab == ChatSearchTab::PublicPosts); if (!folder || !folder->chatsList()->fullSize().current() - || _searchState.inChat) { + || (!globalSearch && _searchState.inChat)) { return; } const auto skip = session().settings().skipArchiveInSearch(); @@ -3263,16 +3271,13 @@ void InnerWidget::showSponsoredMenu(int peerSearchIndex, QPoint globalPos) { refresh(); }); Menu::FillSponsored( - this, Ui::Menu::CreateAddActionCallback(_menu), _controller->uiShow(), Menu::SponsoredPhrases::Search, session().sponsoredMessages().lookupDetails(entry->sponsored->data), session().sponsoredMessages().createReportCallback( entry->sponsored->data.randomId, - remove), - false, - false); + remove)); QObject::connect(_menu.get(), &QObject::destroyed, [=] { if (_peerSearchMenu >= 0 && _peerSearchMenu < _peerSearchResults.size()) { @@ -3811,7 +3816,7 @@ void InnerWidget::itemRemoved(not_null item) { } bool InnerWidget::uniqueSearchResults() const { - return _controller->uniqueChatsInSearchResults(); + return _controller->uniqueChatsInSearchResults(_searchState); } bool InnerWidget::hasHistoryInResults(not_null history) const { @@ -3869,7 +3874,8 @@ void InnerWidget::searchReceived( ? _searchState.inChat : Key(_openedForum->history()); if (inject - && (!_searchState.inChat + && (globalSearch + || !_searchState.inChat || inject->history() == _searchState.inChat.history())) { Assert(_searchResults.empty()); Assert(!toPreview); @@ -4082,9 +4088,18 @@ void InnerWidget::refreshEmpty() { if (state == EmptyState::None) { _emptyState = state; _empty.destroy(); + _emptyList.destroy(); + _emptyButton.destroy(); return; } else if (_emptyState == state) { _empty->setVisible(_state == WidgetState::Default); + if (_emptyList) { + _emptyList->setVisible(_state == WidgetState::Default); + _empty->setVisible(!_emptyList->isVisible()); + } + if (_emptyButton) { + _emptyButton->setVisible(_state == WidgetState::Default); + } return; } _emptyState = state; @@ -4115,7 +4130,6 @@ void InnerWidget::refreshEmpty() { return result; }); _empty.create(this, std::move(full), st::dialogsEmptyLabel); - resizeEmpty(); _empty->overrideLinkClickHandler([=] { if (_emptyState == EmptyState::NoContacts) { _controller->showAddContact(); @@ -4127,6 +4141,58 @@ void InnerWidget::refreshEmpty() { } }); _empty->setVisible(_state == WidgetState::Default); + + if (state == EmptyState::NoContacts) { + const auto isListVisible = _state == WidgetState::Default; + _emptyList.create(this); + _emptyList->setVisible(isListVisible); + + auto icon = ::Settings::CreateLottieIcon( + _emptyList, + { + .name = u"no_chats"_q, + .sizeOverride = Size(st::changePhoneIconSize), + }); + _emptyList->add( + object_ptr>(_emptyList, std::move(icon.widget))); + Ui::AddSkip(_emptyList); + _emptyList->add( + object_ptr( + _emptyList, + tr::lng_no_conversations(), + st::dialogEmptyButtonLabel)); + if (_state == WidgetState::Default) { + icon.animate(anim::repeat::once); + } + _emptyButton.create( + this, + tr::lng_no_conversations_button(), + st::dialogEmptyButton); + _emptyButton->setTextTransform( + Ui::RoundButton::TextTransform::NoTransform); + _emptyButton->setVisible(isListVisible); + _emptyButton->setClickedCallback([=, window = _controller] { + window->show(PrepareContactsBox(window)); + }); + geometryValue() | rpl::start_with_next([=](const QRect &r) { + const auto top = r.height() + - _emptyButton->height() + - st::dialogEmptyButtonSkip; + _emptyButton->moveToLeft(st::dialogEmptyButtonSkip, top); + }, _emptyButton->lifetime()); + geometryValue() | rpl::start_with_next([=](const QRect &r) { + const auto bottom = _emptyButton + ? (_emptyButton->height() + st::dialogEmptyButtonSkip) + : 0; + _emptyList->moveToLeft( + 0, + ((r.height() - bottom) - _emptyList->height()) / 2); + }, _emptyList->lifetime()); + + _empty->setVisible(!_emptyList->isVisible()); + } + + resizeEmpty(); } void InnerWidget::resizeEmpty() { @@ -4135,6 +4201,13 @@ void InnerWidget::resizeEmpty() { _empty->resizeToWidth(width() - 2 * skip); _empty->move(skip, (st::dialogsEmptyHeight - _empty->height()) / 2); } + if (_emptyList) { + _emptyList->resizeToWidth(width()); + } + if (_emptyButton) { + const auto skip = st::dialogEmptyButtonSkip; + _emptyButton->resizeToWidth(width() - 2 * skip); + } if (_searchEmpty) { _searchEmpty->resizeToWidth(width()); _searchEmpty->move(0, searchedOffset()); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index c3c4a6b033..e54955ee2e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -43,6 +43,8 @@ namespace Ui { class IconButton; class PopupMenu; class FlatLabel; +class VerticalLayout; +class RoundButton; struct ScrollToRequest; namespace Controls { enum class QuickDialogAction; @@ -619,6 +621,8 @@ private: object_ptr _searchEmpty = { nullptr }; SearchState _searchEmptyState; object_ptr _empty = { nullptr }; + object_ptr _emptyList = { nullptr }; + object_ptr _emptyButton = { nullptr }; Ui::DraggingScrollManager _draggingScroll; diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp index 5440f38843..605edf3824 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -120,6 +120,10 @@ void MainList::unreadStateChanged( const auto notify = !useClouded || wasState.known; const auto notifier = unreadStateChangeNotifier(notify); _unreadState += nowState - wasState; + if (_unreadState.chatsMuted > _unreadState.chats + || _unreadState.messagesMuted > _unreadState.messages) { + [[maybe_unused]] int a = 0; + } if (updateCloudUnread) { // Assert(nowState.known); _cloudUnreadState += nowState - wasState; @@ -145,6 +149,10 @@ void MainList::unreadEntryChanged( } else { _unreadState -= state; } + if (_unreadState.chatsMuted > _unreadState.chats + || _unreadState.messagesMuted > _unreadState.messages) { + [[maybe_unused]] int a = 0; + } if (updateCloudUnread) { if (added) { _cloudUnreadState += state; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 0c77f7eedd..757da6b31e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -903,10 +903,7 @@ void Widget::chosenRow(const ChosenRow &row) { } else if (const auto topic = row.key.topic()) { auto params = Window::SectionShow( Window::SectionShow::Way::ClearStack); - params.highlightPart.text = _searchState.query; - if (!params.highlightPart.empty()) { - params.highlightPartOffsetHint = kSearchQueryOffsetHint; - } + params.highlight = Window::SearchHighlightId(_searchState.query); if (row.newWindow) { controller()->showInNewWindow( Window::SeparateId(topic), @@ -973,15 +970,12 @@ void Widget::chosenRow(const ChosenRow &row) { return; } else if (history) { const auto peer = history->peer; - const auto showAtMsgId = controller()->uniqueChatsInSearchResults() - ? ShowAtUnreadMsgId - : row.message.fullId.msg; + const auto showAtMsgId = controller()->uniqueChatsInSearchResults( + _searchState + ) ? ShowAtUnreadMsgId : row.message.fullId.msg; auto params = Window::SectionShow( Window::SectionShow::Way::ClearStack); - params.highlightPart.text = _searchState.query; - if (!params.highlightPart.empty()) { - params.highlightPartOffsetHint = kSearchQueryOffsetHint; - } + params.highlight = Window::SearchHighlightId(_searchState.query); if (row.newWindow) { controller()->showInNewWindow(peer, showAtMsgId); } else { diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index e4ef4a0a6f..68e8d37cbe 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1167,8 +1167,15 @@ Chat ParseChat(const MTPChat &data) { result.colorIndex = (color && color->data().vcolor()) ? color->data().vcolor()->v : PeerColorIndex(result.bareId); + result.isMonoforum = data.is_monoforum(); result.isBroadcast = data.is_broadcast(); result.isSupergroup = data.is_megagroup(); + result.hasMonoforumAdminRights = data.is_broadcast() + && (data.is_creator() + || (data.vadmin_rights() + && data.vadmin_rights()->data().is_manage_direct_messages())); + result.monoforumLinkId + = data.vlinked_monoforum_id().value_or_empty(); result.title = ParseString(data.vtitle()); if (const auto username = data.vusername()) { result.username = ParseString(*username); @@ -1188,15 +1195,6 @@ Chat ParseChat(const MTPChat &data) { return result; } -std::map ParseChatsList(const MTPVector &data) { - auto result = std::map(); - for (const auto &chat : data.v) { - auto parsed = ParseChat(chat); - result.emplace(parsed.id(), std::move(parsed)); - } - return result; -} - Utf8String ContactInfo::name() const { return firstName.isEmpty() ? (lastName.isEmpty() @@ -1273,6 +1271,20 @@ std::map ParsePeersLists( auto parsed = ParseChat(chat); result.emplace(parsed.id(), Peer{ std::move(parsed) }); } + for (auto &[peerId, parsed] : result) { + if (const auto chat = std::get_if(&parsed.data)) { + if (chat->isMonoforum) { + const auto i = result.find( + PeerId(ChannelId(chat->monoforumLinkId))); + if (i != end(result)) { + chat->isMonoforumAdmin + = i->second.chat()->hasMonoforumAdminRights; + chat->isMonoforumOfPublicBroadcast + = !i->second.chat()->username.isEmpty(); + } + } + } + } return result; } @@ -2191,7 +2203,13 @@ const DialogInfo *DialogsInfo::item(int index) const { DialogInfo::Type DialogTypeFromChat(const Chat &chat) { using Type = DialogInfo::Type; - return chat.username.isEmpty() + return (chat.isMonoforum && !chat.isMonoforumAdmin) + ? Type::Personal + : (chat.isMonoforumAdmin && chat.isMonoforumOfPublicBroadcast) + ? Type::PublicSupergroup + : chat.isMonoforumAdmin + ? Type::PrivateSupergroup + : chat.username.isEmpty() ? (chat.isBroadcast ? Type::PrivateChannel : chat.isSupergroup @@ -2252,6 +2270,11 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) { info.migratedToChannelId = peer.chat() ? peer.chat()->migratedToChannelId : 0; + info.isMonoforum = peer.chat() + && peer.chat()->isMonoforum; + info.monoforumBroadcastInput = peer.chat() + ? peer.chat()->monoforumBroadcastInput + : MTPInputPeer(MTP_inputPeerEmpty()); } info.topMessageId = fields.vtop_message().v; const auto messageIt = messages.find(MessageId{ @@ -2290,6 +2313,10 @@ DialogInfo DialogInfoFromChat(const Chat &data) { result.topMessageId = 0; result.type = DialogTypeFromChat(data); result.migratedToChannelId = data.migratedToChannelId; + result.isMonoforum = data.isMonoforum; + if (data.isMonoforumAdmin) { + result.monoforumBroadcastInput = data.monoforumBroadcastInput; + } return result; } @@ -2424,7 +2451,8 @@ void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) { } Unexpected("Type in ApiWrap::onlyMyMessages."); }(); - dialog.onlyMyMessages = ((settings.fullChats & setting) != setting); + dialog.onlyMyMessages = (dialog.type != DialogType::Personal) + && ((settings.fullChats & setting) != setting); ranges::sort(dialog.splits); } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 2c650871a3..86e376fdd8 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -319,14 +319,19 @@ struct Chat { Utf8String title; Utf8String username; uint8 colorIndex = 0; + bool isMonoforum = false; bool isBroadcast = false; bool isSupergroup = false; + bool isMonoforumAdmin = false; + bool hasMonoforumAdminRights = false; + bool isMonoforumOfPublicBroadcast = false; + BareId monoforumLinkId = 0; MTPInputPeer input = MTP_inputPeerEmpty(); + MTPInputPeer monoforumBroadcastInput = MTP_inputPeerEmpty(); }; Chat ParseChat(const MTPChat &data); -std::map ParseChatsList(const MTPVector &data); struct Peer { PeerId id() const; @@ -952,12 +957,15 @@ struct DialogInfo { MTPInputPeer migratedFromInput = MTP_inputPeerEmpty(); ChannelId migratedToChannelId = 0; + MTPInputPeer monoforumBroadcastInput = MTP_inputPeerEmpty(); + // User messages splits which contained that dialog. std::vector splits; // Filled after the whole dialogs list is accumulated. bool onlyMyMessages = false; bool isLeftChannel = false; + bool isMonoforum = false; QString relativePath; // Filled when requesting dialog messages. diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index 4fc78bad52..b25c130b7e 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -1370,7 +1370,7 @@ void ApiWrap::appendSinglePeerDialogs(Data::DialogsInfo &&info) { if (isSupergroupType(info.type) && !migratedRequestId) { migratedRequestId = requestSinglePeerMigrated(info); continue; - } else if (isChannelType(info.type)) { + } else if (isChannelType(info.type) || info.isMonoforum) { continue; } for (auto i = last; i != 0; --i) { @@ -1642,6 +1642,9 @@ void ApiWrap::requestChatMessages( const auto realPeerInput = (splitIndex >= 0) ? _chatProcess->info.input : _chatProcess->info.migratedFromInput; + const auto outgoingInput = _chatProcess->info.isMonoforum + ? _chatProcess->info.monoforumBroadcastInput + : MTP_inputPeerSelf(); const auto realSplitIndex = (splitIndex >= 0) ? splitIndex : (splitsCount + splitIndex); @@ -1650,7 +1653,7 @@ void ApiWrap::requestChatMessages( MTP_flags(MTPmessages_Search::Flag::f_from_id), realPeerInput, MTP_string(), // query - MTP_inputPeerSelf(), + outgoingInput, MTPInputPeer(), // saved_peer_id MTPVector(), // saved_reaction MTPint(), // top_msg_id diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index 0f656950b4..618af25507 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -111,7 +111,8 @@ std::optional PrepareLogReply( MTP_int(topId), MTPstring(), // quote_text MTPVector(), // quote_entities - MTPint()); // quote_offset + MTPint(), // quote_offset + MTPint()); // todo_item_id } } return {}; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 98740671f4..c974e44b0d 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -2380,7 +2380,7 @@ Dialogs::UnreadState History::chatListUnreadState() const { return AdjustedForumUnreadState(forum->topicsList()->unreadState()); } else if (const auto monoforum = peer->monoforum()) { return AdjustedForumUnreadState( - monoforum->unreadStateWithParentMuted()); + withMyMuted(monoforum->chatsList()->unreadState()));; } return computeUnreadState(); } @@ -2395,7 +2395,7 @@ Dialogs::BadgesState History::chatListBadgesState() const { } else if (const auto monoforum = peer->monoforum()) { return adjustBadgesStateByFolder( Dialogs::BadgesForUnread( - monoforum->unreadStateWithParentMuted(), + withMyMuted(monoforum->chatsList()->unreadState()), Dialogs::CountInBadge::Chats, Dialogs::IncludeInBadge::All)); } @@ -2429,8 +2429,8 @@ Dialogs::UnreadState History::computeUnreadState() const { result.mentions = unreadMentions().has() ? 1 : 0; const auto peer = this->peer.get(); const auto &settings = AyuSettings::getInstance(); - const auto hideReactions = (peer->isChannel() && !peer->isMegagroup() && !settings.hideChannelReactions) - || (peer->isMegagroup() && !settings.hideGroupReactions); + const auto hideReactions = (peer->isChannel() && !peer->isMegagroup() && !settings.showChannelReactions) + || (peer->isMegagroup() && !settings.showGroupReactions); result.reactions = hideReactions ? 0 : (unreadReactions().has() ? 1 : 0); result.messagesMuted = muted ? result.messages : 0; result.chatsMuted = muted ? result.chats : 0; @@ -2440,6 +2440,16 @@ Dialogs::UnreadState History::computeUnreadState() const { return result; } +Dialogs::UnreadState History::withMyMuted(Dialogs::UnreadState state) const { + if (muted()) { + state.chatsMuted = state.chats; + state.marksMuted = state.marks; + state.messagesMuted = state.messages; + state.reactionsMuted = state.reactions; + } + return state; +} + void History::allowChatListMessageResolve() { if (_flags & Flag::ResolveChatListMessage) { return; @@ -3368,7 +3378,8 @@ bool History::isForum() const { void History::monoforumChanged(Data::SavedMessages *old) { if (inChatList()) { notifyUnreadStateChange(old - ? AdjustedForumUnreadState(old->chatsList()->unreadState()) + ? AdjustedForumUnreadState( + withMyMuted(old->chatsList()->unreadState())) : computeUnreadState()); } @@ -3378,9 +3389,9 @@ void History::monoforumChanged(Data::SavedMessages *old) { monoforum->chatsList()->unreadStateChanges( ) | rpl::filter([=] { return (_flags & Flag::IsMonoforumAdmin) && inChatList(); - }) | rpl::map( - AdjustedForumUnreadState - ) | rpl::start_with_next([=](const Dialogs::UnreadState &old) { + }) | rpl::map([=](const Dialogs::UnreadState &was) { + return AdjustedForumUnreadState(withMyMuted(was)); + }) | rpl::start_with_next([=](const Dialogs::UnreadState &old) { notifyUnreadStateChange(old); }, monoforum->lifetime()); diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index a36d2000c6..7f556bbc71 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -602,6 +602,8 @@ private: [[nodiscard]] Dialogs::BadgesState adjustBadgesStateByFolder( Dialogs::BadgesState state) const; [[nodiscard]] Dialogs::UnreadState computeUnreadState() const; + [[nodiscard]] Dialogs::UnreadState withMyMuted( + Dialogs::UnreadState state) const; void setFolderPointer(Data::Folder *folder); void hasUnreadMentionChanged(bool has) override; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index afadf42c55..18d9c373fe 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -674,10 +674,10 @@ void HistoryInner::setupSwipeReplyAndBack() { : still)->fullId(); _widget->replyToMessage({ .messageId = replyToItemId, - .quote = selected.text, - .quoteOffset = selected.offset, + .quote = selected.highlight.quote, + .quoteOffset = selected.highlight.quoteOffset, }); - if (!selected.text.empty()) { + if (!selected.highlight.quote.empty()) { _widget->clearSelected(); } }; @@ -1221,8 +1221,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) { if (markingAsViewed && item->hasUnwatchedEffect()) { const auto peer = item->history()->peer; const auto &settings = AyuSettings::getInstance(); - const auto hide = (!settings.hideChannelReactions && peer->isChannel() && !peer->isMegagroup()) || - (!settings.hideGroupReactions && peer->isMegagroup()); + const auto hide = (!settings.showChannelReactions && peer->isChannel() && !peer->isMegagroup()) || + (!settings.showGroupReactions && peer->isMegagroup()); if (!hide) { startEffects.emplace(view); } else { @@ -2409,6 +2409,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto linkUserpicPeerId = (link && _dragStateUserpic) ? link->property(kPeerLinkPeerIdProperty).toULongLong() : 0; + const auto todoListTaskId = link + ? link->property(kTodoListItemIdProperty).toInt() + : 0; const auto session = &this->session(); _whoReactedMenuLifetime.destroy(); if (!clickedReaction.empty() @@ -2777,20 +2780,21 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto selected = selectedQuote(item); auto text = (selected ? tr::lng_context_quote_and_reply + : todoListTaskId + ? tr::lng_context_reply_to_task : tr::lng_context_reply_msg)( tr::now, Ui::Text::FixAmpersandInAction); const auto replyToItem = selected.item ? selected.item : item; const auto itemId = replyToItem->fullId(); - const auto quote = selected.text; - const auto quoteOffset = selected.offset; _menu->addAction(std::move(text), [=] { _widget->replyToMessage({ .messageId = itemId, - .quote = quote, - .quoteOffset = quoteOffset, + .quote = selected.highlight.quote, + .quoteOffset = selected.highlight.quoteOffset, + .todoItemId = todoListTaskId, }); - if (!quote.empty()) { + if (!selected.highlight.quote.empty()) { _widget->clearSelected(); } }, &st::menuIconReply); @@ -2809,7 +2813,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { Window::PeerMenuAddTodoListTasks(_controller, item); } }), - &st::menuIconCreateTodoList); + &st::menuIconAdd); }; const auto lnkPhoto = link ? reinterpret_cast( @@ -2955,11 +2959,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { : nullptr; if (sponsored) { Menu::FillSponsored( - this, Ui::Menu::CreateAddActionCallback(_menu), controller->uiShow(), - sponsored->fullId(), - false); + sponsored->fullId()); } if (isUponSelected > 0) { addReplyAction(item); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 6d96224f21..8267fc32be 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -964,12 +964,26 @@ void HistoryItem::updateServiceDependent(bool force) { } if (!dependent->lnk) { + auto todoItemId = 0; + if (const auto done = Get()) { + const auto &items = !done->completed.empty() + ? done->completed + : done->incompleted; + if (items.size() == 1) { + todoItemId = items.front(); + } + } else if (const auto append = Get()) { + if (append->list.size() == 1) { + todoItemId = append->list.front().id; + } + } dependent->lnk = JumpToMessageClickHandler( (dependent->peerId ? _history->owner().peer(dependent->peerId) : _history->peer), dependent->msgId, - fullId()); + fullId(), + { .todoItemId = todoItemId }); } auto gotDependencyItem = false; if (!dependent->msg) { @@ -1316,14 +1330,8 @@ void HistoryItem::setCommentsItemId(FullMsgId id) { void HistoryItem::setServiceText(PreparedServiceText &&prepared) { auto text = std::move(prepared.text); - const auto &settings = AyuSettings::getInstance(); if (date() > 0) { - const auto timeString = QString(" (%1)").arg(QLocale().toString( - base::unixtime::parse(_date), - settings.showMessageSeconds - ? QLocale::system().timeFormat(QLocale::LongFormat).remove(" t") - : QLocale::system().timeFormat(QLocale::ShortFormat) - )); + const auto timeString = QString(" (%1)").arg(formatMessageTime(base::unixtime::parse(_date).time())); if (!text.text.isEmpty() && !text.text.contains(timeString)) { text = text.append(timeString); } @@ -1858,7 +1866,10 @@ bool HistoryItem::isAyuNoForwards() const { } bool HistoryItem::canLookupMessageAuthor() const { - return isRegular() && _history->amMonoforumAdmin() && _from->isChannel(); + return isRegular() + && !isService() + && _history->amMonoforumAdmin() + && _from->isChannel(); } bool HistoryItem::skipNotification() const { @@ -4392,6 +4403,7 @@ void HistoryItem::createComponentsHelper(HistoryItemCommonFields &&fields) { : replyTo.monoforumPeerId ? replyTo.monoforumPeerId : PeerId(); + config.reply.todoItemId = replyTo.todoItemId; const auto replyToTop = replyTo.topicRootId ? replyTo.topicRootId : LookupReplyToTop(_history, to); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 0c7d1f0cff..d44484536c 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -390,6 +390,7 @@ ReplyFields ReplyFieldsFromMTP( = data.vreply_to_top_id().value_or(result.messageId.bare); result.topicPost = data.is_forum_topic() ? 1 : 0; } + result.todoItemId = data.vtodo_item_id().value_or_empty(); if (const auto header = data.vreply_from()) { const auto &data = header->data(); result.externalPostAuthor @@ -704,7 +705,8 @@ auto ReplyMarkupClickHandler::getUrlButton() const -> const HistoryMessageMarkupButton* { if (const auto button = getButton()) { using Type = HistoryMessageMarkupButton::Type; - if (button->type == Type::Url || button->type == Type::Auth || button->type == Type::Callback) { + if (button->type == Type::Url || button->type == Type::Auth || button->type == Type::Callback || + button->type == Type::WebView || button->type == Type::SimpleWebView) { return button; } } diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 4ca58bde84..6b1cfbb495 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -277,6 +277,7 @@ struct ReplyFields { MsgId messageId = 0; MsgId topMessageId = 0; StoryId storyId = 0; + int todoItemId = 0; uint32 quoteOffset : 30 = 0; uint32 manualQuote : 1 = 0; uint32 topicPost : 1 = 0; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index f274356674..2bc5decb92 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -722,22 +722,19 @@ bool IsItemScheduledUntilOnline(not_null item) { ClickHandlerPtr JumpToMessageClickHandler( not_null item, FullMsgId returnToId, - TextWithEntities highlightPart, - int highlightPartOffsetHint) { + MessageHighlightId highlight) { return JumpToMessageClickHandler( item->history()->peer, item->id, returnToId, - std::move(highlightPart), - highlightPartOffsetHint); + std::move(highlight)); } ClickHandlerPtr JumpToMessageClickHandler( not_null peer, MsgId msgId, FullMsgId returnToId, - TextWithEntities highlightPart, - int highlightPartOffsetHint) { + MessageHighlightId highlight) { return std::make_shared([=] { const auto separate = Core::App().separateWindowFor(peer); const auto controller = separate @@ -747,8 +744,7 @@ ClickHandlerPtr JumpToMessageClickHandler( auto params = Window::SectionShow{ Window::SectionShow::Way::Forward }; - params.highlightPart = highlightPart; - params.highlightPartOffsetHint = highlightPartOffsetHint; + params.highlight = highlight; params.origin = Window::SectionShow::OriginMessage{ returnToId }; @@ -910,7 +906,8 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { | Flag::f_quote_offset)) | (quoteEntities.v.empty() ? Flag() - : Flag::f_quote_entities)), + : Flag::f_quote_entities) + | (replyTo.todoItemId ? Flag::f_todo_item_id : Flag())), MTP_int(replyTo.messageId.msg), peerToMTP(externalPeerId), MTPMessageFwdHeader(), // reply_from @@ -918,7 +915,8 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { MTP_int(replyToTop), MTP_string(replyTo.quote.text), quoteEntities, - MTP_int(replyTo.quoteOffset)); + MTP_int(replyTo.quoteOffset), + MTP_int(replyTo.todoItemId)); } return MTPMessageReplyHeader(); } @@ -1173,8 +1171,8 @@ void CheckReactionNotificationSchedule( } const auto peer = item->history()->peer; const auto &settings = AyuSettings::getInstance(); - if ((peer->isChannel() && !peer->isMegagroup() && !settings.hideChannelReactions) - || (peer->isMegagroup() && !settings.hideGroupReactions)) { + if ((peer->isChannel() && !peer->isMegagroup() && !settings.showChannelReactions) + || (peer->isMegagroup() && !settings.showGroupReactions)) { item->markEffectWatched(); return; } diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index d1c472dd58..f6906136c8 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -229,13 +229,11 @@ private: not_null peer, MsgId msgId, FullMsgId returnToId = FullMsgId(), - TextWithEntities highlightPart = {}, - int highlightPartOffsetHint = 0); + MessageHighlightId highlight = {}); [[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler( not_null item, FullMsgId returnToId = FullMsgId(), - TextWithEntities highlightPart = {}, - int highlightPartOffsetHint = 0); + MessageHighlightId highlight = {}); [[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler( not_null story); ClickHandlerPtr JumpToStoryClickHandler( diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp index ee07a1571f..5ff3f71a6d 100644 --- a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp +++ b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp @@ -65,6 +65,7 @@ Ui::ChatPaintHighlight ElementHighlighter::state( if (item->fullId() == _highlighted.itemId) { auto result = _animation.state(); result.range = _highlighted.part; + result.todoItemId = _highlighted.todoListId; return result; } return {}; @@ -82,19 +83,27 @@ ElementHighlighter::Highlight ElementHighlighter::computeHighlight( const auto i = ranges::find(group->items, item); if (i != end(group->items)) { const auto index = int(i - begin(group->items)); - if (quote.text.empty()) { + if (quote.highlight.empty()) { return { leaderId, AddGroupItemSelection({}, index) }; } else if (const auto leaderView = _viewForItem(leader)) { - return { leaderId, leaderView->selectionFromQuote(quote) }; + return { + leaderId, + leaderView->selectionFromQuote(quote), + quote.highlight.todoItemId, + }; } } - return { leaderId }; - } else if (quote.text.empty()) { - return { item->fullId() }; + return { leaderId, {}, quote.highlight.todoItemId }; + } else if (quote.highlight.quote.empty()) { + return { item->fullId(), {}, quote.highlight.todoItemId }; } else if (const auto view = _viewForItem(item)) { - return { item->fullId(), view->selectionFromQuote(quote) }; + return { + item->fullId(), + view->selectionFromQuote(quote), + quote.highlight.todoItemId, + }; } - return { item->fullId() }; + return { item->fullId(), {}, quote.highlight.todoItemId }; } void ElementHighlighter::highlight(Highlight data) { @@ -108,7 +117,7 @@ void ElementHighlighter::highlight(Highlight data) { } } _highlighted = data; - _animation.start(!data.part.empty() + _animation.start((!data.part.empty() || data.todoListId) && !IsSubGroupSelection(data.part)); repaintHighlightedItem(view); diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.h b/Telegram/SourceFiles/history/history_view_highlight_manager.h index 5a3b43ca8e..08ac68e44d 100644 --- a/Telegram/SourceFiles/history/history_view_highlight_manager.h +++ b/Telegram/SourceFiles/history/history_view_highlight_manager.h @@ -65,6 +65,7 @@ private: struct Highlight { FullMsgId itemId; TextSelection part; + int todoListId = 0; explicit operator bool() const { return itemId.operator bool(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 1cc3d28412..9d82d767d0 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -68,6 +68,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_drafts.h" #include "data/data_session.h" +#include "data/data_todo_list.h" #include "data/data_web_page.h" #include "data/data_document.h" #include "data/data_photo.h" @@ -1575,12 +1576,21 @@ int HistoryWidget::itemTopForHighlight( const auto heightLeft = (visibleAreaHeight - viewHeight); if (heightLeft >= 0) { return std::max(itemTop - (heightLeft / 2), 0); - } else if (const auto sel = itemHighlight(item).range - ; !sel.empty() && !IsSubGroupSelection(sel)) { + } else if (const auto highlight = itemHighlight(item) + ; (!highlight.range.empty() || highlight.todoItemId) + && !IsSubGroupSelection(highlight.range)) { + const auto sel = highlight.range; const auto single = st::messageTextStyle.font->height; - const auto begin = HistoryView::FindViewY(view, sel.from) - single; - const auto end = HistoryView::FindViewY(view, sel.to, begin + single) - + 2 * single; + const auto todoy = sel.empty() + ? HistoryView::FindViewTaskY(view, highlight.todoItemId) + : 0; + const auto begin = sel.empty() + ? (todoy - 4 * single) + : HistoryView::FindViewY(view, sel.from) - single; + const auto end = sel.empty() + ? (todoy + 4 * single) + : (HistoryView::FindViewY(view, sel.to, begin + single) + + 2 * single); auto result = itemTop; if (end > visibleAreaHeight) { result = std::max(result, itemTop + end - visibleAreaHeight); @@ -5797,8 +5807,7 @@ void HistoryWidget::switchToSearch(QString query) { const auto item = activation.item; auto params = ::Window::SectionShow( ::Window::SectionShow::Way::ClearStack); - params.highlightPart = { activation.query }; - params.highlightPartOffsetHint = kSearchQueryOffsetHint; + params.highlight = Window::SearchHighlightId(activation.query); controller()->showPeerHistory( item->history()->peer->id, params, @@ -6907,8 +6916,7 @@ int HistoryWidget::countInitialScrollTop() { enqueueMessageHighlight({ item, - base::take(_showAtMsgParams.highlightPart), - base::take(_showAtMsgParams.highlightPartOffsetHint), + base::take(_showAtMsgParams.highlight), }); const auto result = itemTopForHighlight(view); createUnreadBarIfBelowVisibleArea(result); @@ -7670,12 +7678,7 @@ void HistoryWidget::editDraftOptions() { void HistoryWidget::jumpToReply(FullReplyTo to) { if (const auto item = session().data().message(to.messageId)) { - JumpToMessageClickHandler( - item, - {}, - to.quote, - to.quoteOffset - )->onClick({}); + JumpToMessageClickHandler(item, {}, to.highlight())->onClick({}); } } @@ -8718,7 +8721,7 @@ void HistoryWidget::clearFieldText( void HistoryWidget::replyToMessage(FullReplyTo id) { if (const auto item = session().data().message(id.messageId)) { if (CanSendReply(item) && !base::IsCtrlPressed()) { - replyToMessage(item, id.quote, id.quoteOffset); + replyToMessage(item, id); } else if (item->allowsForward()) { const auto show = controller()->uiShow(); HistoryView::Controls::ShowReplyToChatBox(show, id); @@ -8731,16 +8734,12 @@ void HistoryWidget::replyToMessage(FullReplyTo id) { void HistoryWidget::replyToMessage( not_null item, - TextWithEntities quote, - int quoteOffset) { + FullReplyTo fields) { if (isJoinChannel()) { return; } - _processingReplyTo = { - .messageId = item->fullId(), - .quote = quote, - .quoteOffset = quoteOffset, - }; + fields.messageId = item->fullId(); + _processingReplyTo = fields; _processingReplyItem = item; processReply(); } @@ -9429,11 +9428,24 @@ void HistoryWidget::updateReplyEditText(not_null item) { .session = &session(), .repaint = [=] { updateField(); }, }); + const auto text = [&] { + const auto media = _replyTo.todoItemId ? item->media() : nullptr; + if (const auto todolist = media ? media->todolist() : nullptr) { + const auto i = ranges::find( + todolist->items, + _replyTo.todoItemId, + &TodoListItem::id); + if (i != end(todolist->items)) { + return i->text; + } + } + return (_editMsgId || _replyTo.quote.empty()) + ? item->inReplyText() + : _replyTo.quote; + }(); _replyEditMsgText.setMarkedText( st::defaultTextStyle, - ((_editMsgId || _replyTo.quote.empty()) - ? item->inReplyText() - : _replyTo.quote), + text, Ui::DialogTextOptions(), context); if (fieldOrDisabledShown() || isRecording()) { @@ -9519,10 +9531,9 @@ void HistoryWidget::updateReplyToName() { .customEmojiLoopLimit = 1, }); const auto to = _replyEditMsg ? _replyEditMsg : _kbReplyTo; - const auto replyToQuote = _replyTo && !_replyTo.quote.empty(); _replyToName.setMarkedText( st::fwdTextStyle, - HistoryView::Reply::ComposePreviewName(_history, to, replyToQuote), + HistoryView::Reply::ComposePreviewName(_history, to, _replyTo), Ui::NameTextOptions(), context); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 37ac772c18..0d181fa3f4 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -205,8 +205,7 @@ public: void replyToMessage(FullReplyTo id); void replyToMessage( not_null item, - TextWithEntities quote = {}, - int quoteOffset = 0); + FullReplyTo fields = {}); void editMessage( not_null item, const TextSelection &selection); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 6cca420dbf..012e6aea51 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -92,6 +92,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat_helpers.h" #include "styles/style_menu_icons.h" +// AyuGram includes +#include "ayu/ayu_settings.h" + + namespace HistoryView { namespace { @@ -119,6 +123,13 @@ using SetHistoryArgs = ComposeControls::SetHistoryArgs; using VoiceRecordBar = Controls::VoiceRecordBar; using ForwardPanel = Controls::ForwardPanel; +#define SWITCH_BUTTON(button, show_v) \ + if (show_v) { \ + (button)->show(); \ + } else { \ + (button)->hide(); \ + } + } // namespace const ChatHelpers::PauseReason kDefaultPanelsLevel @@ -492,10 +503,9 @@ void FieldHeader::setShownMessage(HistoryItem *item) { .customEmojiLoopLimit = 1, }); const auto replyTo = _replyTo.current(); - const auto quote = replyTo && !replyTo.quote.empty(); _shownMessageName.setMarkedText( st::fwdTextStyle, - HistoryView::Reply::ComposePreviewName(_history, item, quote), + HistoryView::Reply::ComposePreviewName(_history, item, replyTo), Ui::NameTextOptions(), context); } else { @@ -1595,6 +1605,14 @@ void ComposeControls::init() { updateAttachBotsMenu(); }, _wrap->lifetime()); + AyuSettings::get_historyUpdateReactive() | rpl::start_with_next([=] + { + updateSendButtonType(); + updateControlsVisibility(); + updateControlsGeometry(_wrap->size()); + orderControls(); + }, _wrap->lifetime()); + orderControls(); } @@ -1604,6 +1622,11 @@ void ComposeControls::orderControls() { } bool ComposeControls::showRecordButton() const { + const auto &settings = AyuSettings::getInstance(); + if (!settings.showMicrophoneButtonInMessageField) { + return false; + } + return (_recordAvailability != Webrtc::RecordAvailability::None) && !_voiceRecordBar->isListenState() && !_voiceRecordBar->isRecordingByAnotherBar() @@ -2686,17 +2709,19 @@ void ComposeControls::updateControlsGeometry(QSize size) { // (_attachToggle|_replaceMedia) (_sendAs) -- _inlineResults ------ _tabbedPanel -- _fieldBarCancel // (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_botCommandStart) _tabbedSelectorToggle _send + const auto &settings = AyuSettings::getInstance(); + const auto fieldWidth = size.width() - - _attachToggle->width() + - (settings.showAttachButtonInMessageField ? _attachToggle->width() : 0) - (_sendAs ? _sendAs->width() : 0) - st::historySendRight - _send->width() - - _tabbedSelectorToggle->width() + - (settings.showEmojiButtonInMessageField ? _tabbedSelectorToggle->width() : 0) - (_likeShown ? _like->width() : 0) - - (_botCommandShown ? _botCommandStart->width() : 0) + - (_botCommandShown && settings.showCommandsButtonInMessageField ? _botCommandStart->width() : 0) - (_silent ? _silent->width() : 0) - (_scheduled ? _scheduled->width() : 0) - - (_ttlInfo ? _ttlInfo->width() : 0); + - (_ttlInfo && settings.showAutoDeleteButtonInMessageField ? _ttlInfo->width() : 0); { const auto oldFieldHeight = _field->height(); _field->resizeToWidth(fieldWidth); @@ -2713,8 +2738,10 @@ void ComposeControls::updateControlsGeometry(QSize size) { if (_replaceMedia) { _replaceMedia->moveToLeft(left, buttonsTop); } - _attachToggle->moveToLeft(left, buttonsTop); - left += _attachToggle->width(); + if (settings.showAttachButtonInMessageField) { + _attachToggle->moveToLeft(left, buttonsTop); + left += _attachToggle->width(); + } if (_sendAs) { _sendAs->moveToLeft(left, buttonsTop); left += _sendAs->width(); @@ -2731,8 +2758,10 @@ void ComposeControls::updateControlsGeometry(QSize size) { auto right = st::historySendRight; _send->moveToRight(right, buttonsTop); right += _send->width(); - _tabbedSelectorToggle->moveToRight(right, buttonsTop); - right += _tabbedSelectorToggle->width(); + if (settings.showEmojiButtonInMessageField) { + _tabbedSelectorToggle->moveToRight(right, buttonsTop); + right += _tabbedSelectorToggle->width(); + } if (_like) { using Type = Controls::WriteRestrictionType; if (_writeRestriction.current().type == Type::PremiumRequired) { @@ -2746,7 +2775,7 @@ void ComposeControls::updateControlsGeometry(QSize size) { } if (_botCommandStart) { _botCommandStart->moveToRight(right, buttonsTop); - if (_botCommandShown) { + if (_botCommandShown && settings.showCommandsButtonInMessageField) { right += _botCommandStart->width(); } } @@ -2758,7 +2787,7 @@ void ComposeControls::updateControlsGeometry(QSize size) { _scheduled->moveToRight(right, buttonsTop); right += _scheduled->width(); } - if (_ttlInfo) { + if (_ttlInfo && settings.showAutoDeleteButtonInMessageField) { _ttlInfo->move(size.width() - right - _ttlInfo->width(), buttonsTop); } @@ -2769,14 +2798,16 @@ void ComposeControls::updateControlsGeometry(QSize size) { } void ComposeControls::updateControlsVisibility() { + const auto &settings = AyuSettings::getInstance(); + if (_botCommandStart) { - _botCommandStart->setVisible(_botCommandShown); + SWITCH_BUTTON(_botCommandStart, _botCommandShown && settings.showCommandsButtonInMessageField); } if (_like) { _like->setVisible(_likeShown); } if (_ttlInfo) { - _ttlInfo->show(); + SWITCH_BUTTON(_ttlInfo, settings.showAutoDeleteButtonInMessageField); } if (_sendAs) { _sendAs->show(); @@ -2785,11 +2816,12 @@ void ComposeControls::updateControlsVisibility() { _replaceMedia->show(); _attachToggle->hide(); } else { - _attachToggle->show(); + SWITCH_BUTTON(_attachToggle, settings.showAttachButtonInMessageField); } if (_scheduled) { _scheduled->setVisible(!isEditingMessage()); } + SWITCH_BUTTON(_tabbedSelectorToggle, settings.showEmojiButtonInMessageField); } bool ComposeControls::updateLikeShown() { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp index f398211636..15125a480b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -718,8 +718,7 @@ void DraftOptionsBox( state->link = args.usedLink; state->quote = SelectedQuote{ replyItem, - draft.reply.quote, - draft.reply.quoteOffset, + { draft.reply.quote, draft.reply.quoteOffset }, }; state->forward = std::move(args.forward); state->webpage = draft.webpage; @@ -783,7 +782,7 @@ void DraftOptionsBox( box->setTitle(hasLink ? tr::lng_link_options_header() : hasReply - ? (state->quote.current().text.empty() + ? (state->quote.current().highlight.quote.empty() ? tr::lng_reply_options_header() : tr::lng_reply_options_quote()) : (forwardCount == 1) @@ -807,10 +806,12 @@ void DraftOptionsBox( auto result = draft.reply; if (const auto current = state->quote.current()) { result.messageId = current.item->fullId(); - result.quote = current.text; - result.quoteOffset = current.offset; + result.quote = current.highlight.quote; + result.quoteOffset = current.highlight.quoteOffset; +// result.todoItemId = current.highlight.todoItemId; } else { result.quote = {}; +// result.todoItemId = 0; } return result; }; @@ -1112,7 +1113,7 @@ void DraftOptionsBox( state->quote.value(), state->shown.value() ) | rpl::map([=](const SelectedQuote "e, Section shown) { - return (quote.text.empty() || shown != Section::Reply) + return (quote.highlight.quote.empty() || shown != Section::Reply) ? tr::lng_settings_save() : tr::lng_reply_quote_selected(); }) | rpl::flatten_latest(); diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index 186c99e3d0..8c489d4c6d 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL // AyuGram includes #include "ayu/ayu_settings.h" #include "ayu/features/messageshot/message_shot.h" +#include "ayu/utils/telegram_helpers.h" #include "core/ui_integration.h" #include "styles/style_ayu_icons.h" @@ -428,12 +429,7 @@ void BottomInfo::layoutDateText() { : QString(); const auto author = _data.author; const auto prefix = !author.isEmpty() ? u", "_q : QString(); - const auto date = edited + QLocale().toString( - _data.date.time(), - settings.showMessageSeconds - ? QLocale::system().timeFormat(QLocale::LongFormat).remove(" t") - : QLocale::system().timeFormat(QLocale::ShortFormat) - ); + const auto date = edited + formatMessageTime(_data.date.time()); const auto afterAuthor = prefix + date; const auto afterAuthorWidth = st::msgDateFont->width(afterAuthor); const auto authorWidth = st::msgDateFont->width(author); @@ -494,12 +490,9 @@ void BottomInfo::layoutDateText() { const auto author = _data.author; const auto prefix = !author.isEmpty() ? (_data.flags & Data::Flag::Edited ? u" "_q : u", "_q) : QString(); - const auto date = TextWithEntities{}.append(edited).append(QLocale().toString( - _data.date.time(), - settings.showMessageSeconds - ? QLocale::system().timeFormat(QLocale::LongFormat).remove(" t") - : QLocale::system().timeFormat(QLocale::ShortFormat) - )); + const auto date = TextWithEntities{} + .append(edited) + .append(formatMessageTime(_data.date.time())); const auto afterAuthor = TextWithEntities{}.append(prefix).append(date); const auto afterAuthorWidth = st::msgDateFont->width(afterAuthor.text); diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index 12dff183d2..72c2c2e58c 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -123,12 +123,10 @@ rpl::producer RootViewContent( ChatMemento::ChatMemento( ChatViewId id, MsgId highlightId, - const TextWithEntities &highlightPart, - int highlightPartOffsetHint) + MessageHighlightId highlight) : _id(id) -, _highlightPart(highlightPart) -, _highlightPartOffsetHint(highlightPartOffsetHint) -, _highlightId(highlightId) { +, _highlightId(highlightId) +, _highlight(std::move(highlight)) { if (highlightId || _id.sublist) { _list.setAroundPosition({ .fullId = FullMsgId(_id.history->peer->id, highlightId), @@ -884,12 +882,7 @@ void ChatWidget::setupComposeControls() { _composeControls->jumpToItemRequests( ) | rpl::start_with_next([=](FullReplyTo to) { if (const auto item = session().data().message(to.messageId)) { - JumpToMessageClickHandler( - item, - {}, - to.quote, - to.quoteOffset - )->onClick({}); + JumpToMessageClickHandler(item, {}, to.highlight())->onClick({}); } }, lifetime()); @@ -1047,8 +1040,9 @@ void ChatWidget::setupSwipeReplyAndBack() { : still)->fullId(); _inner->replyToMessageRequestNotify({ .messageId = replyToItemId, - .quote = selected.text, - .quoteOffset = selected.offset, + .quote = selected.highlight.quote, + .quoteOffset = selected.highlight.quoteOffset, + .todoItemId = selected.highlight.todoItemId, }); }; return result; @@ -2648,8 +2642,7 @@ void ChatWidget::restoreState(not_null memento) { auto params = Window::SectionShow( Window::SectionShow::Way::Forward, anim::type::instant); - params.highlightPart = memento->highlightPart(); - params.highlightPartOffsetHint = memento->highlightPartOffsetHint(); + params.highlight = memento->highlight(); showAtPosition(Data::MessagePosition{ .fullId = FullMsgId(_peer->id, highlight), .date = TimeId(0), @@ -3452,8 +3445,7 @@ bool ChatWidget::searchInChatEmbedded( const auto item = activation.item; auto params = ::Window::SectionShow( ::Window::SectionShow::Way::ClearStack); - params.highlightPart = { activation.query }; - params.highlightPartOffsetHint = kSearchQueryOffsetHint; + params.highlight = Window::SearchHighlightId(activation.query); controller()->showPeerHistory( item->history()->peer->id, params, diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.h b/Telegram/SourceFiles/history/view/history_view_chat_section.h index 87b4604c04..97030e58e1 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.h +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.h @@ -461,8 +461,7 @@ public: explicit ChatMemento( ChatViewId id, MsgId highlightId = 0, - const TextWithEntities &highlightPart = {}, - int highlightPartOffsetHint = 0); + MessageHighlightId highlight = {}); struct Comments { }; @@ -511,20 +510,16 @@ public: [[nodiscard]] MsgId highlightId() const { return _highlightId; } - [[nodiscard]] const TextWithEntities &highlightPart() const { - return _highlightPart; - } - [[nodiscard]] int highlightPartOffsetHint() const { - return _highlightPartOffsetHint; + [[nodiscard]] const MessageHighlightId &highlight() const { + return _highlight; } private: void setupTopicViewer(); ChatViewId _id; - const TextWithEntities _highlightPart; - const int _highlightPartOffsetHint = 0; const MsgId _highlightId = 0; + const MessageHighlightId _highlight; ListMemento _list; std::shared_ptr _replies; QVector _replyReturns; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 859608d249..4e48d08eff 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -644,8 +644,13 @@ bool AddReplyToMessageAction( return false; } + const auto todoListTaskId = request.link + ? request.link->property(kTodoListItemIdProperty).toInt() + : 0; const auto "e = request.quote; - auto text = (quote.text.empty() + auto text = (todoListTaskId + ? tr::lng_context_reply_to_task + : quote.highlight.quote.empty() ? tr::lng_context_reply_msg : tr::lng_context_quote_and_reply)( tr::now, @@ -653,8 +658,9 @@ bool AddReplyToMessageAction( menu->addAction(std::move(text), [=, itemId = item->fullId()] { list->replyToMessageRequestNotify({ .messageId = itemId, - .quote = quote.text, - .quoteOffset = quote.offset, + .quote = quote.highlight.quote, + .quoteOffset = quote.highlight.quoteOffset, + .todoItemId = todoListTaskId, }, base::IsCtrlPressed()); }, &st::menuIconReply); return true; @@ -680,7 +686,7 @@ bool AddTodoListAction( if (const auto item = controller->session().data().message(itemId)) { Window::PeerMenuAddTodoListTasks(controller, item); } - }, &st::menuIconCreateTodoList); + }, &st::menuIconAdd); return true; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index a6f6f5ac78..586c2033df 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -46,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_saved_sublist.h" #include "data/data_session.h" +#include "data/data_todo_list.h" #include "data/data_forum.h" #include "data/data_forum_topic.h" #include "data/data_message_reactions.h" @@ -1360,9 +1361,18 @@ void Element::validateText() { if (const auto done = item->Get()) { if (!done->completed.empty() && !done->incompleted.empty()) { + const auto todoItemId = (done->incompleted.size() == 1) + ? done->incompleted.front() + : 0; setServicePreMessage( item->composeTodoIncompleted(done), - done->lnk); + JumpToMessageClickHandler( + (done->peerId + ? history()->owner().peer(done->peerId) + : history()->peer), + done->msgId, + item->fullId(), + { .todoItemId = todoItemId })); } else { setServicePreMessage({}); } @@ -2205,7 +2215,7 @@ SelectedQuote Element::FindSelectedQuote( ++i; } } - return { item, result, modified.from, overflown }; + return { item, { result, modified.from }, overflown }; } TextSelection Element::FindSelectionFromQuote( @@ -2213,17 +2223,18 @@ TextSelection Element::FindSelectionFromQuote( const SelectedQuote "e) { Expects(quote.item != nullptr); - if (quote.text.empty()) { + const auto &rich = quote.highlight.quote; + if (rich.empty()) { return {}; } const auto &original = quote.item->originalText(); - if (quote.offset == kSearchQueryOffsetHint) { + if (quote.highlight.quoteOffset == kSearchQueryOffsetHint) { return ApplyModificationsFrom( - FindSearchQueryHighlight(original.text, quote.text.text), + FindSearchQueryHighlight(original.text, rich.text), text); } const auto length = int(original.text.size()); - const auto qlength = int(quote.text.text.size()); + const auto qlength = int(rich.text.size()); const auto checkAt = [&](int offset) { return TextSelection{ uint16(offset), @@ -2234,7 +2245,7 @@ TextSelection Element::FindSelectionFromQuote( if (offset > length - qlength) { return TextSelection(); } - const auto i = original.text.indexOf(quote.text.text, offset); + const auto i = original.text.indexOf(rich.text, offset); return (i >= 0) ? checkAt(i) : TextSelection(); }; const auto findOneBefore = [&](int offset) { @@ -2243,7 +2254,7 @@ TextSelection Element::FindSelectionFromQuote( } const auto end = std::min(offset + qlength - 1, length); const auto from = end - length - 1; - const auto i = original.text.lastIndexOf(quote.text.text, from); + const auto i = original.text.lastIndexOf(rich.text, from); return (i >= 0) ? checkAt(i) : TextSelection(); }; const auto findAfter = [&](int offset) { @@ -2281,7 +2292,7 @@ TextSelection Element::FindSelectionFromQuote( ? before : after; }; - auto result = findTwoWays(quote.offset); + auto result = findTwoWays(quote.highlight.quoteOffset); if (result.empty()) { return {}; } @@ -2468,6 +2479,70 @@ int FindViewY(not_null view, uint16 symbol, int yfrom) { return origin.y() + (yfrom + ytill) / 2; } +int FindViewTaskY(not_null view, int taskId, int yfrom) { + auto request = HistoryView::StateRequest(); + request.flags = Ui::Text::StateRequest::Flag::LookupLink; + const auto single = st::messageTextStyle.font->height; + const auto inner = view->innerGeometry(); + const auto origin = inner.topLeft(); + const auto top = 0; + const auto bottom = view->height(); + if (origin.y() < top + || origin.y() + inner.height() > bottom + || inner.height() <= 0) { + return yfrom; + } + const auto media = view->data()->media(); + const auto todolist = media ? media->todolist() : nullptr; + if (!todolist) { + return yfrom; + } + const auto &items = todolist->items; + const auto indexOf = [&](int id) -> int { + return ranges::find(items, id, &TodoListItem::id) - begin(items); + }; + const auto index = indexOf(taskId); + const auto count = int(items.size()); + if (index == count) { + return yfrom; + } + yfrom = std::max(yfrom - origin.y(), 0); + auto ytill = inner.height() - 1; + const auto middle = (yfrom + ytill) / 2; + const auto fory = [&](int y) { + const auto state = view->textState(origin + QPoint(0, y), request); + const auto &link = state.link; + const auto id = link + ? link->property(kTodoListItemIdProperty).toInt() + : -1; + const auto index = (id >= 0) ? indexOf(id) : int(items.size()); + return (index < count) ? index : (y < middle) ? -1 : count; + }; + auto indexfrom = fory(yfrom); + auto indextill = fory(ytill); + if ((yfrom >= ytill) || (indexfrom >= index)) { + return origin.y() + yfrom; + } else if (indextill <= index) { + return origin.y() + ytill; + } + while (ytill - yfrom >= 2 * single) { + const auto middle = (yfrom + ytill) / 2; + const auto found = fory(middle); + if (found == index + || indexfrom > found + || indextill < found) { + return origin.y() + middle; + } else if (found < index) { + yfrom = middle; + indexfrom = found; + } else { + ytill = middle; + indextill = found; + } + } + return origin.y() + (yfrom + ytill) / 2; +} + Window::SessionController *ExtractController(const ClickContext &context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 6f7a4edea0..34bc8679ce 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -357,12 +357,11 @@ struct TopicButton { struct SelectedQuote { HistoryItem *item = nullptr; - TextWithEntities text; - int offset = 0; + MessageHighlightId highlight; bool overflown = false; explicit operator bool() const { - return item && !text.empty(); + return item && !highlight.quote.empty(); } friend inline bool operator==(SelectedQuote, SelectedQuote) = default; }; @@ -748,6 +747,11 @@ private: uint16 symbol, int yfrom = 0); +[[nodiscard]] int FindViewTaskY( + not_null view, + int taskId, + int yfrom = 0); + [[nodiscard]] Window::SessionController *ExtractController( const ClickContext &context); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index b7477cddfa..0217ef24e3 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -720,12 +720,21 @@ std::optional ListWidget::scrollTopForView( const auto heightLeft = (available - height); if (heightLeft >= 0) { return std::max(top - (heightLeft / 2), 0); - } else if (const auto sel = _highlighter.state(view->data()).range - ; !sel.empty() && !IsSubGroupSelection(sel)) { + } else if (const auto highlight = _highlighter.state(view->data()) + ; (!highlight.range.empty() || highlight.todoItemId) + && !IsSubGroupSelection(highlight.range)) { + const auto sel = highlight.range; const auto single = st::messageTextStyle.font->height; - const auto begin = HistoryView::FindViewY(view, sel.from) - single; - const auto end = HistoryView::FindViewY(view, sel.to, begin + single) - + 2 * single; + const auto todoy = sel.empty() + ? HistoryView::FindViewTaskY(view, highlight.todoItemId) + : 0; + const auto begin = sel.empty() + ? (todoy - 4 * single) + : HistoryView::FindViewY(view, sel.from) - single; + const auto end = sel.empty() + ? (todoy + 4 * single) + : (HistoryView::FindViewY(view, sel.to, begin + single) + + 2 * single); auto result = top; if (end > available) { result = std::max(result, top + end - available); @@ -822,10 +831,9 @@ bool ListWidget::isBelowPosition(Data::MessagePosition position) const { void ListWidget::highlightMessage( FullMsgId itemId, - const TextWithEntities &part, - int partOffsetHint) { + const MessageHighlightId &highlight) { if (const auto view = viewForItem(itemId)) { - _highlighter.highlight({ view->data(), part, partOffsetHint }); + _highlighter.highlight({ view->data(), highlight }); } } @@ -903,11 +911,8 @@ bool ListWidget::showAtPositionNow( } if (position != Data::MaxMessagePosition && position != Data::UnreadMessagePosition) { - const auto hasHighlight = !params.highlightPart.empty(); - highlightMessage( - position.fullId, - params.highlightPart, - params.highlightPartOffsetHint); + const auto hasHighlight = !params.highlight.empty(); + highlightMessage(position.fullId, params.highlight); if (hasHighlight) { // We may want to scroll to a different part of the message. scrollTop = scrollTopForPosition(position); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 8f4a354b8c..f313810626 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -314,8 +314,7 @@ public: bool isBelowPosition(Data::MessagePosition position) const; void highlightMessage( FullMsgId itemId, - const TextWithEntities &part, - int partOffsetHint); + const MessageHighlightId &highlight); void showAtPosition( Data::MessagePosition position, diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 3dde8462fc..708127f9b9 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -490,6 +490,8 @@ void Message::initPaidInformation() { refreshSuggestedInfo(item, suggest, replyData); } return; + } else if (!item->history()->peer->isUser()) { + return; } const auto media = this->media(); const auto mine = PaidInformation{ @@ -3348,7 +3350,7 @@ TextSelection Message::selectionFromQuote( const SelectedQuote "e) const { Expects(quote.item != nullptr); - if (quote.text.empty()) { + if (quote.highlight.quote.empty()) { return {}; } const auto item = quote.item; diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp index 6c3f680bca..d69108bfdd 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer.h" #include "data/data_session.h" #include "data/data_story.h" +#include "data/data_todo_list.h" #include "data/data_user.h" #include "history/view/history_view_item_preview.h" #include "history/history.h" @@ -42,6 +43,85 @@ namespace { constexpr auto kNonExpandedLinesLimit = 5; +[[nodiscard]] QImage MakeTaskImage() { + const auto diameter = st::normalFont->ascent; + const auto line = st::historyPollRadio.thickness; + const auto size = 2 * line + diameter; + const auto ratio = style::DevicePixelRatio(); + auto result = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + result.fill(Qt::transparent); + result.setDevicePixelRatio(ratio); + + auto p = QPainter(&result); + PainterHighQualityEnabler hq(p); + + p.setOpacity(st::historyPollRadioOpacity); + + const auto rect = QRectF(line, line, diameter, diameter).marginsRemoved( + QMarginsF(line / 2., line / 2., line / 2., line / 2.)); + auto pen = QPen(QColor(255, 255, 255)); + pen.setWidth(line); + p.setPen(pen); + p.drawEllipse(rect); + + p.end(); + + return result; +} + +[[nodiscard]] QImage MakeTaskDoneImage() { + const auto white = QColor(255, 255, 255); + const auto black = QColor(0, 0, 0); + + const auto diameter = st::normalFont->ascent; + const auto line = st::historyPollRadio.thickness; + const auto size = 2 * line + diameter; + const auto ratio = style::DevicePixelRatio(); + auto result = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + result.fill(black); + result.setDevicePixelRatio(ratio); + + auto p = QPainter(&result); + PainterHighQualityEnabler hq(p); + + const auto rect = QRectF(line, line, diameter, diameter).marginsRemoved( + QMarginsF(line / 2., line / 2., line / 2., line / 2.)); + auto pen = QPen(white); + pen.setWidth(line); + p.setPen(pen); + p.setBrush(white); + p.drawEllipse(rect); + const auto &icon = st::historyPollInChoiceRight; + icon.paint( + p, + line + (diameter - icon.width()) / 2, + line + (diameter - icon.height()) / 2, + size, + black); + p.end(); + + return style::colorizeImage(result, white); +} + +[[nodiscard]] TextWithEntities TaskDoneIcon( + not_null session) { + return Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + MakeTaskDoneImage(), + QMargins(0, st::lineWidth, st::lineWidth, 0))); +} + +[[nodiscard]] TextWithEntities TaskIcon(not_null session) { + return Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + MakeTaskImage(), + QMargins(0, st::lineWidth, st::lineWidth, 0))); +} + } // namespace void ValidateBackgroundEmoji( @@ -197,6 +277,22 @@ void Reply::update( const auto item = view->data(); const auto &fields = data->fields(); const auto message = data->resolvedMessage.get(); + const auto messageMedia = (message && fields.todoItemId) + ? message->media() + : nullptr; + const auto messageTodoList = messageMedia + ? messageMedia->todolist() + : nullptr; + const auto taskIndex = messageTodoList + ? int(ranges::find( + messageTodoList->items, + fields.todoItemId, + &TodoListItem::id) - begin(messageTodoList->items)) + : -1; + const auto task = (taskIndex >= 0 + && taskIndex < messageTodoList->items.size()) + ? &messageTodoList->items[taskIndex] + : nullptr; const auto story = data->resolvedStory.get(); const auto externalMedia = fields.externalMedia.get(); if (!_externalSender) { @@ -214,7 +310,6 @@ void Reply::update( _hiddenSenderColorIndexPlusOne = (!_colorPeer && message) ? (message->originalHiddenSenderInfo()->colorIndex + 1) : 0; - const auto hasPreview = (story && story->hasReplyPreview()) || (message && message->media() @@ -229,8 +324,13 @@ void Reply::update( && !fields.quote.empty(); _hasQuoteIcon = hasQuoteIcon ? 1 : 0; + const auto session = &view->history()->session(); const auto text = (!_displaying && data->unavailable()) ? TextWithEntities() + : task + ? Ui::Text::Colorized(task->completionDate + ? TaskDoneIcon(session) + : TaskIcon(session)).append(task->text) : (message && (fields.quote.empty() || !fields.manualQuote)) ? message->inReplyText() : !fields.quote.empty() @@ -288,10 +388,11 @@ void Reply::setLinkFrom( const auto &fields = data->fields(); const auto externalChannelId = peerToChannel(fields.externalPeerId); const auto messageId = fields.messageId; - const auto quote = fields.manualQuote - ? fields.quote - : TextWithEntities(); - const auto quoteOffset = fields.quoteOffset; + const auto highlight = MessageHighlightId{ + .quote = fields.manualQuote ? fields.quote : TextWithEntities(), + .quoteOffset = int(fields.quoteOffset), + .todoItemId = fields.todoItemId, + }; const auto returnToId = view->data()->fullId(); const auto externalLink = [=](ClickContext context) { const auto my = context.other.value(); @@ -314,8 +415,7 @@ void Reply::setLinkFrom( channel, messageId, returnToId, - quote, - quoteOffset + highlight )->onClick(context); } else { controller->showPeerInfo(channel); @@ -336,7 +436,7 @@ void Reply::setLinkFrom( const auto message = data->resolvedMessage.get(); const auto story = data->resolvedStory.get(); _link = message - ? JumpToMessageClickHandler(message, returnToId, quote, quoteOffset) + ? JumpToMessageClickHandler(message, returnToId, highlight) : story ? JumpToStoryClickHandler(story) : (data->external() @@ -873,18 +973,28 @@ TextWithEntities Reply::ForwardEmoji(not_null owner) { TextWithEntities Reply::ComposePreviewName( not_null history, not_null to, - bool quote) { + const FullReplyTo &replyTo) { const auto sender = [&] { if (const auto from = to->displayFrom()) { return not_null(from); } return to->author(); }(); + if (const auto media = replyTo.todoItemId ? to->media() : nullptr) { + if (const auto todolist = media->todolist()) { + return tr::lng_preview_reply_to_task( + tr::now, + lt_title, + todolist->title, + Ui::Text::WithEntities); + } + } const auto toPeer = to->history()->peer; const auto displayAsExternal = (to->history() != history); const auto groupNameAdded = displayAsExternal && (toPeer != sender) && (toPeer->isChat() || toPeer->isMegagroup()); + const auto quote = replyTo && !replyTo.quote.empty(); const auto shorten = groupNameAdded || quote; auto nameFull = TextWithEntities(); diff --git a/Telegram/SourceFiles/history/view/history_view_reply.h b/Telegram/SourceFiles/history/view/history_view_reply.h index 416f4c0097..3e1a55addc 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.h +++ b/Telegram/SourceFiles/history/view/history_view_reply.h @@ -110,7 +110,7 @@ public: [[nodiscard]] static TextWithEntities ComposePreviewName( not_null history, not_null to, - bool quote); + const FullReplyTo &replyTo); private: [[nodiscard]] Ui::Text::GeometryDescriptor textGeometry( diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index d888247952..f362110b0b 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -438,12 +438,8 @@ void ScheduledWidget::setupComposeControls() { if (item->isScheduled() && item->history() == _history) { showAtPosition(item->position()); } else { - JumpToMessageClickHandler( - item, - {}, - to.quote, - to.quoteOffset - )->onClick({}); + const auto highlight = to.highlight(); + JumpToMessageClickHandler(item, {}, highlight)->onClick({}); } } }, lifetime()); diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index 47dd11eb8d..ce283943a0 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -463,17 +463,16 @@ QSize Service::performCountCurrentSize(int newWidth) { const auto media = this->media(); const auto mediaDisplayed = media && media->isDisplayed(); auto contentWidth = newWidth; + if (delegate()->elementChatMode() == ElementChatMode::Wide) { + accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); + } + contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins + if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) { + contentWidth = st::msgServicePadding.left() + st::msgServicePadding.right() + 1; + } if (mediaDisplayed && media->hideServiceText()) { newHeight += media->resizeGetHeight(newWidth) + marginBottom(); } else if (!text().isEmpty()) { - if (delegate()->elementChatMode() == ElementChatMode::Wide) { - accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); - } - contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins - if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) { - contentWidth = st::msgServicePadding.left() + st::msgServicePadding.right() + 1; - } - auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0); newHeight += (contentWidth >= maxWidth()) ? minHeight() diff --git a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp index 234bbca061..19036b8788 100644 --- a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp +++ b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp @@ -432,10 +432,14 @@ void SubsectionTabs::setupSlider( .session = &session(), }), }, paused); - slider->setActiveSectionFast(activeIndex); + + const auto ignoreActiveScroll = (scrollSavingIndex >= 0); + slider->setActiveSectionFast(activeIndex, ignoreActiveScroll); _sectionsSlice = _slice; - if (scrollSavingIndex >= 0) { + Assert(slider->sectionsCount() == _slice.size()); + if (ignoreActiveScroll) { + Assert(scrollSavingIndex < slider->sectionsCount()); const auto position = scrollSavingShift + slider->lookupSectionPosition(scrollSavingIndex); if (vertical) { @@ -702,6 +706,8 @@ void SubsectionTabs::refreshSlice() { if (_slice != slice) { _slice = std::move(slice); _refreshed.fire({}); + Assert((!_horizontal && !_vertical) + || (_slice.size() == _sectionsSlice.size())); } }); const auto push = [&](not_null thread) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_todo_list.cpp b/Telegram/SourceFiles/history/view/media/history_view_todo_list.cpp index 4f37d36d0c..e468a46eae 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_todo_list.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_todo_list.cpp @@ -334,9 +334,11 @@ void TodoList::updateTasks(bool skipAnimations) { ClickHandlerPtr TodoList::createTaskClickHandler( const Task &task) { const auto id = task.id; - return std::make_shared(crl::guard(this, [=] { + auto result = std::make_shared(crl::guard(this, [=] { toggleCompletion(id); })); + result->setProperty(kTodoListItemIdProperty, id); + return result; } void TodoList::startToggleAnimation(Task &task) { @@ -375,11 +377,24 @@ void TodoList::toggleCompletion(int id) { if (i == end(_tasks)) { return; } + const auto selected = (i->completionDate != 0); i->completionDate = selected ? TimeId() : base::unixtime::now(); if (!selected) { i->setCompletedBy(_parent->history()->session().user()); } + + const auto parentMedia = _parent->data()->media(); + const auto baseList = parentMedia ? parentMedia->todolist() : nullptr; + if (baseList) { + const auto j = ranges::find(baseList->items, id, &TodoListItem::id); + if (j != end(baseList->items)) { + j->completionDate = i->completionDate; + j->completedBy = i->completedBy; + } + history()->owner().updateDependentMessages(_parent->data()); + } + startToggleAnimation(*i); repaint(); @@ -467,6 +482,7 @@ void TodoList::draw(Painter &p, const PaintContext &context) const { paintw, width(), context); + appendTaskHighlight(task.id, tshift, height, context); if (was) { heavy = true; } else if (!task.userpic.null()) { @@ -561,6 +577,33 @@ int TodoList::paintTask( return height; } +void TodoList::appendTaskHighlight( + int id, + int top, + int height, + const PaintContext &context) const { + if (context.highlight.todoItemId != id + || context.highlight.collapsion <= 0.) { + return; + } + const auto to = context.highlightInterpolateTo; + const auto toProgress = (1. - context.highlight.collapsion); + if (toProgress >= 1.) { + context.highlightPathCache->addRect(to); + } else if (toProgress <= 0.) { + context.highlightPathCache->addRect(0, top, width(), height); + } else { + const auto lerp = [=](int from, int to) { + return from + (to - from) * toProgress; + }; + context.highlightPathCache->addRect( + lerp(0, to.x()), + lerp(top, to.y()), + lerp(width(), to.width()), + lerp(height, to.height())); + } +} + void TodoList::paintRadio( Painter &p, const Task &task, diff --git a/Telegram/SourceFiles/history/view/media/history_view_todo_list.h b/Telegram/SourceFiles/history/view/media/history_view_todo_list.h index 733c5c2ab0..b17e7853cc 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_todo_list.h +++ b/Telegram/SourceFiles/history/view/media/history_view_todo_list.h @@ -117,6 +117,11 @@ private: int top, int paintw, const PaintContext &context) const; + void appendTaskHighlight( + int id, + int top, + int height, + const PaintContext &context) const; void radialAnimationCallback() const; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp index 5daa9f48f8..d62944cc6b 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp @@ -835,12 +835,12 @@ InlineListData InlineListDataFromMessage(not_null view) { using Flag = InlineListData::Flag; const auto item = view->data(); const auto &settings = AyuSettings::getInstance(); - if (!settings.hideChannelReactions + if (!settings.showChannelReactions && item->history()->peer->isChannel() && !item->history()->peer->isMegagroup()) { return InlineListData(); } - if (!settings.hideGroupReactions + if (!settings.showGroupReactions && item->history()->peer->isMegagroup()) { return InlineListData(); } diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index c2eae3ffb2..61f332f6a5 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -1189,11 +1189,6 @@ bool AdjustMenuGeometryForSelector( not_null menu, QPoint desiredPosition, not_null selector) { - const auto &settings = AyuSettings::getInstance(); - if (!AyuUi::needToShowItem(settings.showReactionsPanelInContextMenu)) { - return false; - } - const auto useTransparency = selector->useTransparency(); const auto extend = useTransparency ? st::reactStripExtend @@ -1362,6 +1357,12 @@ AttachSelectorResult AttachSelectorToMenu( return AttachSelectorResult::Skipped; } + const auto peer = item->history()->peer; + if ((peer->isChannel() && !peer->isMegagroup() && !settings.showChannelReactions) + || (peer->isMegagroup() && !settings.showGroupReactions)) { + return AttachSelectorResult::Skipped; + } + const auto result = AttachSelectorToMenu( menu, desiredPosition, diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index e20c3d861e..3eae2fe12d 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -731,8 +731,8 @@ manageDeleteGroupButton: SettingsCountButton(manageGroupNoIconButton) { manageGroupReactions: IconButton(defaultIconButton) { width: 24px; height: 36px; - icon: icon{{ "info/edit/stickers_add", historyComposeIconFg }}; - iconOver: icon{{ "info/edit/stickers_add", historyComposeIconFgOver }}; + icon: icon{{ "menu/add", historyComposeIconFg }}; + iconOver: icon{{ "menu/add", historyComposeIconFgOver }}; } manageGroupReactionsField: InputField(defaultInputField) { textMargins: margins(1px, 12px, 24px, 8px); diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 86b7d7b527..8fd731a9e5 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_premium.h" #include "apiwrap.h" +#include "boxes/star_gift_box.h" #include "data/data_channel.h" #include "data/data_credits.h" #include "data/data_session.h" @@ -380,6 +381,7 @@ void InnerWidget::loadMore() { _entries.clear(); } _entries.reserve(_entries.size() + data.vgifts().v.size()); + auto hasUnique = false; for (const auto &gift : data.vgifts().v) { if (auto parsed = Api::FromTL(_peer, gift)) { auto descriptor = DescriptorForGift(_peer, *parsed); @@ -387,10 +389,15 @@ void InnerWidget::loadMore() { .gift = std::move(*parsed), .descriptor = std::move(descriptor), }); + hasUnique = (parsed->info.unique != nullptr); } } refreshButtons(); refreshAbout(); + + if (hasUnique) { + Ui::PreloadUniqueGiftResellPrices(&_peer->session()); + } }).fail([=] { _loadMoreRequestId = 0; _allLoaded = true; diff --git a/Telegram/SourceFiles/lang/lang_tag.cpp b/Telegram/SourceFiles/lang/lang_tag.cpp index eb6f938bf8..02620f2e1d 100644 --- a/Telegram/SourceFiles/lang/lang_tag.cpp +++ b/Telegram/SourceFiles/lang/lang_tag.cpp @@ -949,7 +949,20 @@ QString FormatCountDecimal(int64 number) { } QString FormatExactCountDecimal(float64 number) { - return QLocale().toString(number, 'f', QLocale::FloatingPointShortest); + const auto locale = QLocale(); + if (qFuzzyCompare(number, base::SafeRound(number))) { + return locale.toString(int64(base::SafeRound(number))); + } + + // Somehow using QLocale::FloatingPointShortest sometimes produces + // "0.8500000000000001" on some systems / locales, + // so I want to stick to 6 digits max (default third argument value). + auto result = locale.toString(number, 'f'); + const auto zero = locale.zeroDigit(); + while (result.endsWith(zero)) { + result.chop(1); + } + return result; } ShortenedCount FormatCreditsAmountToShort(CreditsAmount amount) { diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 2513a792cb..73fd0fa725 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -1087,3 +1087,34 @@ mediaviewSponsoredButton: RoundButton(defaultActiveButton) { ripple: universalRippleAnimation; } + +mediaSponsoredSkip: 16px; +mediaSponsoredShift: 16px; +mediaSponsoredPadding: margins(12px, 8px, 8px, 8px); +mediaSponsoredThumb: 48px; +mediaSponsoredCloseTwice: 3px; +mediaSponsoredCloseSmall: 3px; +mediaSponsoredCloseSize: 11px; +mediaSponsoredCloseCorner: 6px; +mediaSponsoredCloseFull: 64px; +mediaSponsoredCloseStroke: 2px; +mediaSponsoredCloseRipple: 36px; +mediaSponsoredCloseDiameter: 24px; +mediaSponsoredCloseFont: font(12px bold); + +mediaSponsoredAbout: RoundButton(defaultActiveButton) { + textFg: windowActiveTextFg; + textFgOver: windowActiveTextFg; + textBg: lightButtonBgOver; + textBgOver: lightButtonBgOver; + width: -12px; + height: 18px; + radius: 9px; + textTop: 0px; + style: TextStyle(defaultTextStyle) { + font: font(12px); + } + ripple: RippleAnimation(defaultRippleAnimation) { + color: lightButtonBgRipple; + } +} diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index afd69eb966..f6d64f14c6 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -51,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/view/media_view_pip.h" #include "media/view/media_view_overlay_raster.h" #include "media/view/media_view_overlay_opengl.h" +#include "media/view/media_view_playback_sponsored.h" #include "media/stories/media_stories_share.h" #include "media/stories/media_stories_view.h" #include "media/streaming/media_streaming_document.h" @@ -339,6 +340,7 @@ struct OverlayWidget::Streamed { Streaming::Instance instance; std::unique_ptr controls; + std::unique_ptr sponsored; std::unique_ptr powerSaveBlocker; bool ready = false; @@ -1617,7 +1619,11 @@ void OverlayWidget::fillContextMenuActions( if (const auto window = findWindow()) { const auto show = window->uiShow(); const auto fullId = _message->fullId(); - Menu::FillSponsored(_body, addAction, show, fullId, true); + Menu::FillSponsored( + addAction, + show, + fullId, + { .dark = true, .skipInfo = true }); } return; } @@ -3981,7 +3987,11 @@ bool OverlayWidget::initStreaming(const StartStreaming &startStreaming) { && !_streamed->instance.player().finished())) { startStreamingPlayer(startStreaming); } else { - _streamed->ready = _streamed->instance.player().ready(); + if (_streamed->instance.player().ready()) { + markStreamedReady(); + } else { + _streamed->ready = false; + } updatePlaybackState(); } return true; @@ -3994,7 +4004,7 @@ void OverlayWidget::startStreamingPlayer( const auto &player = _streamed->instance.player(); if (player.playing()) { if (!_streamed->withSound) { - _streamed->ready = true; + markStreamedReady(); return; } _pip = nullptr; @@ -4012,6 +4022,18 @@ void OverlayWidget::startStreamingPlayer( restartAtSeekPosition(_streamedPosition); } +void OverlayWidget::markStreamedReady() { + Expects(_streamed != nullptr); + + if (_streamed->ready) { + return; + } + _streamed->ready = true; + if (const auto sponsored = _streamed->sponsored.get()) { + sponsored->start(); + } +} + void OverlayWidget::initStreamingThumbnail() { Expects(_photo || _document); @@ -4083,7 +4105,7 @@ void OverlayWidget::initStreamingThumbnail() { } void OverlayWidget::streamingReady(Streaming::Information &&info) { - _streamed->ready = true; + markStreamedReady(); if (videoShown()) { applyVideoSize(); _streamedQualityChangeFrame = QImage(); @@ -4105,6 +4127,7 @@ void OverlayWidget::applyVideoSize() { bool OverlayWidget::createStreamingObjects() { Expects(_photo || _document); + Expects(!_streamed); const auto origin = fileOrigin(); const auto callback = [=] { waitingAnimationCallback(); }; @@ -4137,6 +4160,18 @@ bool OverlayWidget::createStreamingObjects() { _body, static_cast(this)); _streamed->controls->show(); + _streamed->sponsored = PlaybackSponsored::Has(_message) + ? std::make_unique( + _streamed->controls.get(), + uiShow(), + _message) + : nullptr; + if (const auto sponsored = _streamed->sponsored.get()) { + _layerBg->layerShownValue( + ) | rpl::start_with_next([=](bool shown) { + sponsored->setPaused(shown); + }, sponsored->lifetime()); + } refreshClipControllerGeometry(); } return true; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 8787be47e4..2044d9caae 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -78,6 +78,7 @@ struct ContentLayout; namespace Media::View { +class PlaybackSponsored; class GroupThumbs; class Pip; @@ -412,6 +413,7 @@ private: const StartStreaming &startStreaming = StartStreaming()); void startStreamingPlayer(const StartStreaming &startStreaming); void initStreamingThumbnail(); + void markStreamedReady(); void streamingReady(Streaming::Information &&info); [[nodiscard]] bool createStreamingObjects(); void handleStreamingUpdate(Streaming::Update &&update); diff --git a/Telegram/SourceFiles/media/view/media_view_playback_controls.h b/Telegram/SourceFiles/media/view/media_view_playback_controls.h index a91b8ad2e9..be3e84c824 100644 --- a/Telegram/SourceFiles/media/view/media_view_playback_controls.h +++ b/Telegram/SourceFiles/media/view/media_view_playback_controls.h @@ -19,14 +19,13 @@ class MediaSlider; class PopupMenu; } // namespace Ui -namespace Media { -namespace Player { +namespace Media::Player { struct TrackState; class SettingsButton; class SpeedController; -} // namespace Player +} // namespace Media::Player -namespace View { +namespace Media::View { class PlaybackProgress; @@ -131,5 +130,4 @@ private: }; -} // namespace View -} // namespace Media +} // namespace Media::View diff --git a/Telegram/SourceFiles/media/view/media_view_playback_sponsored.cpp b/Telegram/SourceFiles/media/view/media_view_playback_sponsored.cpp new file mode 100644 index 0000000000..895b4c70b4 --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_view_playback_sponsored.cpp @@ -0,0 +1,767 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "media/view/media_view_playback_sponsored.h" + +#include "boxes/premium_preview_box.h" +#include "data/components/sponsored_messages.h" +#include "data/data_file_origin.h" +#include "data/data_photo.h" +#include "data/data_photo_media.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "menu/menu_sponsored.h" +#include "ui/effects/numbers_animation.h" +#include "ui/effects/ripple_animation.h" +#include "ui/widgets/menu/menu_add_action_callback.h" +#include "ui/widgets/menu/menu_add_action_callback_factory.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/popup_menu.h" +#include "ui/basic_click_handlers.h" +#include "ui/painter.h" +#include "ui/ui_utility.h" +#include "ui/cached_round_corners.h" +#include "styles/style_chat.h" +#include "styles/style_media_view.h" + +namespace Media::View { +namespace { + +constexpr auto kStartDelayMin = crl::time(1000); +constexpr auto kDurationMin = 5 * crl::time(1000); + +enum class Action { + Close, + PromotePremium, + Pause, + Unpause, +}; + +class Close final : public Ui::RippleButton { +public: + Close( + not_null parent, + const style::RippleAnimation &st, + rpl::producer allowCloseAt); + + [[nodiscard]] rpl::producer actions() const; + +private: + QPoint prepareRippleStartPosition() const override; + QImage prepareRippleMask() const override; + void paintEvent(QPaintEvent *e) override; + + void updateProgress(crl::time now); + + rpl::event_stream _actions; + + Ui::NumbersAnimation _countdown; + Ui::Animations::Basic _progress; + base::Timer _noAnimationTimer; + crl::time _allowCloseAt = 0; + crl::time _startedAt = 0; + crl::time _pausedAt = 0; + int _secondsTill = 0; + int _rippleSize = 0; + QPoint _rippleOrigin; + bool _allowClose = false; + +}; + +Close::Close( + not_null parent, + const style::RippleAnimation &st, + rpl::producer allowCloseAt) +: RippleButton(parent, st) +, _countdown(st::mediaSponsoredCloseFont, [=] { update(); }) +, _progress([=](crl::time now) { updateProgress(now); }) +, _noAnimationTimer([=] { updateProgress(crl::now()); }) +, _startedAt(crl::now()) { + resize(st::mediaSponsoredCloseFull, st::mediaSponsoredCloseFull); + + const auto size = st::mediaSponsoredCloseRipple; + const auto cut = int(base::SafeRound((width() - size) / 2.)); + _rippleSize = std::min(width() - 2 * cut, height() - 2 * cut); + _rippleOrigin = QPoint( + (width() - _rippleSize) / 2, + (height() - _rippleSize) / 2); + + std::move( + allowCloseAt + ) | rpl::start_with_next([=](crl::time at) { + const auto now = crl::now(); + if (!at) { + updateProgress(now); + _pausedAt = now; + _progress.stop(); + } else { + if (_pausedAt) { + _startedAt += now - base::take(_pausedAt); + } + _allowCloseAt = at; + updateProgress(now); + if (!anim::Disabled()) { + _progress.start(); + } else if (!_allowClose) { + _noAnimationTimer.callEach(crl::time(200)); + } + } + }, lifetime()); + updateProgress(_startedAt); + + setClickedCallback([=] { + _actions.fire(_allowClose ? Action::Close : Action::PromotePremium); + }); +} + +rpl::producer Close::actions() const { + return _actions.events(); +} + +void Close::updateProgress(crl::time now) { + update(); +} + +QPoint Close::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()) - _rippleOrigin; +} + +QImage Close::prepareRippleMask() const { + return Ui::RippleAnimation::EllipseMask({ _rippleSize, _rippleSize }); +} + +void Close::paintEvent(QPaintEvent *e) { + auto p = QPainter(this); + + paintRipple(p, _rippleOrigin); + + const auto now = crl::now(); + if (!_pausedAt) { + _allowClose = (now >= _allowCloseAt); + } + const auto msTill = _allowCloseAt - (_pausedAt ? _pausedAt : now); + const auto msFull = _allowCloseAt - _startedAt; + const auto secondsTill = (std::max(msTill, crl::time()) + 999) / 1000; + const auto secondsFull = (std::max(msFull, crl::time()) + 999) / 1000; + const auto allowCloseLeft = anim::Disabled() + ? (secondsFull ? (secondsTill / float64(secondsFull)) : 0) + : std::max(msFull ? (msTill / float64(msFull)) : 0., 0.); + const auto duration = crl::time(st::fadeWrapDuration); + const auto allowedProgress = anim::Disabled() + ? (secondsTill ? 0. : 1.) + : std::clamp(-msTill, crl::time(), duration) / float64(duration); + + if (_secondsTill != secondsTill) { + const auto initial = !_secondsTill; + _secondsTill = secondsTill; + _countdown.setText(QString::number(_secondsTill), _secondsTill); + if (initial) { + _countdown.finishAnimating(); + } + } + + auto pen = st::mediaviewTextLinkFg->p; + if (allowedProgress < 1.) { + if (allowedProgress > 0.) { + p.setOpacity(1. - allowedProgress); + } + p.setPen(pen); + + const auto inner = QRect( + (width() - st::mediaSponsoredCloseDiameter) / 2, + (height() - st::mediaSponsoredCloseDiameter) / 2, + st::mediaSponsoredCloseDiameter, + st::mediaSponsoredCloseDiameter); + p.setFont(st::mediaSponsoredCloseFont); + _countdown.paint( + p, + inner.x() + (inner.width() - _countdown.countWidth()) / 2, + (inner.y() + + (inner.height() + - st::mediaSponsoredCloseFont->height) / 2), + width()); + + const auto skip = 0.23; + const auto len = int(base::SafeRound( + arc::kFullLength * (1. - skip) * allowCloseLeft)); + if (len > 0) { + const auto from = arc::kFullLength / 4; + auto hq = PainterHighQualityEnabler(p); + pen.setWidthF(st::mediaSponsoredCloseStroke); + pen.setCapStyle(Qt::RoundCap); + p.setPen(pen); + p.drawArc(inner, from, len); + } + + p.setOpacity(1.); + } + + const auto sizeFinal = st::mediaSponsoredCloseSize; + const auto sizeSmall = st::mediaSponsoredCloseCorner; + const auto twiceFinal = st::mediaSponsoredCloseTwice; + const auto twiceSmall = st::mediaSponsoredCloseSmall; + const auto size = sizeSmall + allowedProgress * (sizeFinal - sizeSmall); + const auto twice = twiceSmall + + allowedProgress * (twiceFinal - twiceSmall); + const auto leftFinal = (width() - size) / 2.; + const auto leftSmall = (width() + st::mediaSponsoredCloseDiameter) / 2. + - (st::mediaSponsoredCloseStroke / 2.) + - sizeSmall; + const auto topFinal = (height() - size) / 2.; + const auto topSmall = (height() - st::mediaSponsoredCloseDiameter) / 2.; + const auto left = leftSmall + allowedProgress * (leftFinal - leftSmall); + const auto top = topSmall + allowedProgress * (topFinal - topSmall); + + auto hq = PainterHighQualityEnabler(p); + pen.setWidthF(twice / 2.); + p.setPen(pen); + p.drawLine(QPointF(left, top), QPointF(left + size, top + size)); + p.drawLine(QPointF(left + size, top), QPointF(left, top + size)); +} + +[[nodiscard]] style::RoundButton PrepareAboutStyle() { + static auto textBg = style::complex_color([] { + auto result = st::mediaviewTextLinkFg->c; + result.setAlphaF(result.alphaF() * 0.1); + return result; + }); + static auto textBgOver = style::complex_color([] { + auto result = st::mediaviewTextLinkFg->c; + result.setAlphaF(result.alphaF() * 0.15); + return result; + }); + static auto rippleColor = style::complex_color([] { + auto result = st::mediaviewTextLinkFg->c; + result.setAlphaF(result.alphaF() * 0.2); + return result; + }); + + auto result = st::mediaSponsoredAbout; + result.textFg = st::mediaviewTextLinkFg; + result.textFgOver = st::mediaviewTextLinkFg; + result.textBg = textBg.color(); + result.textBgOver = textBgOver.color(); + result.ripple.color = rippleColor.color(); + return result; +} + +} // namespace + +class PlaybackSponsored::Message final : public Ui::RpWidget { +public: + Message( + QWidget *parent, + std::shared_ptr show, + const Data::SponsoredMessage &data, + rpl::producer allowCloseAt); + + [[nodiscard]] rpl::producer actions() const; + + void setFinalPosition(int x, int y); + + void fadeIn(); + void fadeOut(Fn hidden); + +private: + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + + int resizeGetHeight(int newWidth) override; + + void populate(); + void startFadeIn(); + void updateShown(Fn finished = nullptr); + void startFade(Fn finished); + + const not_null _session; + const std::shared_ptr _show; + const Data::SponsoredMessage _data; + + style::RoundButton _aboutSt; + std::unique_ptr _about; + std::unique_ptr _close; + + base::unique_qptr _menu; + rpl::event_stream _actions; + + std::shared_ptr _photo; + Ui::Text::String _title; + Ui::Text::String _text; + + QPoint _finalPosition; + int _left = 0; + int _top = 0; + int _titleHeight = 0; + int _textHeight = 0; + + QImage _cache; + Ui::Animations::Simple _showAnimation; + bool _shown = false; + bool _over = false; + bool _pressed = false; + + rpl::lifetime _photoLifetime; + +}; + +PlaybackSponsored::Message::Message( + QWidget *parent, + std::shared_ptr show, + const Data::SponsoredMessage &data, + rpl::producer allowCloseAt) +: RpWidget(parent) +, _session(&data.history->session()) +, _show(std::move(show)) +, _data(data) +, _aboutSt(PrepareAboutStyle()) +, _about(std::make_unique( + this, + tr::lng_search_sponsored_button(), + _aboutSt)) +, _close( + std::make_unique( + this, + _aboutSt.ripple, + std::move(allowCloseAt))) { + _about->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + setMouseTracking(true); + populate(); + hide(); +} + +rpl::producer PlaybackSponsored::Message::actions() const { + return rpl::merge(_actions.events(), _close->actions()); +} + +void PlaybackSponsored::Message::setFinalPosition(int x, int y) { + _finalPosition = { x, y }; + if (_shown) { + updateShown(); + } +} + +void PlaybackSponsored::Message::fadeIn() { + _shown = true; + if (!_photo || _photo->loaded()) { + startFadeIn(); + return; + } + _photo->owner()->session().downloaderTaskFinished( + ) | rpl::filter([=] { + return _photo->loaded(); + }) | rpl::start_with_next([=] { + _photoLifetime.destroy(); + startFadeIn(); + }, _photoLifetime); +} + +void PlaybackSponsored::Message::startFadeIn() { + if (!_shown) { + return; + } + startFade([=] { + _session->sponsoredMessages().view(_data.randomId); + }); + show(); +} + +void PlaybackSponsored::Message::fadeOut(Fn hidden) { + if (!_shown) { + if (const auto onstack = hidden) { + onstack(); + } + return; + } + _shown = false; + startFade(std::move(hidden)); +} + +void PlaybackSponsored::Message::startFade(Fn finished) { + _cache = Ui::GrabWidgetToImage(this); + _about->hide(); + _close->hide(); + const auto from = _shown ? 0. : 1.; + const auto till = _shown ? 1. : 0.; + _showAnimation.start([=] { + updateShown(finished); + }, from, till, st::fadeWrapDuration); +} + +void PlaybackSponsored::Message::updateShown(Fn finished) { + const auto shown = _showAnimation.value(_shown ? 1. : 0.); + const auto shift = anim::interpolate(st::mediaSponsoredShift, 0, shown); + move(_finalPosition.x(), _finalPosition.y() + shift); + update(); + if (!_showAnimation.animating()) { + _cache = QImage(); + _close->show(); + _about->show(); + if (const auto onstack = finished) { + onstack(); + } + } +} + +void PlaybackSponsored::Message::paintEvent(QPaintEvent *e) { + auto p = QPainter(this); + + const auto shown = _showAnimation.value(_shown ? 1. : 0.); + if (!_cache.isNull()) { + p.setOpacity(shown); + p.drawImage(0, 0, _cache); + return; + } + + Ui::FillRoundRect( + p, + rect(), + st::mediaviewSaveMsgBg, + Ui::MediaviewSaveCorners); + + const auto &padding = st::mediaSponsoredPadding; + if (_photo) { + if (const auto image = _photo->image(Data::PhotoSize::Large)) { + const auto size = st::mediaSponsoredThumb; + const auto x = padding.left(); + const auto y = (height() - size) / 2; + p.drawPixmap( + x, + y, + image->pixSingle( + size, + size, + { .options = Images::Option::RoundCircle })); + } + } + + p.setPen(st::mediaviewControlFg); + + _title.draw(p, { + .position = { _left, _top }, + .availableWidth = _about->x() - _left, + .palette = &st::mediaviewTextPalette, + }); + + _text.draw(p, { + .position = { _left, _top + _titleHeight }, + .availableWidth = _close->x() - _left, + .palette = &st::mediaviewTextPalette, + }); +} + +void PlaybackSponsored::Message::mouseMoveEvent(QMouseEvent *e) { + const auto &padding = st::mediaSponsoredPadding; + const auto point = e->pos(); + const auto about = _about->geometry(); + const auto close = _close->geometry(); + const auto over = !about.marginsAdded(padding).contains(point) + && !close.marginsAdded(padding).contains(point); + if (_over != over) { + _over = over; + setCursor(_over ? style::cur_pointer : style::cur_default); + } +} + +void PlaybackSponsored::Message::mousePressEvent(QMouseEvent *e) { + if (_over) { + _pressed = true; + } +} + +void PlaybackSponsored::Message::mouseReleaseEvent(QMouseEvent *e) { + if (base::take(_pressed) && _over) { + _session->sponsoredMessages().clicked(_data.randomId, false, false); + UrlClickHandler::Open(_data.link); + } +} + +int PlaybackSponsored::Message::resizeGetHeight(int newWidth) { + const auto &padding = st::mediaSponsoredPadding; + const auto userpic = st::mediaSponsoredThumb; + const auto innerWidth = newWidth - _left - _close->width(); + const auto titleWidth = innerWidth - _about->width() - padding.right(); + _titleHeight = _title.countHeight(titleWidth); + _textHeight = _text.countHeight(innerWidth); + + const auto use = std::max(_titleHeight + _textHeight, userpic); + + const auto height = padding.top() + use + padding.bottom(); + _left = padding.left() + (_photo ? (userpic + padding.left()) : 0); + _top = padding.top() + (use - _titleHeight - _textHeight) / 2; + + _about->move( + _left + std::min(titleWidth, _title.maxWidth()) + padding.right(), + _top); + _close->move( + newWidth - _close->width(), + (height - _close->height()) / 2); + + return height; +} + +void PlaybackSponsored::Message::populate() { + const auto &from = _data.from; + const auto photo = from.photoId + ? _data.history->owner().photo(from.photoId).get() + : nullptr; + if (photo) { + _photo = photo->createMediaView(); + photo->load({}, LoadFromCloudOrLocal, true); + } + _title = Ui::Text::String( + st::semiboldTextStyle, + from.title, + kDefaultTextOptions, + st::msgMinWidth); + _text = Ui::Text::String( + st::defaultTextStyle, + _data.textWithEntities, + kMarkupTextOptions, + st::msgMinWidth); + + _about->setClickedCallback([=] { + _menu = nullptr; + const auto parent = parentWidget(); + _menu = base::make_unique_q( + parent, + st::mediaviewPopupMenu); + const auto raw = _menu.get(); + const auto addAction = Ui::Menu::CreateAddActionCallback(raw); + Menu::FillSponsored( + addAction, + _show, + Menu::SponsoredPhrases::Channel, + _session->sponsoredMessages().lookupDetails(_data), + _session->sponsoredMessages().createReportCallback( + _data.randomId, + crl::guard(this, [=] { _actions.fire(Action::Close); })), + { .dark = true }); + _actions.fire(Action::Pause); + Ui::Connect(raw, &QObject::destroyed, this, [=] { + _actions.fire(Action::Unpause); + }); + raw->popup(QCursor::pos()); + }); +} + +PlaybackSponsored::PlaybackSponsored( + not_null controls, + std::shared_ptr show, + not_null item) +: _parent(controls->parentWidget()) +, _session(&item->history()->session()) +, _show(std::move(show)) +, _itemId(item->fullId()) +, _controlsGeometry(controls->geometryValue()) +, _timer([=] { update(); }) { + _session->sponsoredMessages().requestForVideo(item, crl::guard(this, [=]( + Data::SponsoredForVideo data) { + if (data.list.empty()) { + return; + } + _data = std::move(data); + if (_data->state.initial() + || (_data->state.itemIndex > _data->list.size()) + || (_data->state.itemIndex == _data->list.size() + && _data->state.leftTillShow <= 0)) { + _data->state.itemIndex = 0; + _data->state.leftTillShow = std::max( + _data->startDelay, + kStartDelayMin); + } + update(); + })); +} + +PlaybackSponsored::~PlaybackSponsored() { + saveState(); +} + +void PlaybackSponsored::start() { + _started = true; + if (!_paused) { + _start = crl::now(); + update(); + } +} + +void PlaybackSponsored::setPaused(bool paused) { + setPausedOutside(paused); +} + +void PlaybackSponsored::updatePaused() { + const auto paused = _pausedInside || _pausedOutside; + if (_paused == paused) { + return; + } else if (_started && paused) { + update(); + } + _paused = paused; + if (!_started) { + return; + } else if (_paused) { + _start = 0; + _timer.cancel(); + _allowCloseAt = 0; + } else { + _start = crl::now(); + update(); + } +} + +void PlaybackSponsored::setPausedInside(bool paused) { + if (_pausedInside == paused) { + return; + } + _pausedInside = paused; + updatePaused(); +} + +void PlaybackSponsored::setPausedOutside(bool paused) { + if (_pausedOutside == paused) { + return; + } + _pausedOutside = paused; + updatePaused(); +} + +void PlaybackSponsored::finish() { + _timer.cancel(); + if (_data) { + saveState(); + _data = std::nullopt; + } +} + +void PlaybackSponsored::update() { + if (!_data || !_start) { + return; + } + + const auto [now, state] = computeState(); + const auto message = (_data->state.itemIndex < _data->list.size()) + ? &_data->list[state.itemIndex] + : nullptr; + const auto duration = message + ? std::max( + message->durationMin + kDurationMin, + message->durationMax) + : crl::time(0); + if (_data->state.leftTillShow > 0 && state.leftTillShow <= 0) { + _data->state.leftTillShow = 0; + if (duration) { + _allowCloseAt = now + message->durationMin; + show(*message); + + _start = now; + _timer.callOnce(duration); + saveState(); + } else { + finish(); + } + } else if (_data->state.leftTillShow <= 0 + && state.leftTillShow <= -duration) { + hide(now); + } else { + if (state.leftTillShow <= 0 && duration) { + _allowCloseAt = now + state.leftTillShow + message->durationMin; + if (!_widget) { + show(*message); + } + } + _data->state = state; + _timer.callOnce((state.leftTillShow > 0) + ? state.leftTillShow + : (state.leftTillShow + duration)); + } +} + + +void PlaybackSponsored::show(const Data::SponsoredMessage &data) { + _widget = std::make_unique( + _parent, + _show, + data, + _allowCloseAt.value()); + const auto raw = _widget.get(); + + _controlsGeometry.value() | rpl::start_with_next([=](QRect controls) { + raw->resizeToWidth(controls.width()); + raw->setFinalPosition( + controls.x(), + controls.y() - st::mediaSponsoredSkip - raw->height()); + }, raw->lifetime()); + + raw->actions() | rpl::start_with_next([=](Action action) { + switch (action) { + case Action::Close: hide(crl::now()); break; + case Action::PromotePremium: showPremiumPromo(); break; + case Action::Pause: setPausedInside(true); break; + case Action::Unpause: setPausedInside(false); break; + } + }, raw->lifetime()); + + raw->fadeIn(); +} + +void PlaybackSponsored::showPremiumPromo() { + ShowPremiumPreviewBox(_show, PremiumFeature::NoAds); +} + +void PlaybackSponsored::hide(crl::time now) { + Expects(_widget != nullptr); + + _widget->fadeOut([this, raw = _widget.get()] { + if (_widget.get() == raw) { + _widget = nullptr; + } + }); + + ++_data->state.itemIndex; + _data->state.leftTillShow = std::max( + _data->betweenDelay, + kStartDelayMin); + _start = now; + _timer.callOnce(_data->state.leftTillShow); + saveState(); +} + +void PlaybackSponsored::saveState() { + _session->sponsoredMessages().updateForVideo( + _itemId, + computeState().data); +} + +PlaybackSponsored::State PlaybackSponsored::computeState() const { + auto result = State{ crl::now() }; + if (!_data) { + return result; + } + result.data = _data->state; + if (!_start) { + return result; + } + const auto elapsed = result.now - _start; + result.data.leftTillShow -= elapsed; + return result; +} + +rpl::lifetime &PlaybackSponsored::lifetime() { + return _lifetime; +} + +bool PlaybackSponsored::Has(HistoryItem *item) { + return item + && item->history()->session().sponsoredMessages().canHaveFor(item); +} + +} // namespace Media::View diff --git a/Telegram/SourceFiles/media/view/media_view_playback_sponsored.h b/Telegram/SourceFiles/media/view/media_view_playback_sponsored.h new file mode 100644 index 0000000000..84fe4b9c42 --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_view_playback_sponsored.h @@ -0,0 +1,83 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/timer.h" +#include "base/weak_ptr.h" +#include "data/components/sponsored_messages.h" + +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { +class RpWidget; +} // namespace Ui + +namespace Media::View { + +class PlaybackSponsored final : public base::has_weak_ptr { +public: + PlaybackSponsored( + not_null controls, + std::shared_ptr show, + not_null item); + ~PlaybackSponsored(); + + void start(); + void setPaused(bool paused); + + [[nodiscard]] rpl::lifetime &lifetime(); + + [[nodiscard]] static bool Has(HistoryItem *item); + +private: + class Message; + struct State { + crl::time now = 0; + Data::SponsoredForVideoState data; + }; + + void update(); + void finish(); + void updatePaused(); + void showPremiumPromo(); + void setPausedInside(bool paused); + void setPausedOutside(bool paused); + void show(const Data::SponsoredMessage &data); + void hide(crl::time now); + [[nodiscard]] State computeState() const; + void saveState(); + + const not_null _parent; + const not_null _session; + const std::shared_ptr _show; + const FullMsgId _itemId; + + rpl::variable _controlsGeometry; + std::unique_ptr _widget; + + rpl::variable _allowCloseAt; + crl::time _start = 0; + bool _started = false; + bool _paused = false; + bool _pausedInside = false; + bool _pausedOutside = false; + base::Timer _timer; + + std::optional _data; + + rpl::lifetime _lifetime; + +}; + +} // namespace Media::View diff --git a/Telegram/SourceFiles/menu/menu_sponsored.cpp b/Telegram/SourceFiles/menu/menu_sponsored.cpp index ec65ef28db..2073239170 100644 --- a/Telegram/SourceFiles/menu/menu_sponsored.cpp +++ b/Telegram/SourceFiles/menu/menu_sponsored.cpp @@ -287,14 +287,12 @@ void AboutBox( top->setForceRippled(false); }); FillSponsored( - top, Ui::Menu::CreateAddActionCallback(menu->get()), show, phrases, details, report, - false, - true); + { .skipAbout = true }); const auto global = top->mapToGlobal( QPoint(top->width() / 4 * 3, top->height() / 2)); raw->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight); @@ -390,18 +388,17 @@ void ShowReportSponsoredBox( } // namespace void FillSponsored( - not_null parent, const Ui::Menu::MenuCallback &addAction, std::shared_ptr show, SponsoredPhrases phrases, const Data::SponsoredMessages::Details &details, Data::SponsoredReportAction report, - bool mediaViewer, - bool skipAbout) { + SponsoredMenuSettings settings) { const auto session = &show->session(); const auto &info = details.info; + const auto dark = settings.dark; - if (!mediaViewer && !info.empty()) { + if (!settings.skipInfo && !info.empty()) { auto fillSubmenu = [&](not_null menu) { const auto allText = ranges::accumulate( info, @@ -416,8 +413,10 @@ void FillSponsored( for (const auto &i : info) { auto item = base::make_unique_q( menu, - st::defaultMenu, - st::historySponsorInfoItem, + dark ? st::storiesMenu : st::defaultMenu, + (dark + ? st::historySponsorInfoItemDark + : st::historySponsorInfoItem), st::historyHasCustomEmojiPosition, base::duplicate(i)); item->clicks( @@ -431,27 +430,31 @@ void FillSponsored( addAction({ .text = tr::lng_sponsored_info_menu(tr::now), .handler = nullptr, - .icon = &st::menuIconChannel, + .icon = (dark + ? &st::mediaMenuIconChannel + : &st::menuIconChannel), .fillSubmenu = std::move(fillSubmenu), }); addAction({ - .separatorSt = &st::expandedMenuSeparator, + .separatorSt = (dark + ? &st::mediaviewMenuSeparator + : &st::expandedMenuSeparator), .isSeparator = true, }); } if (details.canReport) { - if (!skipAbout) { + if (!settings.skipAbout) { addAction(tr::lng_sponsored_menu_revenued_about(tr::now), [=] { show->show(Box(AboutBox, show, phrases, details, report)); - }, (mediaViewer ? &st::mediaMenuIconInfo : &st::menuIconInfo)); + }, (dark ? &st::mediaMenuIconInfo : &st::menuIconInfo)); } addAction(tr::lng_sponsored_menu_revenued_report(tr::now), [=] { ShowReportSponsoredBox(show, report); - }, (mediaViewer ? &st::mediaMenuIconBlock : &st::menuIconBlock)); + }, (dark ? &st::mediaMenuIconBlock : &st::menuIconBlock)); addAction({ - .separatorSt = (mediaViewer + .separatorSt = (dark ? &st::mediaviewMenuSeparator : &st::expandedMenuSeparator), .isSeparator = true, @@ -464,26 +467,22 @@ void FillSponsored( } else { ShowPremiumPreviewBox(show, PremiumFeature::NoAds); } - }, (mediaViewer ? &st::mediaMenuIconCancel : &st::menuIconCancel)); + }, (dark ? &st::mediaMenuIconCancel : &st::menuIconCancel)); } void FillSponsored( - not_null parent, const Ui::Menu::MenuCallback &addAction, std::shared_ptr show, const FullMsgId &fullId, - bool mediaViewer, - bool skipAbout) { + SponsoredMenuSettings settings) { const auto session = &show->session(); FillSponsored( - parent, addAction, show, PhrasesForMessage(fullId), session->sponsoredMessages().lookupDetails(fullId), session->sponsoredMessages().createReportCallback(fullId), - mediaViewer, - skipAbout); + settings); } void ShowSponsored( @@ -495,11 +494,9 @@ void ShowSponsored( st::popupMenuWithIcons); FillSponsored( - parent, Ui::Menu::CreateAddActionCallback(menu), show, - fullId, - false); + fullId); menu->popup(QCursor::pos()); } diff --git a/Telegram/SourceFiles/menu/menu_sponsored.h b/Telegram/SourceFiles/menu/menu_sponsored.h index 106509e2e6..987630e4bb 100644 --- a/Telegram/SourceFiles/menu/menu_sponsored.h +++ b/Telegram/SourceFiles/menu/menu_sponsored.h @@ -33,23 +33,25 @@ enum class SponsoredPhrases { Search, }; +struct SponsoredMenuSettings { + bool dark = false; + bool skipAbout = false; + bool skipInfo = false; +}; + void FillSponsored( - not_null parent, const Ui::Menu::MenuCallback &addAction, std::shared_ptr show, SponsoredPhrases phrases, const Data::SponsoredMessageDetails &details, Data::SponsoredReportAction report, - bool mediaViewer, - bool skipAbout); + SponsoredMenuSettings settings = {}); void FillSponsored( - not_null parent, const Ui::Menu::MenuCallback &addAction, std::shared_ptr show, const FullMsgId &fullId, - bool mediaViewer, - bool skipAbout = false); + SponsoredMenuSettings settings = {}); void ShowSponsored( not_null parent, diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index e33dd8366b..c6e4c22b2c 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1344,7 +1344,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; -messageReplyHeader#afbc09db flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector quote_offset:flags.10?int = MessageReplyHeader; +messageReplyHeader#6917560b flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector quote_offset:flags.10?int todo_item_id:flags.11?int = MessageReplyHeader; messageReplyStoryHeader#e5af939 peer:Peer story_id:int = MessageReplyHeader; messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; @@ -1649,7 +1649,7 @@ stories.storyViewsList#59d78fc5 flags:# count:int views_count:int forwards_count stories.storyViews#de9eed1d views:Vector users:Vector = stories.StoryViews; -inputReplyToMessage#b07038b0 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector quote_offset:flags.4?int monoforum_peer_id:flags.5?InputPeer = InputReplyTo; +inputReplyToMessage#869fbe10 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector quote_offset:flags.4?int monoforum_peer_id:flags.5?InputPeer todo_item_id:flags.6?int = InputReplyTo; inputReplyToStory#5881323a peer:InputPeer story_id:int = InputReplyTo; inputReplyToMonoForum#69d66c45 monoforum_peer_id:InputPeer = InputReplyTo; @@ -2720,4 +2720,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 207 +// LAYER 209 diff --git a/Telegram/SourceFiles/platform/win/tray_win.cpp b/Telegram/SourceFiles/platform/win/tray_win.cpp index 8dcf5347dd..0052f6b009 100644 --- a/Telegram/SourceFiles/platform/win/tray_win.cpp +++ b/Telegram/SourceFiles/platform/win/tray_win.cpp @@ -117,7 +117,7 @@ bool DarkTasbarValueValid/* = false*/; p.setPen(Qt::NoPen); p.drawEllipse(QRectF( // cx=3.9, cy=12.7, r=2.2 1.7 * xm, - 10.5 * ym, + 9.5 * ym, 4.4 * xm, 4.4 * ym)); return image; diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index afb9b9c64b..2850e85a01 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -939,7 +939,8 @@ void FillUniqueGiftMenu( && e.id.isEmpty() && (e.in || (giftChannel && giftChannel->canManageGifts())) && !e.giftTransferred - && !e.giftRefunded; + && !e.giftRefunded + && !e.converted; const auto unique = e.uniqueGift; if (unique @@ -1148,7 +1149,6 @@ void GenericCreditsEntryBox( const auto isStarGift = e.stargift || e.soldOutInfo; const auto creditsHistoryStarGift = isStarGift && !e.id.isEmpty(); const auto sentStarGift = creditsHistoryStarGift && !e.in; - const auto convertedStarGift = creditsHistoryStarGift && e.converted; const auto giftToSelf = isStarGift && (e.barePeerId == selfPeerId) && (e.in || e.bareGiftOwnerId == selfPeerId); @@ -1164,7 +1164,8 @@ void GenericCreditsEntryBox( const auto starGiftCanManage = isStarGift && !creditsHistoryStarGift && (e.in || giftToChannelCanManage) - && !e.fromGiftSlug; + && !e.fromGiftSlug + && !e.converted; const auto starGiftCanTransfer = isStarGift && !creditsHistoryStarGift && (e.in || giftToChannelCanTransfer); @@ -1250,12 +1251,13 @@ void GenericCreditsEntryBox( EntryToSavedStarGiftId(session, e), style); }; + const auto canResell = CanResellGift(session, e); AddUniqueGiftCover( content, rpl::single(*uniqueGift), {}, std::move(price), - CanResellGift(session, e) ? std::move(change) : Fn()); + canResell ? std::move(change) : Fn()); AddSkip(content, st::defaultVerticalListSkip * 2); @@ -1263,6 +1265,10 @@ void GenericCreditsEntryBox( const auto type = SavedStarGiftMenuType::View; FillUniqueGiftMenu(show, menu, e, type, st); }); + + if (canResell) { + Ui::PreloadUniqueGiftResellPrices(session); + } } else if (const auto callback = Ui::PaintPreviewCallback(session, e)) { const auto thumb = content->add(object_ptr>( content, @@ -1419,7 +1425,7 @@ void GenericCreditsEntryBox( ? tr::lng_credits_box_history_entry_gift_unavailable(tr::now) : sentStarGift ? tr::lng_credits_box_history_entry_gift_sent(tr::now) - : convertedStarGift + : e.converted ? tr::lng_credits_box_history_entry_gift_converted(tr::now) : (isStarGift && !starGiftCanManage) ? tr::lng_gift_link_label_gift(tr::now) @@ -1622,7 +1628,7 @@ void GenericCreditsEntryBox( } const auto arrow = Ui::Text::IconEmoji(&st::textMoreIconEmoji); - if (!uniqueGift && starGiftCanManage) { + if (!uniqueGift && (starGiftCanManage || e.converted)) { Ui::AddSkip(content); const auto about = box->addRow( object_ptr>( @@ -1751,7 +1757,8 @@ void GenericCreditsEntryBox( const auto canToggle = starGiftCanManage && !e.giftTransferred - && !e.giftRefunded; + && !e.giftRefunded + && !e.converted; const auto toggleVisibility = [=, weak = Ui::MakeWeak(box)](bool save) { const auto showSection = !e.fromGiftsList; const auto savedId = EntryToSavedStarGiftId(&show->session(), e); diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index ef7252be56..e4a5ae6045 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -843,7 +843,6 @@ void SetupPremium( button->addClickHandler([=] { showOther(BusinessId()); }); - Ui::NewBadge::AddToRight(button); if (controller->session().premiumCanBuy()) { const auto button = AddButtonWithIcon( @@ -852,6 +851,8 @@ void SetupPremium( st::settingsButton, { .icon = &st::menuIconGiftPremium } ); + Ui::NewBadge::AddToRight(button); + button->addClickHandler([=] { Ui::ChooseStarGiftRecipient(controller); }); diff --git a/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp b/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp index 97704994aa..c000d919b9 100644 --- a/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp +++ b/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp @@ -324,9 +324,8 @@ void PointDetailsWidget::setXIndex(int xIndex) { nullptr, { float64(xIndex), float64(xIndex) }).parts : std::vector(); - const auto multiplier = float64(kOneStarInNano); const auto isCredits - = _chartData.currency == Data::StatisticalCurrency::Credits; + = (_chartData.currency == Data::StatisticalCurrency::Credits); for (auto i = 0; i < _chartData.lines.size(); i++) { const auto &dataLine = _chartData.lines[i]; auto textLine = Line(); @@ -350,19 +349,23 @@ void PointDetailsWidget::setXIndex(int xIndex) { ? tr::lng_channel_earn_chart_overriden_detail_credits : tr::lng_channel_earn_chart_overriden_detail_currency)( tr::now)); + const auto provided = dataLine.y[xIndex]; + const auto value = isCredits + ? CreditsAmount(provided, CreditsType::Stars) + : CreditsAmount( + provided / kOneStarInNano, + provided % kOneStarInNano, + CreditsType::Ton); copy.value.setText( _textStyle, - Lang::FormatExactCountDecimal( - dataLine.y[xIndex] / multiplier)); + Lang::FormatCreditsAmountDecimal(value)); _lines.push_back(std::move(copy)); textLine.name.setText( _textStyle, tr::lng_channel_earn_chart_overriden_detail_usd(tr::now)); textLine.value.setText( _textStyle, - Info::ChannelEarn::ToUsd( - dataLine.y[xIndex] / multiplier, - _chartData.currencyRate, 0)); + Info::ChannelEarn::ToUsd(value, _chartData.currencyRate, 0)); } _lines.push_back(std::move(textLine)); } diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index c45b937ab7..c5d072ac11 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -947,6 +947,9 @@ historySponsorInfoItem: FlatLabel(defaultFlatLabel) { minWidth: 136px; maxHeight: 120px; } +historySponsorInfoItemDark: FlatLabel(historySponsorInfoItem) { + textFg: mediaviewControlFg; +} historyHasCustomEmoji: FlatLabel(defaultFlatLabel) { style: TextStyle(defaultTextStyle) { font: font(11px); diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 606cf1d74c..4e75c5cecf 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -156,6 +156,7 @@ struct ChatPaintHighlight { float64 opacity = 0.; float64 collapsion = 0.; TextSelection range; + int todoItemId = 0; }; struct ChatPaintContext { diff --git a/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.cpp b/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.cpp index a057463507..68c64708d7 100644 --- a/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.cpp +++ b/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.cpp @@ -394,7 +394,7 @@ void SubsectionSlider::activate(int index) { } } -void SubsectionSlider::setActiveSectionFast(int active) { +void SubsectionSlider::setActiveSectionFast(int active, bool ignoreScroll) { Expects(active < int(_tabs.size())); if (_active == active) { @@ -403,8 +403,10 @@ void SubsectionSlider::setActiveSectionFast(int active) { _active = active; _activeFrom.stop(); _activeSize.stop(); - const auto now = getFinalActiveRange(); - _requestShown.fire({ now.from, now.from + now.size }); + if (_active >= 0 && !ignoreScroll) { + const auto now = getFinalActiveRange(); + _requestShown.fire({ now.from, now.from + now.size }); + } _bar->update(); } @@ -425,6 +427,7 @@ rpl::producer SubsectionSlider::sectionContextMenu() const { } int SubsectionSlider::lookupSectionPosition(int index) const { + Expects(!_tabs.empty()); Expects(index >= 0 && index < _tabs.size()); return _vertical ? _tabs[index]->y() : _tabs[index]->x(); diff --git a/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.h b/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.h index 2c82684ef9..704169b92f 100644 --- a/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.h +++ b/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.h @@ -81,7 +81,7 @@ public: void setSections( SubsectionTabs sections, Fn paused); - void setActiveSectionFast(int active); + void setActiveSectionFast(int active, bool ignoreScroll = false); [[nodiscard]] int sectionsCount() const; [[nodiscard]] rpl::producer sectionActivated() const; diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index fcd6d2bbb8..3c37626681 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -185,6 +185,7 @@ menuIconPayment: icon {{ "payments/payment_card", menuIconColor }}; menuIconOrderPrice: icon {{ "menu/order_price", menuIconColor }}; menuIconOrderDate: icon {{ "menu/order_date", menuIconColor }}; menuIconOrderNumber: icon {{ "menu/order_number", menuIconColor }}; +menuIconAdd: icon{{ "menu/add", menuIconColor }}; menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }}; menuIconTTLAnyTextPosition: point(11px, 22px); @@ -204,6 +205,7 @@ menuBlueIconGroupCreate: icon {{ "menu/groups_create", lightButtonFg }}; mediaMenuIconStickers: icon {{ "menu/stickers", mediaviewMenuFg }}; mediaMenuIconCancel: icon {{ "menu/cancel", mediaviewMenuFg }}; +mediaMenuIconChannel: icon {{ "menu/channel", mediaviewMenuFg }}; mediaMenuIconShowInChat: icon {{ "menu/show_in_chat", mediaviewMenuFg }}; mediaMenuIconShowInFolder: icon {{ "menu/show_in_folder", mediaviewMenuFg }}; mediaMenuIconDownload: icon {{ "menu/download", mediaviewMenuFg }}; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index fa9565e9d6..b2705ad026 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -3835,7 +3835,7 @@ void PeerMenuConfirmToggleFee( MTP_flags((refund ? Flag::f_refund_charged : Flag()) | (removeFee ? Flag() : Flag::f_require_payment) | (parent ? Flag::f_parent_peer : Flag())), - parent->input, + (parent ? parent->input : MTPInputPeer()), user->inputUser )).done([=] { if (!parent) { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index c849a249f9..80ddb4607d 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer_values.h" #include "data/data_premium_limits.h" #include "data/data_web_page.h" +#include "dialogs/ui/chat_search_in.h" #include "passport/passport_form_controller.h" #include "chat_helpers/tabbed_selector.h" #include "chat_helpers/emoji_interactions.h" @@ -361,6 +362,14 @@ void DateClickHandler::onClick(ClickContext context) const { } } +MessageHighlightId SearchHighlightId(const QString &query) { + auto result = MessageHighlightId{ .quote = { query } }; + if (!result.quote.empty()) { + result.quoteOffset = kSearchQueryOffsetHint; + } + return result; +} + SessionNavigation::SessionNavigation(not_null session) : _session(session) , _api(&_session->mtp()) { @@ -1149,8 +1158,7 @@ void SessionNavigation::showRepliesForMessage( .repliesRootId = rootId, }, commentId, - params.highlightPart, - params.highlightPartOffsetHint); + params.highlight); memento->setFromTopic(topic); showSection(std::move(memento), params); return; @@ -1272,8 +1280,7 @@ void SessionNavigation::showSublist( .sublist = sublist, }, itemId, - params.highlightPart, - params.highlightPartOffsetHint); + params.highlight); showSection(std::move(memento), params); } @@ -1811,10 +1818,13 @@ void SessionController::activateFirstChatsFilter() { } } -bool SessionController::uniqueChatsInSearchResults() const { +bool SessionController::uniqueChatsInSearchResults( + const Dialogs::SearchState &state) const { + const auto global = (state.tab == Dialogs::ChatSearchTab::MyMessages) + || (state.tab == Dialogs::ChatSearchTab::PublicPosts); return session().supportMode() && !session().settings().supportAllSearchResults() - && !_searchInChat.current(); + && (global || !state.inChat); } bool SessionController::openFolderInDifferentWindow( diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 9af6714b60..7bde45c28d 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -30,6 +30,10 @@ class SavedMessages; enum class StorySourcesList : uchar; } // namespace Data +namespace Dialogs { +struct SearchState; +} // namespace Dialogs + namespace ChatHelpers { class TabbedSelector; class EmojiInteractions; @@ -162,8 +166,9 @@ struct SectionShow { return copy; } - TextWithEntities highlightPart; + MessageHighlightId highlight; int highlightPartOffsetHint = 0; + int highlightTodoItemId = 0; std::optional videoTimestamp; Way way = Way::Forward; anim::type animated = anim::type::normal; @@ -178,6 +183,8 @@ struct SectionShow { }; +[[nodiscard]] MessageHighlightId SearchHighlightId(const QString &query); + class SessionController; class SessionNavigation : public base::has_weak_ptr { @@ -404,7 +411,7 @@ public: void setSearchInChat(Dialogs::Key value) { _searchInChat = value; } - bool uniqueChatsInSearchResults() const; + bool uniqueChatsInSearchResults(const Dialogs::SearchState &state) const; void openFolder(not_null folder); void closeFolder(); diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 1348de6aa6..d78b0507c5 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 1348de6aa6c07ed32354d3e26423c45304000a39 +Subproject commit d78b0507c54d76d5fe9691c8efe2638dee2c1536 diff --git a/Telegram/build/version b/Telegram/build/version index 4d8e1cfc6a..ea436f7a45 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5016003 +AppVersion 5016004 AppVersionStrMajor 5.16 -AppVersionStrSmall 5.16.3 -AppVersionStr 5.16.3 +AppVersionStrSmall 5.16.4 +AppVersionStr 5.16.4 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.16.3 +AppVersionOriginal 5.16.4 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 7dae77ff08..eed6fe4254 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 7dae77ff08c60c30eb4c23abad2135760d9f07fb +Subproject commit eed6fe4254c3670e48b680a7e0b61f642d3a40e8 diff --git a/changelog.txt b/changelog.txt index 3474436d1e..ac227269eb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +5.16.4 (11.07.25) + +- Fix problem with negative unread counters. +- Fix stars values display in statistics. +- Fix crash in messages fee disabling. + 5.16.3 (08.07.25) - Allow removing / charging fee in channel direct messages. diff --git a/cmake b/cmake index b032f270b6..f3d6471bd5 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit b032f270b622610ca3f42a83f37b3a183c9da0da +Subproject commit f3d6471bd58dbad727d4f8fbccd0fb36632eee9e