tdesktop/Telegram/SourceFiles/historywidget.h
John Preston cbde6e1ee9 Using square rounded photos in native notifications (Win, OS X).
When updating local online status from user action events we use
action timestamp, and ignoring events from getDifference, if no
timestamp is available for the specific action.
Couple of hidden actions added in Settings. Alpha version 0.9.54.
2016-06-22 21:47:43 +03:00

1160 lines
34 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "localimageloader.h"
#include "ui/boxshadow.h"
#include "dropdown.h"
#include "history/history_common.h"
#include "history/field_autocomplete.h"
#include "window/section_widget.h"
namespace InlineBots {
namespace Layout {
class ItemBase;
} // namespace Layout
class Result;
} // namespace InlineBots
namespace Ui {
class HistoryDownButton;
class InnerDropdown;
} // namespace Ui
class HistoryWidget;
class HistoryInner : public TWidget, public AbstractTooltipShower {
Q_OBJECT
public:
HistoryInner(HistoryWidget *historyWidget, ScrollArea *scroll, History *history);
void messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages);
void messagesReceivedDown(PeerData *peer, const QVector<MTPMessage> &messages);
bool event(QEvent *e) override; // calls touchEvent when necessary
void touchEvent(QTouchEvent *e);
void paintEvent(QPaintEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseDoubleClickEvent(QMouseEvent *e) override;
void enterEvent(QEvent *e) override;
void leaveEvent(QEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
TextWithEntities getSelectedText() const;
void dragActionStart(const QPoint &screenPos, Qt::MouseButton button = Qt::LeftButton);
void dragActionUpdate(const QPoint &screenPos);
void dragActionFinish(const QPoint &screenPos, Qt::MouseButton button = Qt::LeftButton);
void dragActionCancel();
void touchScrollUpdated(const QPoint &screenPos);
QPoint mapMouseToItem(QPoint p, HistoryItem *item);
void recountHeight();
void updateSize();
void repaintItem(const HistoryItem *item);
bool canCopySelected() const;
bool canDeleteSelected() const;
void getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const;
void clearSelectedItems(bool onlyTextSelection = false);
void fillSelectedItems(SelectedItemSet &sel, bool forDelete = true);
void selectItem(HistoryItem *item);
void itemRemoved(HistoryItem *item);
void updateBotInfo(bool recount = true);
bool wasSelectedText() const;
void setFirstLoading(bool loading);
// updates history->scrollTopItem/scrollTopOffset
void visibleAreaUpdated(int top, int bottom);
int historyHeight() const;
int historyScrollTop() const;
int migratedTop() const;
int historyTop() const;
int historyDrawTop() const;
int itemTop(const HistoryItem *item) const; // -1 if should not be visible, -2 if bad history()
void notifyIsBotChanged();
void notifyMigrateUpdated();
// When inline keyboard has moved because of the edition of its item we want
// to move scroll position so that mouse points to the same button row.
int moveScrollFollowingInlineKeyboard(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop);
// AbstractTooltipShower interface
QString tooltipText() const override;
QPoint tooltipPos() const override;
~HistoryInner();
protected:
bool focusNextPrevChild(bool next) override;
public slots:
void onUpdateSelected();
void onParentGeometryChanged();
void copyContextUrl();
void saveContextImage();
void copyContextImage();
void cancelContextDownload();
void showContextInFolder();
void saveContextFile();
void saveContextGif();
void copyContextText();
void copySelectedText();
void onMenuDestroy(QObject *obj);
void onTouchSelect();
void onTouchScrollTimer();
void onDragExec();
private slots:
void onScrollDateCheck();
void onScrollDateHide();
private:
void touchResetSpeed();
void touchUpdateSpeed();
void touchDeaccelerate(int32 elapsed);
void adjustCurrent(int32 y) const;
void adjustCurrent(int32 y, History *history) const;
HistoryItem *prevItem(HistoryItem *item);
HistoryItem *nextItem(HistoryItem *item);
void updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting, bool force = false);
void setToClipboard(const TextWithEntities &forClipboard, QClipboard::Mode mode = QClipboard::Clipboard);
void toggleScrollDateShown();
void repaintScrollDateCallback();
bool displayScrollDate() const;
PeerData *_peer = nullptr;
History *_migrated = nullptr;
History *_history = nullptr;
int _historyOffset = 0;
// with migrated history we perhaps do not need to display first _history message
// (if last _migrated message and first _history message are both isGroupMigrate)
// or at least we don't need to display first _history date (just skip it by height)
int _historySkipHeight = 0;
class BotAbout : public ClickHandlerHost {
public:
BotAbout(HistoryInner *parent, BotInfo *info) : info(info), _parent(parent) {
}
BotInfo *info = nullptr;
int width = 0;
int height = 0;
QRect rect;
// ClickHandlerHost interface
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
private:
HistoryInner *_parent;
};
std_::unique_ptr<BotAbout> _botAbout;
HistoryWidget *_widget = nullptr;
ScrollArea *_scroll = nullptr;
mutable History *_curHistory = nullptr;
mutable int _curBlock = 0;
mutable int _curItem = 0;
bool _firstLoading = false;
style::cursor _cursor = style::cur_default;
using SelectedItems = QMap<HistoryItem*, TextSelection>;
SelectedItems _selected;
void applyDragSelection();
void applyDragSelection(SelectedItems *toItems) const;
void addSelectionRange(SelectedItems *toItems, int32 fromblock, int32 fromitem, int32 toblock, int32 toitem, History *h) const;
// Does any of the shown histories has this flag set.
bool hasPendingResizedItems() const {
return (_history && _history->hasPendingResizedItems()) || (_migrated && _migrated->hasPendingResizedItems());
}
enum DragAction {
NoDrag = 0x00,
PrepareDrag = 0x01,
Dragging = 0x02,
PrepareSelect = 0x03,
Selecting = 0x04,
};
DragAction _dragAction = NoDrag;
TextSelectType _dragSelType = TextSelectType::Letters;
QPoint _dragStartPos, _dragPos;
HistoryItem *_dragItem = nullptr;
HistoryCursorState _dragCursorState = HistoryDefaultCursorState;
uint16 _dragSymbol = 0;
bool _dragWasInactive = false;
QPoint _trippleClickPoint;
QTimer _trippleClickTimer;
ClickHandlerPtr _contextMenuLnk;
HistoryItem *_dragSelFrom = nullptr;
HistoryItem *_dragSelTo = nullptr;
bool _dragSelecting = false;
bool _wasSelectedText = false; // was some text selected in current drag action
// scroll by touch support (at least Windows Surface tablets)
bool _touchScroll = false;
bool _touchSelect = false;
bool _touchInProgress = false;
QPoint _touchStart, _touchPrevPos, _touchPos;
QTimer _touchSelectTimer;
TouchScrollState _touchScrollState = TouchScrollManual;
bool _touchPrevPosValid = false;
bool _touchWaitingAcceleration = false;
QPoint _touchSpeed;
uint64 _touchSpeedTime = 0;
uint64 _touchAccelerationTime = 0;
uint64 _touchTime = 0;
QTimer _touchScrollTimer;
// context menu
PopupMenu *_menu = nullptr;
// save visible area coords for painting / pressing userpics
int _visibleAreaTop = 0;
int _visibleAreaBottom = 0;
bool _scrollDateShown = false;
FloatAnimation _scrollDateOpacity;
SingleDelayedCall _scrollDateCheck = { this, "onScrollDateCheck" };
SingleTimer _scrollDateHideTimer;
HistoryItem *_scrollDateLastItem = nullptr;
int _scrollDateLastItemTop = 0;
// this function finds all history items that are displayed and calls template method
// for each found message (from the bottom to the top) in the passed history with passed top offset
//
// method has "bool (*Method)(HistoryItem *item, int itemtop, int itembottom)" signature
// if it returns false the enumeration stops immidiately
template <typename Method>
void enumerateItemsInHistory(History *history, int historytop, Method method);
template <typename Method>
void enumerateItems(Method method) {
enumerateItemsInHistory(_history, historyTop(), method);
if (_migrated) {
enumerateItemsInHistory(_migrated, migratedTop(), method);
}
}
// this function finds all userpics on the left that are displayed and calls template method
// for each found userpic (from the bottom to the top) using enumerateItems() method
//
// method has "bool (*Method)(HistoryMessage *message, int userpicTop)" signature
// if it returns false the enumeration stops immidiately
template <typename Method>
void enumerateUserpics(Method method);
// this function finds all date elements that are displayed and calls template method
// for each found date element (from the bottom to the top) using enumerateItems() method
//
// method has "bool (*Method)(HistoryItem *item, int itemtop, int dateTop)" signature
// if it returns false the enumeration stops immidiately
template <typename Method>
void enumerateDates(Method method);
};
class MessageField : public FlatTextarea {
Q_OBJECT
public:
MessageField(HistoryWidget *history, const style::flatTextarea &st, const QString &ph = QString(), const QString &val = QString());
void dropEvent(QDropEvent *e);
bool canInsertFromMimeData(const QMimeData *source) const;
void insertFromMimeData(const QMimeData *source);
void focusInEvent(QFocusEvent *e);
bool hasSendText() const;
public slots:
void onEmojiInsert(EmojiPtr emoji);
signals:
void focused();
private:
HistoryWidget *history;
};
class HistoryWidget;
class ReportSpamPanel : public TWidget {
Q_OBJECT
public:
ReportSpamPanel(HistoryWidget *parent);
void resizeEvent(QResizeEvent *e);
void paintEvent(QPaintEvent *e);
void setReported(bool reported, PeerData *onPeer);
signals:
void hideClicked();
void reportClicked();
void clearClicked();
private:
FlatButton _report, _hide;
LinkButton _clear;
};
class BotKeyboard : public TWidget, public AbstractTooltipShower, public ClickHandlerHost {
Q_OBJECT
public:
BotKeyboard();
void paintEvent(QPaintEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void enterEvent(QEvent *e) override;
void leaveEvent(QEvent *e) override;
bool moderateKeyActivate(int index);
// With force=true the markup is updated even if it is
// already shown for the passed history item.
bool updateMarkup(HistoryItem *last, bool force = false);
bool hasMarkup() const;
bool forceReply() const;
void step_selected(uint64 ms, bool timer);
void resizeToWidth(int newWidth, int maxOuterHeight);
bool maximizeSize() const;
bool singleUse() const;
FullMsgId forMsgId() const {
return _wasForMsgId;
}
// AbstractTooltipShower interface
QString tooltipText() const override;
QPoint tooltipPos() const override;
// ClickHandlerHost interface
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
private:
void updateSelected();
void updateStyle(int newWidth);
void clearSelection();
FullMsgId _wasForMsgId;
int _height = 0;
int _maxOuterHeight = 0;
bool _maximizeSize = false;
bool _singleUse = false;
bool _forceReply = false;
QPoint _lastMousePos;
std_::unique_ptr<ReplyKeyboard> _impl;
class Style : public ReplyKeyboard::Style {
public:
Style(BotKeyboard *parent, const style::botKeyboardButton &st) : ReplyKeyboard::Style(st), _parent(parent) {
}
void startPaint(Painter &p) const override;
style::font textFont() const override;
void repaint(const HistoryItem *item) const override;
protected:
void paintButtonBg(Painter &p, const QRect &rect, bool down, float64 howMuchOver) const override;
void paintButtonIcon(Painter &p, const QRect &rect, HistoryMessageReplyMarkup::Button::Type type) const override;
void paintButtonLoading(Painter &p, const QRect &rect) const override;
int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const override;
private:
BotKeyboard *_parent;
};
const style::botKeyboardButton *_st = &st::botKbButton;
};
class HistoryHider : public TWidget {
Q_OBJECT
public:
HistoryHider(MainWidget *parent, bool forwardSelected); // forward messages
HistoryHider(MainWidget *parent, UserData *sharedContact); // share contact
HistoryHider(MainWidget *parent); // send path from command line argument
HistoryHider(MainWidget *parent, const QString &url, const QString &text); // share url
HistoryHider(MainWidget *parent, const QString &botAndQuery); // inline switch button handler
void step_appearance(float64 ms, bool timer);
bool withConfirm() const;
void paintEvent(QPaintEvent *e);
void keyPressEvent(QKeyEvent *e);
void mousePressEvent(QMouseEvent *e);
void resizeEvent(QResizeEvent *e);
bool offerPeer(PeerId peer);
QString offeredText() const;
QString botAndQuery() const {
return _botAndQuery;
}
bool wasOffered() const;
void forwardDone();
~HistoryHider();
public slots:
void startHide();
void forward();
signals:
void forwarded();
private:
void init();
MainWidget *parent();
UserData *_sharedContact;
bool _forwardSelected, _sendPath;
QString _shareUrl, _shareText;
QString _botAndQuery;
BoxButton _send, _cancel;
PeerData *offered;
anim::fvalue a_opacity;
Animation _a_appearance;
QRect box;
bool hiding;
mtpRequestId _forwardRequest;
int32 _chooseWidth;
Text toText;
int32 toTextWidth;
QPixmap cacheForAnim;
BoxShadow shadow;
};
class SilentToggle : public FlatCheckbox, public AbstractTooltipShower {
public:
SilentToggle(QWidget *parent);
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void leaveEvent(QEvent *e) override;
// AbstractTooltipShower interface
QString tooltipText() const override;
QPoint tooltipPos() const override;
};
EntitiesInText entitiesFromTextTags(const TextWithTags::Tags &tags);
TextWithTags::Tags textTagsFromEntities(const EntitiesInText &entities);
class HistoryWidget : public TWidget, public RPCSender {
Q_OBJECT
public:
HistoryWidget(QWidget *parent);
void start();
void messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, mtpRequestId requestId);
void historyLoaded();
void windowShown();
bool doWeReadServerHistory() const;
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void dragEnterEvent(QDragEnterEvent *e) override;
void dragLeaveEvent(QDragLeaveEvent *e) override;
void leaveEvent(QEvent *e) override;
void dropEvent(QDropEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void leaveToChildEvent(QEvent *e, QWidget *child) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void updateTopBarSelection();
void paintTopBar(Painter &p, float64 over, int32 decreaseWidth);
QRect getMembersShowAreaGeometry() const;
void setMembersShowAreaActive(bool active);
void topBarClick();
void loadMessages();
void loadMessagesDown();
void firstLoadMessages();
void delayedShowAt(MsgId showAtMsgId);
void peerMessagesUpdated(PeerId peer);
void peerMessagesUpdated();
void newUnreadMsg(History *history, HistoryItem *item);
void historyToDown(History *history);
void historyWasRead(ReadServerHistoryChecks checks);
void historyCleared(History *history);
void unreadCountChanged(History *history);
QRect historyRect() const;
void updateSendAction(History *history, SendActionType type, int32 progress = 0);
void cancelSendAction(History *history, SendActionType type);
void updateRecentStickers();
void stickersInstalled(uint64 setId);
void sendActionDone(const MTPBool &result, mtpRequestId req);
void destroyData();
void updateFieldPlaceholder();
void updateStickersByEmoji();
void uploadImage(const QImage &img, PrepareMediaType type, FileLoadForceConfirmType confirm = FileLoadNoForceConfirm, const QString &source = QString(), bool withText = false);
void uploadFile(const QString &file, PrepareMediaType type, FileLoadForceConfirmType confirm = FileLoadNoForceConfirm, bool withText = false); // with confirmation
void uploadFiles(const QStringList &files, PrepareMediaType type);
void uploadFileContent(const QByteArray &fileContent, PrepareMediaType type);
void shareContactWithConfirm(const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, bool withText = false);
void confirmSendFile(const FileLoadResultPtr &file, bool ctrlShiftEnter);
void cancelSendFile(const FileLoadResultPtr &file);
void confirmShareContact(const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, bool ctrlShiftEnter);
void cancelShareContact();
void updateControlsVisibility();
void updateOnlineDisplay(int32 x, int32 w);
void updateOnlineDisplayTimer();
void onShareContact(const PeerId &peer, UserData *contact);
void onSendPaths(const PeerId &peer);
void shareContact(const PeerId &peer, const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, int32 userId = 0);
History *history() const;
PeerData *peer() const;
void setMsgId(MsgId showAtMsgId);
MsgId msgId() const;
bool hasTopBarShadow() const {
return peer() != nullptr;
}
void showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams &params);
void step_show(float64 ms, bool timer);
void animStop();
void updateAdaptiveLayout();
void doneShow();
QPoint clampMousePosition(QPoint point);
void checkSelectingScroll(QPoint point);
void noSelectingScroll();
bool touchScroll(const QPoint &delta);
uint64 animActiveTimeStart(const HistoryItem *msg) const;
void stopAnimActive();
void fillSelectedItems(SelectedItemSet &sel, bool forDelete = true);
void itemRemoved(HistoryItem *item);
void itemEdited(HistoryItem *item);
void updateScrollColors();
MsgId replyToId() const;
void messageDataReceived(ChannelData *channel, MsgId msgId);
bool lastForceReplyReplied(const FullMsgId &replyTo = FullMsgId(NoChannel, -1)) const;
bool cancelReply(bool lastKeyboardUsed = false);
void cancelEdit();
void updateForwarding(bool force = false);
void cancelForwarding(); // called by MainWidget
void clearReplyReturns();
void pushReplyReturn(HistoryItem *item);
QList<MsgId> replyReturns();
void setReplyReturns(PeerId peer, const QList<MsgId> &replyReturns);
void calcNextReplyReturn();
void updatePreview();
void previewCancel();
void step_record(float64 ms, bool timer);
void step_recording(float64 ms, bool timer);
void stopRecording(bool send);
void onListEscapePressed();
void onListEnterPressed();
void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo);
bool insertBotCommand(const QString &cmd, bool specialGif);
bool eventFilter(QObject *obj, QEvent *e) override;
// With force=true the markup is updated even if it is
// already shown for the passed history item.
void updateBotKeyboard(History *h = nullptr, bool force = false);
DragState getDragState(const QMimeData *d);
void fastShowAtEnd(History *h);
void applyDraft(bool parseLinks = true);
void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false);
void clearDelayedShowAt();
void clearAllLoadRequests();
void saveFieldToHistoryLocalDraft();
void applyCloudDraft(History *history);
void contactsReceived();
void updateToEndVisibility();
void updateAfterDrag();
void updateFieldSubmitSettings();
void setInnerFocus();
bool canSendMessages(PeerData *peer) const;
void updateNotifySettings();
void saveGif(DocumentData *doc);
bool contentOverlapped(const QRect &globalRect);
void grabStart() override {
_inGrab = true;
resizeEvent(0);
}
void grapWithoutTopBarShadow() {
grabStart();
_topShadow.hide();
}
void grabFinish() override {
_inGrab = false;
resizeEvent(0);
_topShadow.show();
}
bool isItemVisible(HistoryItem *item);
void app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col);
void ui_repaintHistoryItem(const HistoryItem *item);
void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *gif);
bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout);
bool ui_isInlineItemBeingChosen();
PeerData *ui_getPeerForMouseAction();
void notify_historyItemLayoutChanged(const HistoryItem *item);
void notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout);
void notify_botCommandsChanged(UserData *user);
void notify_inlineBotRequesting(bool requesting);
void notify_replyMarkupUpdated(const HistoryItem *item);
void notify_inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop);
bool notify_switchInlineBotButtonReceived(const QString &query);
void notify_userIsBotChanged(UserData *user);
void notify_migrateUpdated(PeerData *peer);
void notify_clipStopperHidden(ClipStopperType type);
void notify_handlePendingHistoryUpdate();
void cmd_search();
void cmd_next_chat();
void cmd_previous_chat();
~HistoryWidget();
signals:
void cancelled();
void historyShown(History *history, MsgId atMsgId);
public slots:
void onCancel();
void onReplyToMessage();
void onEditMessage();
void onPinMessage();
void onUnpinMessage();
void onUnpinMessageSure();
void onPinnedHide();
void onCopyPostLink();
void onFieldBarCancel();
void onCancelSendAction();
void onStickerPackInfo();
void onPreviewParse();
void onPreviewCheck();
void onPreviewTimeout();
void peerUpdated(PeerData *data);
void onFullPeerUpdated(PeerData *data);
void onPhotoUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
void onDocumentUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
void onThumbDocumentUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb);
void onPhotoProgress(const FullMsgId &msgId);
void onDocumentProgress(const FullMsgId &msgId);
void onPhotoFailed(const FullMsgId &msgId);
void onDocumentFailed(const FullMsgId &msgId);
void onReportSpamClicked();
void onReportSpamSure();
void onReportSpamHide();
void onReportSpamClear();
void onScroll();
void onHistoryToEnd();
void onSend(bool ctrlShiftEnter = false, MsgId replyTo = -1);
void onUnblock();
void onBotStart();
void onJoinChannel();
void onMuteUnmute();
void onBroadcastSilentChange();
void onPhotoSelect();
void onDocumentSelect();
void onPhotoDrop(const QMimeData *data);
void onDocumentDrop(const QMimeData *data);
void onFilesDrop(const QMimeData *data);
void onKbToggle(bool manual = true);
void onCmdStart();
void activate();
void onStickersUpdated();
void onTextChange();
void onFieldTabbed();
void onStickerSend(DocumentData *sticker);
void onPhotoSend(PhotoData *photo);
void onInlineResultSend(InlineBots::Result *result, UserData *bot);
void onWindowVisibleChanged();
void deleteMessage();
void forwardMessage();
void selectMessage();
void onForwardHere(); // instead of a reply
void onFieldFocused();
void onFieldResize();
void onCheckFieldAutocomplete();
void onScrollTimer();
void onForwardSelected();
void onDeleteSelected();
void onDeleteSelectedSure();
void onDeleteContextSure();
void onClearSelected();
void onAnimActiveStep();
void onDraftSaveDelayed();
void onDraftSave(bool delayed = false);
void onCloudDraftSave();
void updateStickers();
void onRecordError();
void onRecordDone(QByteArray result, VoiceWaveform waveform, qint32 samples);
void onRecordUpdate(quint16 level, qint32 samples);
void onUpdateHistoryItems();
// checks if we are too close to the top or to the bottom
// in the scroll area and preloads history if needed
void preloadHistoryIfNeeded();
private slots:
void onHashtagOrBotCommandInsert(QString str, FieldAutocomplete::ChooseMethod method);
void onMentionInsert(UserData *user);
void onInlineBotCancel();
void onMembersDropdownHidden();
void onMembersDropdownShow();
void onModerateKeyActivate(int index, bool *outHandled);
void updateField();
private:
// Updates position of controls around the message field,
// like send button, emoji button and others.
void moveFieldControls();
void updateFieldSize();
bool historyHasNotFreezedUnreadBar(History *history) const;
bool canWriteMessage() const;
void clearInlineBot();
void inlineBotChanged();
// 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);
void cancelReplyAfterMediaSend(bool lastKeyboardUsed);
int countMembersDropdownHeightMax() const;
MsgId _replyToId = 0;
Text _replyToName;
int _replyToNameVersion = 0;
void updateReplyToName();
MsgId _editMsgId = 0;
HistoryItem *_replyEditMsg = nullptr;
Text _replyEditMsgText;
mutable SingleTimer _updateEditTimeLeftDisplay;
IconedButton _fieldBarCancel;
void updateReplyEditTexts(bool force = false);
struct PinnedBar {
PinnedBar(MsgId msgId, HistoryWidget *parent);
MsgId msgId = 0;
HistoryItem *msg = nullptr;
Text text;
IconedButton cancel;
PlainShadow shadow;
};
PinnedBar *_pinnedBar = nullptr;
void updatePinnedBar(bool force = false);
bool pinnedMsgVisibilityUpdated();
void destroyPinnedBar();
void unpinDone(const MTPUpdates &updates);
class ReplyEditMessageDataCallback : public SharedCallback<void, ChannelData*, MsgId> {
public:
void call(ChannelData *channel, MsgId msgId) const override;
};
void sendExistingDocument(DocumentData *doc, const QString &caption);
void sendExistingPhoto(PhotoData *photo, const QString &caption);
void drawField(Painter &p, const QRect &rect);
void paintEditHeader(Painter &p, const QRect &rect, int left, int top) const;
void drawRecordButton(Painter &p);
void drawRecording(Painter &p);
void drawPinnedBar(Painter &p);
void updateMouseTracking();
// destroys _history and _migrated unread bars
void destroyUnreadBar();
mtpRequestId _saveEditMsgRequestId = 0;
void saveEditMsg();
void saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req);
bool saveEditMsgFail(History *history, const RPCError &error, mtpRequestId req);
static const mtpRequestId ReportSpamRequestNeeded = -1;
DBIPeerReportSpamStatus _reportSpamStatus = dbiprsUnknown;
mtpRequestId _reportSpamSettingRequestId = ReportSpamRequestNeeded;
void updateReportSpamStatus();
void requestReportSpamSetting();
void reportSpamSettingDone(const MTPPeerSettings &result, mtpRequestId req);
bool reportSpamSettingFail(const RPCError &error, mtpRequestId req);
QString _previewLinks;
WebPageData *_previewData = nullptr;
typedef QMap<QString, WebPageId> PreviewCache;
PreviewCache _previewCache;
mtpRequestId _previewRequest = 0;
Text _previewTitle;
Text _previewDescription;
SingleTimer _previewTimer;
bool _previewCancelled = false;
void gotPreview(QString links, const MTPMessageMedia &media, mtpRequestId req);
bool _replyForwardPressed = false;
HistoryItem *_replyReturn = nullptr;
QList<MsgId> _replyReturns;
bool messagesFailed(const RPCError &error, mtpRequestId requestId);
void addMessagesToFront(PeerData *peer, const QVector<MTPMessage> &messages);
void addMessagesToBack(PeerData *peer, const QVector<MTPMessage> &messages);
struct BotCallbackInfo {
FullMsgId msgId;
int row, col;
};
void botCallbackDone(BotCallbackInfo info, const MTPmessages_BotCallbackAnswer &answer, mtpRequestId req);
bool botCallbackFail(BotCallbackInfo info, const RPCError &error, mtpRequestId req);
enum ScrollChangeType {
ScrollChangeNone,
// When we toggle a pinned message.
ScrollChangeAdd,
// When loading a history part while scrolling down.
ScrollChangeNoJumpToBottom,
};
struct ScrollChange {
ScrollChangeType type;
int value;
};
void updateListSize(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 });
// Does any of the shown histories has this flag set.
bool hasPendingResizedItems() const {
return (_history && _history->hasPendingResizedItems()) || (_migrated && _migrated->hasPendingResizedItems());
}
// Counts scrollTop for placing the scroll right at the unread
// messages bar, choosing from _history and _migrated unreadBar.
int unreadBarTop() const;
void saveGifDone(DocumentData *doc, const MTPBool &result);
void reportSpamDone(PeerData *peer, const MTPBool &result, mtpRequestId request);
bool reportSpamFail(const RPCError &error, mtpRequestId request);
void unblockDone(PeerData *peer, const MTPBool &result, mtpRequestId req);
bool unblockFail(const RPCError &error, mtpRequestId req);
void blockDone(PeerData *peer, const MTPBool &result);
void joinDone(const MTPUpdates &result, mtpRequestId req);
bool joinFail(const RPCError &error, mtpRequestId req);
void countHistoryShowFrom();
mtpRequestId _stickersUpdateRequest = 0;
void stickersGot(const MTPmessages_AllStickers &stickers);
bool stickersFailed(const RPCError &error);
mtpRequestId _savedGifsUpdateRequest = 0;
void savedGifsGot(const MTPmessages_SavedGifs &gifs);
bool savedGifsFailed(const RPCError &error);
enum class TextUpdateEvent {
SaveDraft = 0x01,
SendTyping = 0x02,
};
Q_DECLARE_FLAGS(TextUpdateEvents, TextUpdateEvent);
Q_DECLARE_FRIEND_OPERATORS_FOR_FLAGS(TextUpdateEvents);
void writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft);
void writeDrafts(History *history);
void setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events = 0, FlatTextarea::UndoHistoryAction undoHistoryAction = FlatTextarea::ClearUndoHistory);
void clearFieldText(TextUpdateEvents events = 0, FlatTextarea::UndoHistoryAction undoHistoryAction = FlatTextarea::ClearUndoHistory) {
setFieldText(TextWithTags(), events, undoHistoryAction);
}
QStringList getMediasFromMime(const QMimeData *d);
void updateDragAreas();
// when scroll position or scroll area size changed this method
// updates the boundings of the visible area in HistoryInner
void visibleAreaUpdated();
bool readyToForward() const;
bool hasSilentToggle() const;
PeerData *_peer = nullptr;
// cache current _peer in _clearPeer when showing clear history box
PeerData *_clearPeer = nullptr;
ChannelId _channel = NoChannel;
bool _canSendMessages = false;
MsgId _showAtMsgId = ShowAtUnreadMsgId;
mtpRequestId _firstLoadRequest = 0;
mtpRequestId _preloadRequest = 0;
mtpRequestId _preloadDownRequest = 0;
MsgId _delayedShowAtMsgId = -1; // wtf?
mtpRequestId _delayedShowAtRequest = 0;
MsgId _activeAnimMsgId = 0;
ScrollArea _scroll;
HistoryInner *_list = nullptr;
History *_migrated = nullptr;
History *_history = nullptr;
bool _histInited = false; // initial updateListSize() called
int _addToScroll = 0;
int _lastScroll = 0;// gifs optimization
uint64 _lastScrolled = 0;
QTimer _updateHistoryItems;
ChildWidget<Ui::HistoryDownButton> _historyToEnd;
ChildWidget<FieldAutocomplete> _fieldAutocomplete;
UserData *_inlineBot = nullptr;
QString _inlineBotUsername;
mtpRequestId _inlineBotResolveRequestId = 0;
std_::unique_ptr<IconedButton> _inlineBotCancel;
void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result);
bool inlineBotResolveFail(QString name, const RPCError &error);
bool isBotStart() const;
bool isBlocked() const;
bool isJoinChannel() const;
bool isMuteUnmute() const;
bool updateCmdStartShown();
ReportSpamPanel _reportSpamPanel;
FlatButton _send, _unblock, _botStart, _joinChannel, _muteUnmute;
mtpRequestId _unblockRequest = 0;
mtpRequestId _reportSpamRequest = 0;
IconedButton _attachDocument, _attachPhoto;
EmojiButton _attachEmoji;
IconedButton _kbShow, _kbHide, _cmdStart;
SilentToggle _silent;
bool _cmdStartShown = false;
MessageField _field;
Animation _a_record, _a_recording;
bool _recording = false;
bool _inRecord = false;
bool _inField = false;
bool _inReplyEdit = false;
bool _inPinnedMsg = false;
anim::ivalue a_recordingLevel = { 0, 0 };
int32 _recordingSamples = 0;
anim::fvalue a_recordOver = { 0, 0 };
anim::fvalue a_recordDown = { 0, 0 };
anim::cvalue a_recordCancel;
int32 _recordCancelWidth;
bool kbWasHidden() const;
bool _kbShown = false;
HistoryItem *_kbReplyTo = nullptr;
ScrollArea _kbScroll;
BotKeyboard _keyboard;
ChildWidget<Ui::InnerDropdown> _membersDropdown = { nullptr };
QTimer _membersDropdownShowTimer;
Dropdown _attachType;
EmojiPan _emojiPan;
DragState _attachDrag = DragStateNone;
DragArea _attachDragDocument, _attachDragPhoto;
int32 _selCount; // < 0 - text selected, focus list, not _field
TaskQueue _fileLoader;
TextUpdateEvents _textUpdateEvents = (TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping);
int64 _serviceImageCacheSize = 0;
QString _confirmSource;
uint64 _confirmWithTextId = 0;
QString _titlePeerText;
bool _titlePeerTextOnline = false;
int _titlePeerTextWidth = 0;
Animation _a_show;
QPixmap _cacheUnder, _cacheOver;
anim::ivalue a_coordUnder, a_coordOver;
anim::fvalue a_progress;
QTimer _scrollTimer;
int32 _scrollDelta = 0;
QTimer _animActiveTimer;
float64 _animActiveStart = 0;
QMap<QPair<History*, SendActionType>, mtpRequestId> _sendActionRequests;
QTimer _sendActionStopTimer;
uint64 _saveDraftStart = 0;
bool _saveDraftText = false;
QTimer _saveDraftTimer, _saveCloudDraftTimer;
PlainShadow _topShadow;
bool _inGrab = false;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(HistoryWidget::TextUpdateEvents)