/* 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 "api/api_common.h" #include "base/required.h" #include "base/unique_qptr.h" #include "base/timer.h" #include "chat_helpers/compose/compose_features.h" #include "dialogs/dialogs_key.h" #include "history/view/controls/compose_controls_common.h" #include "ui/round_rect.h" #include "ui/rp_widget.h" #include "ui/effects/animations.h" #include "ui/widgets/fields/input_field.h" class History; class DocumentData; class FieldAutocomplete; namespace style { struct ComposeControls; } // namespace style namespace SendMenu { enum class Type; } // namespace SendMenu namespace ChatHelpers { class TabbedPanel; class TabbedSelector; struct FileChosen; struct PhotoChosen; class Show; } // namespace ChatHelpers namespace Data { struct MessagePosition; struct Draft; class DraftKey; class PhotoMedia; struct WebPageDraft; } // namespace Data namespace InlineBots { namespace Layout { class ItemBase; class Widget; } // namespace Layout class Result; struct ResultSelected; } // namespace InlineBots namespace Ui { class SendButton; class IconButton; class EmojiButton; class SendAsButton; class SilentToggle; class DropdownMenu; struct PreparedList; } // namespace Ui namespace Main { class Session; } // namespace Main namespace Window { struct SectionShow; class SessionController; } // namespace Window namespace Api { enum class SendProgressType; } // namespace Api namespace HistoryView::Controls { class VoiceRecordBar; class TTLButton; class WebpageProcessor; } // namespace HistoryView::Controls namespace HistoryView { class FieldHeader; enum class ComposeControlsMode { Normal, Scheduled, }; struct ComposeControlsDescriptor { const style::ComposeControls *stOverride = nullptr; std::shared_ptr show; Fn)> unavailableEmojiPasted; ComposeControlsMode mode = ComposeControlsMode::Normal; SendMenu::Type sendMenuType = {}; Window::SessionController *regularWindow = nullptr; rpl::producer stickerOrEmojiChosen; rpl::producer customPlaceholder; QString voiceCustomCancelText; bool voiceLockFromBottom = false; ChatHelpers::ComposeFeatures features; }; class ComposeControls final { public: using FileChosen = ChatHelpers::FileChosen; using PhotoChosen = ChatHelpers::PhotoChosen; using InlineChosen = InlineBots::ResultSelected; using MessageToEdit = Controls::MessageToEdit; using VoiceToSend = Controls::VoiceToSend; using SendActionUpdate = Controls::SendActionUpdate; using SetHistoryArgs = Controls::SetHistoryArgs; using ReplyNextRequest = Controls::ReplyNextRequest; using FieldHistoryAction = Ui::InputField::HistoryAction; using Mode = ComposeControlsMode; ComposeControls( not_null parent, not_null controller, Fn)> unavailableEmojiPasted, Mode mode, SendMenu::Type sendMenuType); ComposeControls( not_null parent, ComposeControlsDescriptor descriptor); ~ComposeControls(); [[nodiscard]] Main::Session &session() const; void setHistory(SetHistoryArgs &&args); void updateTopicRootId(MsgId topicRootId); void setCurrentDialogsEntryState(Dialogs::EntryState state); [[nodiscard]] PeerData *sendAsPeer() const; void finishAnimating(); void move(int x, int y); void resizeToWidth(int width); void setAutocompleteBoundingRect(QRect rect); [[nodiscard]] rpl::producer height() const; [[nodiscard]] int heightCurrent() const; bool focus(); [[nodiscard]] bool focused() const; [[nodiscard]] rpl::producer focusedValue() const; [[nodiscard]] rpl::producer tabbedPanelShownValue() const; [[nodiscard]] rpl::producer<> cancelRequests() const; [[nodiscard]] rpl::producer sendRequests() const; [[nodiscard]] rpl::producer sendVoiceRequests() const; [[nodiscard]] rpl::producer sendCommandRequests() const; [[nodiscard]] rpl::producer editRequests() const; [[nodiscard]] rpl::producer> attachRequests() const; [[nodiscard]] rpl::producer fileChosen() const; [[nodiscard]] rpl::producer photoChosen() const; [[nodiscard]] rpl::producer jumpToItemRequests() const; [[nodiscard]] rpl::producer inlineResultChosen() const; [[nodiscard]] rpl::producer sendActionUpdates() const; [[nodiscard]] rpl::producer> viewportEvents() const; [[nodiscard]] rpl::producer<> likeToggled() const; [[nodiscard]] auto scrollKeyEvents() const -> rpl::producer>; [[nodiscard]] auto editLastMessageRequests() const -> rpl::producer>; [[nodiscard]] auto replyNextRequests() const -> rpl::producer; [[nodiscard]] rpl::producer<> focusRequests() const; using MimeDataHook = Fn data, Ui::InputField::MimeAction action)>; void setMimeDataHook(MimeDataHook hook); bool confirmMediaEdit(Ui::PreparedList &list); bool pushTabbedSelectorToThirdSection( not_null thread, const Window::SectionShow ¶ms); bool returnTabbedSelector(); [[nodiscard]] bool isEditingMessage() const; [[nodiscard]] bool readyToForward() const; [[nodiscard]] const HistoryItemsList &forwardItems() const; [[nodiscard]] FullReplyTo replyingToMessage() const; [[nodiscard]] bool preventsClose(Fn &&continueCallback) const; void showForGrab(); void showStarted(); void showFinished(); void raisePanels(); void editMessage(FullMsgId id); void cancelEditMessage(); void maybeCancelEditMessage(); // Confirm if changed and cancel. void replyToMessage(FullReplyTo id); void cancelReplyMessage(); void updateForwarding(); void cancelForward(); bool handleCancelRequest(); void tryProcessKeyInput(not_null e); [[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const; [[nodiscard]] Data::WebPageDraft webPageDraft() const; void setText(const TextWithTags &text); void clear(); void hidePanelsAnimated(); void clearListenState(); void hide(); void show(); [[nodiscard]] rpl::producer lockShowStarts() const; [[nodiscard]] bool isLockPresent() const; [[nodiscard]] bool isRecording() const; [[nodiscard]] bool isRecordingPressed() const; [[nodiscard]] rpl::producer recordingActiveValue() const; [[nodiscard]] rpl::producer hasSendTextValue() const; [[nodiscard]] rpl::producer fieldMenuShownValue() const; [[nodiscard]] not_null likeAnimationTarget() const; void applyCloudDraft(); void applyDraft( FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); Fn restoreTextCallback(const QString &insertTextOnCancel) const; private: enum class TextUpdateEvent { SaveDraft = (1 << 0), SendTyping = (1 << 1), }; enum class DraftType { Normal, Edit, }; enum class SendRequestType { Text, Voice, }; using TextUpdateEvents = base::flags; friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; }; void init(); void initField(); void initTabbedSelector(); void initSendButton(); void initSendAsButton(not_null peer); void initWebpageProcess(); void initForwardProcess(); void initWriteRestriction(); void initVoiceRecordBar(); void initAutocomplete(); void initKeyHandler(); void updateSubmitSettings(); void updateSendButtonType(); void updateMessagesTTLShown(); bool updateSendAsButton(); void updateAttachBotsMenu(); void updateHeight(); void updateWrappingVisibility(); void updateControlsVisibility(); void updateControlsGeometry(QSize size); bool updateReplaceMediaButton(); void updateOuterGeometry(QRect rect); void paintBackground(QRect clip); [[nodiscard]] auto computeSendButtonType() const; [[nodiscard]] SendMenu::Type sendMenuType() const; [[nodiscard]] SendMenu::Type sendButtonMenuType() const; [[nodiscard]] auto sendContentRequests( SendRequestType requestType = SendRequestType::Text) const; void orderControls(); void checkAutocomplete(); bool updateStickersByEmoji(); void updateFieldPlaceholder(); void updateSilentBroadcast(); void editMessage(not_null item); void escape(); void fieldChanged(); void fieldTabbed(); void toggleTabbedSelectorMode(); void createTabbedPanel(); void setTabbedPanel(std::unique_ptr panel); bool showRecordButton() const; void drawRestrictedWrite(QPainter &p, const QString &error); bool updateBotCommandShown(); bool updateLikeShown(); void cancelInlineBot(); void clearInlineBot(); void inlineBotChanged(); bool hasSilentBroadcastToggle() const; // Look in the _field for the inline bot and query string. void updateInlineBotQuery(); // Request to show results in the emoji panel. void applyInlineBotQuery(UserData *bot, const QString &query); [[nodiscard]] Data::DraftKey draftKey( DraftType type = DraftType::Normal) const; [[nodiscard]] Data::DraftKey draftKeyCurrent() const; void saveDraft(bool delayed = false); void saveDraftDelayed(); void saveCloudDraft(); void writeDrafts(); void writeDraftTexts(); void writeDraftCursors(); void setFieldText( const TextWithTags &textWithTags, TextUpdateEvents events = 0, FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); void clearFieldText( TextUpdateEvents events = 0, FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); void saveFieldToHistoryLocalDraft(); void unregisterDraftSources(); void registerDraftSource(); void changeFocusedControl(); const style::ComposeControls &_st; const ChatHelpers::ComposeFeatures _features; const not_null _parent; const std::shared_ptr _show; const not_null _session; Window::SessionController * const _regularWindow = nullptr; std::unique_ptr _ownedSelector; const not_null _selector; rpl::event_stream _stickerOrEmojiChosen; History *_history = nullptr; MsgId _topicRootId = 0; Fn _showSlowmodeError; Fn _sendActionFactory; rpl::variable _slowmodeSecondsLeft; rpl::variable _sendDisabledBySlowmode; rpl::variable _liked; rpl::variable> _writeRestriction; rpl::variable _hidden; Mode _mode = Mode::Normal; const std::unique_ptr _wrap; const std::unique_ptr _writeRestricted; rpl::event_stream _jumpToItemRequests; std::optional _backgroundRect; const std::shared_ptr _send; Ui::IconButton * const _like = nullptr; const not_null _attachToggle; std::unique_ptr _replaceMedia; const not_null _tabbedSelectorToggle; rpl::producer _fieldCustomPlaceholder; const not_null _field; Ui::IconButton * const _botCommandStart = nullptr; std::unique_ptr _sendAs; std::unique_ptr _silent; std::unique_ptr _ttlInfo; std::unique_ptr _inlineResults; std::unique_ptr _tabbedPanel; std::unique_ptr _attachBotsMenu; std::unique_ptr _autocomplete; friend class FieldHeader; const std::unique_ptr _header; const std::unique_ptr _voiceRecordBar; const SendMenu::Type _sendMenuType; const Fn)> _unavailableEmojiPasted; rpl::event_stream _sendCustomRequests; rpl::event_stream<> _cancelRequests; rpl::event_stream _fileChosen; rpl::event_stream _photoChosen; rpl::event_stream _inlineResultChosen; rpl::event_stream _sendActionUpdates; rpl::event_stream _sendCommandRequests; rpl::event_stream> _scrollKeyEvents; rpl::event_stream> _editLastMessageRequests; rpl::event_stream> _attachRequests; rpl::event_stream<> _likeToggled; rpl::event_stream _replyNextRequests; rpl::event_stream<> _focusRequests; rpl::variable _recording; rpl::variable _hasSendText; TextUpdateEvents _textUpdateEvents = TextUpdateEvents() | TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; Dialogs::EntryState _currentDialogsEntryState; crl::time _saveDraftStart = 0; bool _saveDraftText = false; base::Timer _saveDraftTimer; base::Timer _saveCloudDraftTimer; UserData *_inlineBot = nullptr; QString _inlineBotUsername; bool _inlineLookingUpBot = false; mtpRequestId _inlineBotResolveRequestId = 0; bool _isInlineBot = false; bool _botCommandShown = false; bool _likeShown = false; FullMsgId _editingId; std::shared_ptr _photoEditMedia; bool _canReplaceMedia = false; std::unique_ptr _preview; Fn _raiseEmojiSuggestions; rpl::lifetime _historyLifetime; rpl::lifetime _uploaderSubscriptions; }; } // namespace HistoryView