/* 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. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "style.h" #include "lang.h" #include "boxes/confirmbox.h" #include "historywidget.h" #include "gui/filedialog.h" #include "boxes/photosendbox.h" #include "mainwidget.h" #include "window.h" #include "passcodewidget.h" #include "window.h" #include "fileuploader.h" #include "localstorage.h" // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html HistoryList::HistoryList(HistoryWidget *historyWidget, ScrollArea *scroll, History *history) : QWidget(0) , hist(history) , historyWidget(historyWidget) , scrollArea(scroll) , currentBlock(0) , currentItem(0) , _cursor(style::cur_default) , _dragAction(NoDrag) , _dragSelType(TextSelectLetters) , _dragItem(0) , _dragWasInactive(false) , _dragSelFrom(0) , _dragSelTo(0) , _dragSelecting(false) , _touchScroll(false) , _touchSelect(false) , _touchInProgress(false) , _touchScrollState(TouchScrollManual) , _touchPrevPosValid(false) , _touchWaitingAcceleration(false) , _touchSpeedTime(0) , _touchAccelerationTime(0) , _touchTime(0) , _menu(0) { linkTipTimer.setSingleShot(true); connect(&linkTipTimer, SIGNAL(timeout()), this, SLOT(showLinkTip())); _touchSelectTimer.setSingleShot(true); connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect())); setAttribute(Qt::WA_AcceptTouchEvents); connect(&_touchScrollTimer, SIGNAL(timeout()), this, SLOT(onTouchScrollTimer())); _trippleClickTimer.setSingleShot(true); setMouseTracking(true); } void HistoryList::messagesReceived(const QVector &messages) { hist->addToFront(messages); } void HistoryList::messagesReceivedDown(const QVector &messages) { hist->addToBack(messages); } void HistoryList::updateMsg(const HistoryItem *msg) { if (!msg || msg->detached() || !hist || hist != msg->history()) return; update(0, height() - hist->height - st::historyPadding + msg->block()->y + msg->y, width(), msg->height()); } void HistoryList::paintEvent(QPaintEvent *e) { QRect r(e->rect()); bool trivial = (rect() == r); QPainter p(this); if (!trivial) { p.setClipRect(r); } if (hist->isEmpty()) { QPoint dogPos((width() - st::msgDogImg.pxWidth()) / 2, ((height() - st::msgDogImg.pxHeight()) * 4) / 9); p.drawPixmap(dogPos, *cChatDogImage()); } else { adjustCurrent(r.top()); HistoryBlock *block = (*hist)[currentBlock]; HistoryItem *item = (*block)[currentItem]; SelectedItems::const_iterator selEnd = _selected.cend(); bool hasSel = !_selected.isEmpty(); int32 firstItemY = height() - hist->height - st::historyPadding, drawToY = r.bottom() - firstItemY; int32 selfromy = 0, seltoy = 0; if (_dragSelFrom && _dragSelTo) { selfromy = _dragSelFrom->y + _dragSelFrom->block()->y; seltoy = _dragSelTo->y + _dragSelTo->block()->y + _dragSelTo->height(); } int32 iBlock = currentBlock, iItem = currentItem, y = block->y + item->y; p.translate(0, firstItemY + y); while (y < drawToY) { int32 h = item->height(); uint32 sel = 0; if (y >= selfromy && y < seltoy) { sel = (_dragSelecting && !item->serviceMsg() && item->id > 0) ? FullItemSel : 0; } else if (hasSel) { SelectedItems::const_iterator i = _selected.constFind(item); if (i != selEnd) { sel = i.value(); } } item->draw(p, sel); p.translate(0, h); ++iItem; if (iItem == block->size()) { iItem = 0; ++iBlock; if (iBlock == hist->size()) { break; } block = (*hist)[iBlock]; } item = (*block)[iItem]; y += h; } } } bool HistoryList::event(QEvent *e) { if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { QTouchEvent *ev = static_cast(e); if (ev->device()->type() == QTouchDevice::TouchScreen) { touchEvent(ev); return true; } } return QWidget::event(e); } void HistoryList::onTouchScrollTimer() { uint64 nowTime = getms(); if (_touchScrollState == TouchScrollAcceleration && _touchWaitingAcceleration && (nowTime - _touchAccelerationTime) > 40) { _touchScrollState = TouchScrollManual; touchResetSpeed(); } else if (_touchScrollState == TouchScrollAuto || _touchScrollState == TouchScrollAcceleration) { int32 elapsed = int32(nowTime - _touchTime); QPoint delta = _touchSpeed * elapsed / 1000; bool hasScrolled = historyWidget->touchScroll(delta); if (_touchSpeed.isNull() || !hasScrolled) { _touchScrollState = TouchScrollManual; _touchScroll = false; _touchScrollTimer.stop(); } else { _touchTime = nowTime; } touchDeaccelerate(elapsed); } } void HistoryList::touchUpdateSpeed() { const uint64 nowTime = getms(); if (_touchPrevPosValid) { const int elapsed = nowTime - _touchSpeedTime; if (elapsed) { const QPoint newPixelDiff = (_touchPos - _touchPrevPos); const QPoint pixelsPerSecond = newPixelDiff * (1000 / elapsed); // fingers are inacurates, we ignore small changes to avoid stopping the autoscroll because // of a small horizontal offset when scrolling vertically const int newSpeedY = (qAbs(pixelsPerSecond.y()) > FingerAccuracyThreshold) ? pixelsPerSecond.y() : 0; const int newSpeedX = (qAbs(pixelsPerSecond.x()) > FingerAccuracyThreshold) ? pixelsPerSecond.x() : 0; if (_touchScrollState == TouchScrollAuto) { const int oldSpeedY = _touchSpeed.y(); const int oldSpeedX = _touchSpeed.x(); if ((oldSpeedY <= 0 && newSpeedY <= 0) || ((oldSpeedY >= 0 && newSpeedY >= 0) && (oldSpeedX <= 0 && newSpeedX <= 0)) || (oldSpeedX >= 0 && newSpeedX >= 0)) { _touchSpeed.setY(snap((oldSpeedY + (newSpeedY / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated)); _touchSpeed.setX(snap((oldSpeedX + (newSpeedX / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated)); } else { _touchSpeed = QPoint(); } } else { // we average the speed to avoid strange effects with the last delta if (!_touchSpeed.isNull()) { _touchSpeed.setX(snap((_touchSpeed.x() / 4) + (newSpeedX * 3 / 4), -MaxScrollFlick, +MaxScrollFlick)); _touchSpeed.setY(snap((_touchSpeed.y() / 4) + (newSpeedY * 3 / 4), -MaxScrollFlick, +MaxScrollFlick)); } else { _touchSpeed = QPoint(newSpeedX, newSpeedY); } } } } else { _touchPrevPosValid = true; } _touchSpeedTime = nowTime; _touchPrevPos = _touchPos; } void HistoryList::touchResetSpeed() { _touchSpeed = QPoint(); _touchPrevPosValid = false; } void HistoryList::touchDeaccelerate(int32 elapsed) { int32 x = _touchSpeed.x(); int32 y = _touchSpeed.y(); _touchSpeed.setX((x == 0) ? x : (x > 0) ? qMax(0, x - elapsed) : qMin(0, x + elapsed)); _touchSpeed.setY((y == 0) ? y : (y > 0) ? qMax(0, y - elapsed) : qMin(0, y + elapsed)); } void HistoryList::touchEvent(QTouchEvent *e) { const Qt::TouchPointStates &states(e->touchPointStates()); if (e->type() == QEvent::TouchCancel) { // cancel if (!_touchInProgress) return; _touchInProgress = false; _touchSelectTimer.stop(); _touchScroll = _touchSelect = false; _touchScrollState = TouchScrollManual; dragActionCancel(); return; } if (!e->touchPoints().isEmpty()) { _touchPrevPos = _touchPos; _touchPos = e->touchPoints().cbegin()->screenPos().toPoint(); } switch (e->type()) { case QEvent::TouchBegin: if (_menu) { e->accept(); return; // ignore mouse press, that was hiding context menu } if (_touchInProgress) return; if (e->touchPoints().isEmpty()) return; _touchInProgress = true; if (_touchScrollState == TouchScrollAuto) { _touchScrollState = TouchScrollAcceleration; _touchWaitingAcceleration = true; _touchAccelerationTime = getms(); touchUpdateSpeed(); _touchStart = _touchPos; } else { _touchScroll = false; _touchSelectTimer.start(QApplication::startDragTime()); } _touchSelect = false; _touchStart = _touchPrevPos = _touchPos; break; case QEvent::TouchUpdate: if (!_touchInProgress) return; if (_touchSelect) { dragActionUpdate(_touchPos); } else if (!_touchScroll && (_touchPos - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { _touchSelectTimer.stop(); _touchScroll = true; touchUpdateSpeed(); } if (_touchScroll) { if (_touchScrollState == TouchScrollManual) { touchScrollUpdated(_touchPos); } else if (_touchScrollState == TouchScrollAcceleration) { touchUpdateSpeed(); _touchAccelerationTime = getms(); if (_touchSpeed.isNull()) { _touchScrollState = TouchScrollManual; } } } break; case QEvent::TouchEnd: if (!_touchInProgress) return; _touchInProgress = false; if (_touchSelect) { dragActionFinish(_touchPos, Qt::RightButton); QContextMenuEvent contextMenu(QContextMenuEvent::Mouse, mapFromGlobal(_touchPos), _touchPos); showContextMenu(&contextMenu, true); _touchScroll = false; } else if (_touchScroll) { if (_touchScrollState == TouchScrollManual) { _touchScrollState = TouchScrollAuto; _touchPrevPosValid = false; _touchScrollTimer.start(15); _touchTime = getms(); } else if (_touchScrollState == TouchScrollAuto) { _touchScrollState = TouchScrollManual; _touchScroll = false; touchResetSpeed(); } else if (_touchScrollState == TouchScrollAcceleration) { _touchScrollState = TouchScrollAuto; _touchWaitingAcceleration = false; _touchPrevPosValid = false; } } else { // one short tap -- like mouse click dragActionStart(_touchPos); dragActionFinish(_touchPos); } _touchSelectTimer.stop(); _touchSelect = false; break; } } void HistoryList::mouseMoveEvent(QMouseEvent *e) { if (!(e->buttons() & (Qt::LeftButton | Qt::MiddleButton)) && (textlnkDown() || _dragAction != NoDrag)) { mouseReleaseEvent(e); } dragActionUpdate(e->globalPos()); } void HistoryList::dragActionUpdate(const QPoint &screenPos) { _dragPos = screenPos; onUpdateSelected(); } void HistoryList::touchScrollUpdated(const QPoint &screenPos) { _touchPos = screenPos; historyWidget->touchScroll(_touchPos - _touchPrevPos); touchUpdateSpeed(); } QPoint HistoryList::mapMouseToItem(QPoint p, HistoryItem *item) { if (!item || item->detached()) return QPoint(0, 0); p.setY(p.y() - (height() - hist->height - st::historyPadding) - item->block()->y - item->y); return p; } void HistoryList::mousePressEvent(QMouseEvent *e) { if (_menu) { e->accept(); return; // ignore mouse press, that was hiding context menu } dragActionStart(e->globalPos(), e->button()); } void HistoryList::dragActionStart(const QPoint &screenPos, Qt::MouseButton button) { dragActionUpdate(screenPos); if (button != Qt::LeftButton) return; if (App::pressedItem() != App::hoveredItem()) { updateMsg(App::pressedItem()); App::pressedItem(App::hoveredItem()); updateMsg(App::pressedItem()); } if (textlnkDown() != textlnkOver()) { updateMsg(App::pressedLinkItem()); textlnkDown(textlnkOver()); App::pressedLinkItem(App::hoveredLinkItem()); updateMsg(App::pressedLinkItem()); updateMsg(App::pressedItem()); } _dragAction = NoDrag; _dragItem = App::mousedItem(); _dragStartPos = mapMouseToItem(mapFromGlobal(screenPos), _dragItem); _dragWasInactive = App::wnd()->inactivePress(); if (_dragWasInactive) App::wnd()->inactivePress(false); bool textLink = textlnkDown() && !textlnkDown()->encoded().isEmpty(); if (textLink) { _dragAction = PrepareDrag; } else if (!_selected.isEmpty()) { if (_selected.cbegin().value() == FullItemSel) { if (_selected.constFind(_dragItem) != _selected.cend() && App::hoveredItem()) { _dragAction = PrepareDrag; // start items drag } else if (!_dragWasInactive) { _dragAction = PrepareSelect; // start items select } } } if (_dragAction == NoDrag && _dragItem) { bool afterDragSymbol, uponSymbol; uint16 symbol; if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) { _dragItem->getSymbol(symbol, afterDragSymbol, uponSymbol, _dragStartPos.x(), _dragStartPos.y()); if (uponSymbol) { uint32 selStatus = (symbol << 16) | symbol; if (selStatus != FullItemSel && (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel)) { if (!_selected.isEmpty()) { updateMsg(_selected.cbegin().key()); _selected.clear(); } _selected.insert(_dragItem, selStatus); _dragSymbol = symbol; _dragAction = Selecting; _dragSelType = TextSelectParagraphs; dragActionUpdate(_dragPos); _trippleClickTimer.start(QApplication::doubleClickInterval()); } } } else if (App::pressedItem()) { _dragItem->getSymbol(symbol, afterDragSymbol, uponSymbol, _dragStartPos.x(), _dragStartPos.y()); } if (_dragSelType != TextSelectParagraphs) { if (App::pressedItem()) { _dragSymbol = symbol; bool uponSelected = uponSymbol; if (uponSelected) { if (_selected.isEmpty() || _selected.cbegin().value() == FullItemSel || _selected.cbegin().key() != _dragItem ) { uponSelected = false; } else { uint16 selFrom = (_selected.cbegin().value() >> 16) & 0xFFFF, selTo = _selected.cbegin().value() & 0xFFFF; if (_dragSymbol < selFrom || _dragSymbol >= selTo) { uponSelected = false; } } } if (uponSelected) { _dragAction = PrepareDrag; // start text drag } else if (!_dragWasInactive) { if (afterDragSymbol) ++_dragSymbol; uint32 selStatus = (_dragSymbol << 16) | _dragSymbol; if (selStatus != FullItemSel && (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel)) { if (!_selected.isEmpty()) { updateMsg(_selected.cbegin().key()); _selected.clear(); } _selected.insert(_dragItem, selStatus); _dragAction = Selecting; updateMsg(_dragItem); } else { _dragAction = PrepareSelect; } } } else if (!_dragWasInactive) { _dragAction = PrepareSelect; // start items select } } } if (!_dragItem) { _dragAction = NoDrag; } else if (_dragAction == NoDrag) { _dragItem = 0; } } void HistoryList::dragActionCancel() { _dragItem = 0; _dragAction = NoDrag; _dragStartPos = QPoint(0, 0); _dragSelFrom = _dragSelTo = 0; historyWidget->noSelectingScroll(); } void HistoryList::itemRemoved(HistoryItem *item) { SelectedItems::iterator i = _selected.find(item); if (i != _selected.cend()) { _selected.erase(i); historyWidget->updateTopBarSelection(); } if (_dragAction == NoDrag) return; if (_dragItem == item) { dragActionCancel(); } onUpdateSelected(); if (_dragSelFrom == item) _dragSelFrom = 0; if (_dragSelTo == item) _dragSelTo = 0; updateDragSelection(_dragSelFrom, _dragSelTo, _dragSelecting, true); } void HistoryList::itemReplaced(HistoryItem *oldItem, HistoryItem *newItem) { if (_dragItem == oldItem) _dragItem = newItem; SelectedItems::iterator i = _selected.find(oldItem); if (i != _selected.cend()) { uint32 v = i.value(); _selected.erase(i); _selected.insert(newItem, v); } if (_dragSelFrom == oldItem) _dragSelFrom = newItem; if (_dragSelTo == oldItem) _dragSelTo = newItem; } void HistoryList::dragActionFinish(const QPoint &screenPos, Qt::MouseButton button) { TextLinkPtr needClick; dragActionUpdate(screenPos); if (textlnkOver()) { if (textlnkDown() == textlnkOver() && _dragAction != Dragging) { needClick = textlnkDown(); } } if (textlnkDown()) { updateMsg(App::pressedLinkItem()); textlnkDown(TextLinkPtr()); App::pressedLinkItem(0); if (!textlnkOver() && _cursor != style::cur_default) { _cursor = style::cur_default; setCursor(_cursor); } } if (App::pressedItem()) { updateMsg(App::pressedItem()); App::pressedItem(0); } if (needClick) { needClick->onClick(button); dragActionCancel(); return; } if (_dragAction == PrepareSelect && !needClick && !_dragWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullItemSel) { SelectedItems::iterator i = _selected.find(_dragItem); if (i == _selected.cend() && !_dragItem->serviceMsg() && _dragItem->id > 0) { if (_selected.size() < MaxSelectedItems) { if (!_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { _selected.clear(); } _selected.insert(_dragItem, FullItemSel); } } else { _selected.erase(i); } updateMsg(_dragItem); } else if (_dragAction == PrepareDrag && !needClick && !_dragWasInactive && button != Qt::RightButton) { SelectedItems::iterator i = _selected.find(_dragItem); if (i != _selected.cend() && i.value() == FullItemSel) { _selected.erase(i); updateMsg(_dragItem); } else { _selected.clear(); parentWidget()->update(); } } else if (_dragAction == Selecting) { if (_dragSelFrom && _dragSelTo) { applyDragSelection(); } else if (!_selected.isEmpty() && !_dragWasInactive) { uint32 sel = _selected.cbegin().value(); if (sel != FullItemSel && (sel & 0xFFFF) == ((sel >> 16) & 0xFFFF)) { _selected.clear(); App::wnd()->setInnerFocus(); } } } _dragAction = NoDrag; _dragSelType = TextSelectLetters; historyWidget->noSelectingScroll(); historyWidget->updateTopBarSelection(); } void HistoryList::mouseReleaseEvent(QMouseEvent *e) { dragActionFinish(e->globalPos(), e->button()); if (!rect().contains(e->pos())) { leaveEvent(e); } } void HistoryList::mouseDoubleClickEvent(QMouseEvent *e) { if (((_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) || (_dragAction == NoDrag && (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel))) && _dragSelType == TextSelectLetters && _dragItem) { bool afterDragSymbol, uponSelected; uint16 symbol; _dragItem->getSymbol(symbol, afterDragSymbol, uponSelected, _dragStartPos.x(), _dragStartPos.y()); if (uponSelected) { _dragSymbol = symbol; _dragSelType = TextSelectWords; if (_dragAction == NoDrag) { _dragAction = Selecting; uint32 selStatus = (symbol << 16) | symbol; if (!_selected.isEmpty()) { updateMsg(_selected.cbegin().key()); _selected.clear(); } _selected.insert(_dragItem, selStatus); } mouseMoveEvent(e); _trippleClickPoint = e->globalPos(); _trippleClickTimer.start(QApplication::doubleClickInterval()); } } else { mousePressEvent(e); } } void HistoryList::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (_menu) { _menu->deleteLater(); _menu = 0; } if (e->reason() == QContextMenuEvent::Mouse) { dragActionUpdate(e->globalPos()); } // -2 - has full selected items, but not over, -1 - has selection, but no over, 0 - no selection, 1 - over text, 2 - over full selected items int32 isUponSelected = 0, hasSelected = 0;; if (!_selected.isEmpty()) { isUponSelected = -1; if (_selected.cbegin().value() == FullItemSel) { hasSelected = 2; if (App::hoveredItem() && _selected.constFind(App::hoveredItem()) != _selected.cend()) { isUponSelected = 2; } else { isUponSelected = -2; } } else { uint16 symbol, selFrom = (_selected.cbegin().value() >> 16) & 0xFFFF, selTo = _selected.cbegin().value() & 0xFFFF; hasSelected = (selTo > selFrom) ? 1 : 0; if (_dragItem && _dragItem == App::hoveredItem()) { QPoint mousePos(mapMouseToItem(mapFromGlobal(_dragPos), _dragItem)); bool afterDragSymbol, uponSymbol; _dragItem->getSymbol(symbol, afterDragSymbol, uponSymbol, mousePos.x(), mousePos.y()); if (uponSymbol && symbol >= selFrom && symbol < selTo) { isUponSelected = 1; } } } } if (showFromTouch && hasSelected && isUponSelected < hasSelected) { isUponSelected = hasSelected; } _contextMenuLnk = textlnkOver(); HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem(); PhotoLink *lnkPhoto = dynamic_cast(_contextMenuLnk.data()); VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); if (lnkPhoto || lnkVideo || lnkAudio || lnkDocument) { _menu = new ContextMenu(historyWidget); if (isUponSelected > 0) { _menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true); } if (item && item->id > 0 && isUponSelected != 2 && isUponSelected != -2) { _menu->addAction(lang(lng_context_reply_msg), historyWidget, SLOT(onReplyToMessage())); } if (lnkPhoto) { _menu->addAction(lang(lng_context_open_image), this, SLOT(openContextUrl()))->setEnabled(true); _menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextImage()))->setEnabled(true); _menu->addAction(lang(lng_context_copy_image), this, SLOT(copyContextImage()))->setEnabled(true); } else { if ((lnkVideo && lnkVideo->video()->loader) || (lnkAudio && lnkAudio->audio()->loader) || (lnkDocument && lnkDocument->document()->loader)) { _menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true); } else { if ((lnkVideo && !lnkVideo->video()->already(true).isEmpty()) || (lnkAudio && !lnkAudio->audio()->already(true).isEmpty()) || (lnkDocument && !lnkDocument->document()->already(true).isEmpty())) { _menu->addAction(lang(cPlatform() == dbipMac ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true); } _menu->addAction(lang(lnkVideo ? lng_context_open_video : (lnkAudio ? lng_context_open_audio : lng_context_open_file)), this, SLOT(openContextFile()))->setEnabled(true); _menu->addAction(lang(lnkVideo ? lng_context_save_video : (lnkAudio ? lng_context_save_audio : lng_context_save_file)), this, SLOT(saveContextFile()))->setEnabled(true); } } if (isUponSelected > 1) { _menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected())); _menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected())); _menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected())); } else if (App::hoveredLinkItem()) { if (isUponSelected != -2) { if (dynamic_cast(App::hoveredLinkItem()) && App::hoveredLinkItem()->id > 0) { _menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true); } _menu->addAction(lang(lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true); } if (App::hoveredLinkItem()->id > 0) { _menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true); } App::contextItem(App::hoveredLinkItem()); } } else { // maybe cursor on some text history item? bool canDelete = (item && item->itemType() == HistoryItem::MsgType); bool canForward = canDelete && (item->id > 0) && !item->serviceMsg(); HistoryMessage *msg = dynamic_cast(item); HistoryServiceMsg *srv = dynamic_cast(item); if (isUponSelected > 0) { if (!_menu) _menu = new ContextMenu(this); _menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true); if (item && item->id > 0 && isUponSelected != 2) { _menu->addAction(lang(lng_context_reply_msg), historyWidget, SLOT(onReplyToMessage())); } } else { if (item && item->id > 0 && isUponSelected != -2) { if (!_menu) _menu = new ContextMenu(this); _menu->addAction(lang(lng_context_reply_msg), historyWidget, SLOT(onReplyToMessage())); } if (item && !isUponSelected && !_contextMenuLnk) { QString contextMenuText = item->selectedText(FullItemSel); if (!contextMenuText.isEmpty() && (!msg || !msg->getMedia() || msg->getMedia()->type() != MediaTypeSticker)) { if (!_menu) _menu = new ContextMenu(this); _menu->addAction(lang(lng_context_copy_text), this, SLOT(copyContextText()))->setEnabled(true); } } } if (_contextMenuLnk && dynamic_cast(_contextMenuLnk.data())) { if (!_menu) _menu = new ContextMenu(historyWidget); _menu->addAction(lang(lng_context_open_link), this, SLOT(openContextUrl()))->setEnabled(true); _menu->addAction(lang(lng_context_copy_link), this, SLOT(copyContextUrl()))->setEnabled(true); } else if (_contextMenuLnk && dynamic_cast(_contextMenuLnk.data())) { if (!_menu) _menu = new ContextMenu(historyWidget); _menu->addAction(lang(lng_context_open_email), this, SLOT(openContextUrl()))->setEnabled(true); _menu->addAction(lang(lng_context_copy_email), this, SLOT(copyContextUrl()))->setEnabled(true); } else if (_contextMenuLnk && dynamic_cast(_contextMenuLnk.data())) { if (!_menu) _menu = new ContextMenu(historyWidget); _menu->addAction(lang(lng_context_open_mention), this, SLOT(openContextUrl()))->setEnabled(true); _menu->addAction(lang(lng_context_copy_mention), this, SLOT(copyContextUrl()))->setEnabled(true); } else if (_contextMenuLnk && dynamic_cast(_contextMenuLnk.data())) { if (!_menu) _menu = new ContextMenu(historyWidget); _menu->addAction(lang(lng_context_open_hashtag), this, SLOT(openContextUrl()))->setEnabled(true); _menu->addAction(lang(lng_context_copy_hashtag), this, SLOT(copyContextUrl()))->setEnabled(true); } else { } if (isUponSelected > 1) { if (!_menu) _menu = new ContextMenu(this); _menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected())); _menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected())); _menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected())); } else if (item && ((isUponSelected != -2 && (canForward || canDelete)) || item->id > 0)) { if (!_menu) _menu = new ContextMenu(this); if (isUponSelected != -2) { if (canForward) { _menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true); } if (canDelete) { _menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true); } } if (item->id > 0) { _menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true); } } else { if (App::mousedItem() && App::mousedItem()->itemType() == HistoryItem::MsgType && App::mousedItem()->id > 0) { if (!_menu) _menu = new ContextMenu(this); _menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true); item = App::mousedItem(); } } App::contextItem(item); } if (_menu) { _menu->deleteOnHide(); connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*))); _menu->popup(e->globalPos()); e->accept(); } } void HistoryList::onMenuDestroy(QObject *obj) { if (_menu == obj) { _menu = 0; } } void HistoryList::copySelectedText() { QApplication::clipboard()->setText(getSelectedText()); } void HistoryList::openContextUrl() { HistoryItem *was = App::hoveredLinkItem(); App::hoveredLinkItem(App::contextItem()); _contextMenuLnk->onClick(Qt::LeftButton); App::hoveredLinkItem(was); } void HistoryList::copyContextUrl() { QString enc = _contextMenuLnk->encoded(); if (!enc.isEmpty()) { QApplication::clipboard()->setText(enc); } } void HistoryList::saveContextImage() { PhotoLink *lnk = dynamic_cast(_contextMenuLnk.data()); if (!lnk) return; PhotoData *photo = lnk->photo(); if (!photo || !photo->date || !photo->full->loaded()) return; QString file; if (filedialogGetSaveFile(file, lang(lng_save_photo), qsl("JPEG Image (*.jpg);;All files (*.*)"), filedialogDefaultName(qsl("photo"), qsl(".jpg")))) { if (!file.isEmpty()) { photo->full->pix().toImage().save(file, "JPG"); } } } void HistoryList::copyContextImage() { PhotoLink *lnk = dynamic_cast(_contextMenuLnk.data()); if (!lnk) return; PhotoData *photo = lnk->photo(); if (!photo || !photo->date || !photo->full->loaded()) return; QApplication::clipboard()->setPixmap(photo->full->pix()); } void HistoryList::cancelContextDownload() { VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); mtpFileLoader *loader = lnkVideo ? lnkVideo->video()->loader : (lnkAudio ? lnkAudio->audio()->loader : (lnkDocument ? lnkDocument->document()->loader : 0)); if (loader) loader->cancel(); } void HistoryList::showContextInFolder() { VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); QString already = lnkVideo ? lnkVideo->video()->already(true) : (lnkAudio ? lnkAudio->audio()->already(true) : (lnkDocument ? lnkDocument->document()->already(true) : QString())); if (!already.isEmpty()) psShowInFolder(already); } void HistoryList::openContextFile() { VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); if (lnkVideo) VideoOpenLink(lnkVideo->video()).onClick(Qt::LeftButton); if (lnkAudio) AudioOpenLink(lnkAudio->audio()).onClick(Qt::LeftButton); if (lnkDocument) DocumentOpenLink(lnkDocument->document()).onClick(Qt::LeftButton); } void HistoryList::saveContextFile() { VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); if (lnkVideo) VideoSaveLink(lnkVideo->video()).doSave(true); if (lnkAudio) AudioSaveLink(lnkAudio->audio()).doSave(true); if (lnkDocument) DocumentSaveLink(lnkDocument->document()).doSave(true); } void HistoryList::copyContextText() { HistoryItem *item = App::contextItem(); if (item && item->itemType() != HistoryItem::MsgType) { item = 0; } if (!item) return; QString contextMenuText = item->selectedText(FullItemSel); if (!contextMenuText.isEmpty()) { QApplication::clipboard()->setText(contextMenuText); } } void HistoryList::resizeEvent(QResizeEvent *e) { onUpdateSelected(); } QString HistoryList::getSelectedText() const { if (_selected.isEmpty()) return QString(); if (_selected.cbegin().value() != FullItemSel) { return _selected.cbegin().key()->selectedText(_selected.cbegin().value()); } int32 fullSize = 0; QString timeFormat(qsl(", [dd.MM.yy hh:mm]\n")); QMap texts; for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) { HistoryItem *item = i.key(); QString text, sel = item->selectedText(FullItemSel), time = item->date.toString(timeFormat); int32 size = item->from()->name.size() + time.size() + sel.size(); text.reserve(size); texts.insert(item->y + item->block()->y, text.append(item->from()->name).append(time).append(sel)); fullSize += size; } QString result, sep(qsl("\n\n")); result.reserve(fullSize + (texts.size() - 1) * 2); for (QMap::const_iterator i = texts.cbegin(), e = texts.cend(); i != e; ++i) { result.append(i.value()); if (i + 1 != e) { result.append(sep); } } return result; } void HistoryList::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { historyWidget->onClearSelected(); } else if (e == QKeySequence::Copy && !_selected.isEmpty()) { copySelectedText(); } else if (e == QKeySequence::Delete) { historyWidget->onDeleteSelected(); } else { e->ignore(); } } int32 HistoryList::recountHeight(bool dontRecountText) { int32 st = hist->lastScrollTop; hist->geomResize(scrollArea->width(), &st, dontRecountText); return st; } void HistoryList::updateSize() { int32 ph = scrollArea->height(), nh = (hist->height + st::historyPadding) > ph ? (hist->height + st::historyPadding) : ph; if (width() != scrollArea->width() || height() != nh) { resize(scrollArea->width(), nh); } else { update(); } } void HistoryList::enterEvent(QEvent *e) { return QWidget::enterEvent(e); } void HistoryList::leaveEvent(QEvent *e) { if (textlnkOver()) { updateMsg(App::hoveredItem()); updateMsg(App::hoveredLinkItem()); textlnkOver(TextLinkPtr()); App::hoveredLinkItem(0); App::hoveredItem(0); if (!textlnkDown() && _cursor != style::cur_default) { _cursor = style::cur_default; setCursor(_cursor); } } return QWidget::leaveEvent(e); } HistoryList::~HistoryList() { delete _menu; } void HistoryList::adjustCurrent(int32 y) { if (hist->isEmpty()) return; if (currentBlock >= hist->size()) { currentBlock = hist->size() - 1; currentItem = 0; } int32 dh = height() - hist->height - st::historyPadding; while ((*hist)[currentBlock]->y + dh > y && currentBlock > 0) { --currentBlock; currentItem = 0; } while ((*hist)[currentBlock]->y + (*hist)[currentBlock]->height + dh <= y && currentBlock + 1 < hist->size()) { ++currentBlock; currentItem = 0; } HistoryBlock *block = (*hist)[currentBlock]; if (currentItem >= block->size()) { currentItem = block->size() - 1; } int32 by = block->y; while ((*block)[currentItem]->y + by + dh > y && currentItem > 0) { --currentItem; } while ((*block)[currentItem]->y + (*block)[currentItem]->height() + by + dh <= y && currentItem + 1 < block->size()) { ++currentItem; } } HistoryItem *HistoryList::prevItem(HistoryItem *item) { if (!item) return 0; HistoryBlock *block = item->block(); int32 blockIndex = hist->indexOf(block), itemIndex = block->indexOf(item); if (blockIndex < 0 || itemIndex < 0) return 0; if (itemIndex > 0) { return (*block)[itemIndex - 1]; } if (blockIndex > 0) { return *((*hist)[blockIndex - 1]->cend() - 1); } return 0; } HistoryItem *HistoryList::nextItem(HistoryItem *item) { if (!item) return 0; HistoryBlock *block = item->block(); int32 blockIndex = hist->indexOf(block), itemIndex = block->indexOf(item); if (blockIndex < 0 || itemIndex < 0) return 0; if (itemIndex + 1 < block->size()) { return (*block)[itemIndex + 1]; } if (blockIndex + 1 < hist->size()) { return *(*hist)[blockIndex + 1]->cbegin(); } return 0; } bool HistoryList::canCopySelected() const { return !_selected.isEmpty(); } bool HistoryList::canDeleteSelected() const { return !_selected.isEmpty() && (_selected.cbegin().value() == FullItemSel); } void HistoryList::getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const { selectedForForward = selectedForDelete = 0; for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) { if (i.key()->itemType() == HistoryItem::MsgType && i.value() == FullItemSel) { ++selectedForDelete; if (!i.key()->serviceMsg() && i.key()->id > 0) { ++selectedForForward; } } } if (!selectedForDelete && !selectedForForward && !_selected.isEmpty()) { // text selection selectedForForward = -1; } } void HistoryList::clearSelectedItems(bool onlyTextSelection) { if (!_selected.isEmpty() && (!onlyTextSelection || _selected.cbegin().value() != FullItemSel)) { _selected.clear(); historyWidget->updateTopBarSelection(); historyWidget->update(); } } void HistoryList::fillSelectedItems(SelectedItemSet &sel, bool forDelete) { if (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel) return; for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) { HistoryItem *item = i.key(); if (dynamic_cast(item) && item->id > 0) { sel.insert(item->id, item); } } } void HistoryList::selectItem(HistoryItem *item) { if (!_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { _selected.clear(); } else if (_selected.size() == MaxSelectedItems && _selected.constFind(item) == _selected.cend()) { return; } _selected.insert(item, FullItemSel); historyWidget->updateTopBarSelection(); historyWidget->update(); } void HistoryList::onTouchSelect() { _touchSelect = true; dragActionStart(_touchPos); } void HistoryList::onUpdateSelected() { if (!hist || hist->isEmpty()) return; QPoint mousePos(mapFromGlobal(_dragPos)); QPoint m(historyWidget->clampMousePosition(mousePos)); adjustCurrent(m.y()); HistoryBlock *block = (*hist)[currentBlock]; HistoryItem *item = (*block)[currentItem]; App::mousedItem(item); m = mapMouseToItem(m, item); if (item->hasPoint(m.x(), m.y())) { updateMsg(App::hoveredItem()); App::hoveredItem(item); updateMsg(App::hoveredItem()); } else if (App::hoveredItem()) { updateMsg(App::hoveredItem()); App::hoveredItem(0); } linkTipTimer.start(1000); Qt::CursorShape cur = style::cur_default; bool inText, lnkChanged = false; TextLinkPtr lnk; item->getState(lnk, inText, m.x(), m.y()); if (lnk != textlnkOver()) { lnkChanged = true; updateMsg(App::hoveredLinkItem()); textlnkOver(lnk); QToolTip::showText(_dragPos, QString(), App::wnd()); App::hoveredLinkItem(lnk ? item : 0); updateMsg(App::hoveredLinkItem()); } if (_dragAction == NoDrag) { if (lnk) { cur = style::cur_pointer; } else if (inText && (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel)) { cur = style::cur_text; } } else { if (item != _dragItem || (m - _dragStartPos).manhattanLength() >= QApplication::startDragDistance()) { if (_dragAction == PrepareDrag) { _dragAction = Dragging; } else if (_dragAction == PrepareSelect) { _dragAction = Selecting; } } cur = textlnkDown() ? style::cur_pointer : style::cur_default; if (_dragAction == Selecting) { if (item == _dragItem && item == App::hoveredItem() && !_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { bool afterSymbol, uponSymbol; uint16 second; _dragItem->getSymbol(second, afterSymbol, uponSymbol, m.x(), m.y()); if (afterSymbol && _dragSelType == TextSelectLetters) ++second; _selected[_dragItem] = _dragItem->adjustSelection(qMin(second, _dragSymbol), qMax(second, _dragSymbol), _dragSelType); updateDragSelection(0, 0, false); } else { bool selectingDown = (_dragItem->block()->y < item->block()->y) || ((_dragItem->block() == item->block()) && (_dragItem->y < item->y || (_dragItem == item && _dragStartPos.y() < m.y()))); HistoryItem *dragSelFrom = _dragItem, *dragSelTo = item; if (!dragSelFrom->hasPoint(_dragStartPos.x(), _dragStartPos.y())) { // maybe exclude dragSelFrom if (selectingDown) { if (_dragStartPos.y() >= dragSelFrom->height() - st::msgMargin.bottom() || ((item == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance()))) { dragSelFrom = (dragSelFrom == dragSelTo) ? 0 : nextItem(dragSelFrom); } } else { if (_dragStartPos.y() < st::msgMargin.top() || ((item == dragSelFrom) && (m.y() >= _dragStartPos.y() - QApplication::startDragDistance()))) { dragSelFrom = (dragSelFrom == dragSelTo) ? 0 : prevItem(dragSelFrom); } } } if (_dragItem != item) { // maybe exclude dragSelTo if (selectingDown) { if (m.y() < st::msgMargin.top()) { dragSelTo = (dragSelFrom == dragSelTo) ? 0 : prevItem(dragSelTo); } } else { if (m.y() >= dragSelTo->height() - st::msgMargin.bottom()) { dragSelTo = (dragSelFrom == dragSelTo) ? 0 : nextItem(dragSelTo); } } } bool dragSelecting = false; HistoryItem *dragFirstAffected = dragSelFrom; while (dragFirstAffected && (dragFirstAffected->id < 0 || dragFirstAffected->serviceMsg())) { dragFirstAffected = (dragFirstAffected == dragSelTo) ? 0 : (selectingDown ? nextItem(dragFirstAffected) : prevItem(dragFirstAffected)); } if (dragFirstAffected) { SelectedItems::const_iterator i = _selected.constFind(dragFirstAffected); dragSelecting = (i == _selected.cend() || i.value() != FullItemSel); } updateDragSelection(dragSelFrom, dragSelTo, dragSelecting); } } else if (_dragAction == Dragging) { } if (textlnkDown()) { cur = style::cur_pointer; } else if (_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { if (!_dragSelFrom || !_dragSelTo) { cur = style::cur_text; } } } if (_dragAction == Selecting) { historyWidget->checkSelectingScroll(mousePos); } else { updateDragSelection(0, 0, false); historyWidget->noSelectingScroll(); } if (lnkChanged || cur != _cursor) { setCursor(_cursor = cur); } } void HistoryList::updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting, bool force) { if (_dragSelFrom != dragSelFrom || _dragSelTo != dragSelTo || _dragSelecting != dragSelecting) { _dragSelFrom = dragSelFrom; _dragSelTo = dragSelTo; if (_dragSelFrom && _dragSelTo && _dragSelFrom->y + _dragSelFrom->block()->y > _dragSelTo->y + _dragSelTo->block()->y) { qSwap(_dragSelFrom, _dragSelTo); } _dragSelecting = dragSelecting; force = true; } if (!force) return; parentWidget()->update(); } void HistoryList::applyDragSelection() { if (!_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { _selected.clear(); } int32 fromy = _dragSelFrom->y + _dragSelFrom->block()->y, toy = _dragSelTo->y + _dragSelTo->block()->y + _dragSelTo->height(); if (_dragSelecting) { int32 fromblock = hist->indexOf(_dragSelFrom->block()), fromitem = _dragSelFrom->block()->indexOf(_dragSelFrom); int32 toblock = hist->indexOf(_dragSelTo->block()), toitem = _dragSelTo->block()->indexOf(_dragSelTo); if (fromblock >= 0 && fromitem >= 0 && toblock >= 0 && toitem >= 0) { for (; fromblock <= toblock; ++fromblock) { HistoryBlock *block = (*hist)[fromblock]; for (int32 cnt = (fromblock < toblock) ? block->size() : (toitem + 1); fromitem < cnt; ++fromitem) { HistoryItem *item = (*block)[fromitem]; SelectedItems::iterator i = _selected.find(item); if (item->id > 0 && !item->serviceMsg()) { if (i == _selected.cend()) { if (_selected.size() >= MaxSelectedItems) break; _selected.insert(item, FullItemSel); } else if (i.value() != FullItemSel) { *i = FullItemSel; } } else { if (i != _selected.cend()) { _selected.erase(i); } } } if (_selected.size() >= MaxSelectedItems) break; fromitem = 0; } } } else { for (SelectedItems::iterator i = _selected.begin(); i != _selected.cend(); ) { int32 iy = i.key()->y + i.key()->block()->y; if (iy >= fromy && iy < toy) { i = _selected.erase(i); } else { ++i; } } } _dragSelFrom = _dragSelTo = 0; } void HistoryList::showLinkTip() { TextLinkPtr lnk = textlnkOver(); if (lnk && !lnk->fullDisplayed()) { QToolTip::showText(_dragPos, lnk->readable(), App::wnd()); } } void HistoryList::onParentGeometryChanged() { bool needToUpdate = (_dragAction != NoDrag || _touchScroll || rect().contains(mapFromGlobal(QCursor::pos()))); if (needToUpdate) { dragActionUpdate(QCursor::pos()); } } MessageField::MessageField(HistoryWidget *history, const style::flatTextarea &st, const QString &ph, const QString &val) : FlatTextarea(history, st, ph, val), history(history) { connect(this, SIGNAL(changed()), this, SLOT(onChange())); } void MessageField::onChange() { int newh = ceil(document()->size().height()) + 2 * fakeMargin(), minh = st::btnSend.height - 2 * st::sendPadding; if (newh > st::maxFieldHeight) { newh = st::maxFieldHeight; } else if (newh < minh) { newh = minh; } if (height() != newh) { resize(width(), newh); emit resized(); } } void MessageField::onEmojiInsert(EmojiPtr emoji) { insertEmoji(emoji, textCursor()); } void MessageField::dropEvent(QDropEvent *e) { FlatTextarea::dropEvent(e); if (e->isAccepted()) { App::wnd()->activateWindow(); } } void MessageField::resizeEvent(QResizeEvent *e) { FlatTextarea::resizeEvent(e); onChange(); } bool MessageField::canInsertFromMimeData(const QMimeData *source) const { if (source->hasImage()) return true; return FlatTextarea::canInsertFromMimeData(source); } void MessageField::insertFromMimeData(const QMimeData *source) { if (source->hasImage()) { QImage img = qvariant_cast(source->imageData()); if (!img.isNull()) { history->uploadImage(img); return; } } return FlatTextarea::insertFromMimeData(source); } void MessageField::focusInEvent(QFocusEvent *e) { FlatTextarea::focusInEvent(e); emit focused(); } HistoryHider::HistoryHider(MainWidget *parent, bool forwardSelected) : QWidget(parent) , _sharedContact(0) , _forwardSelected(forwardSelected) , _sendPath(false) , forwardButton(this, lang(lng_forward), st::btnSelectDone) , cancelButton(this, lang(lng_cancel), st::btnSelectCancel) , offered(0) , aOpacity(0, 1) , aOpacityFunc(anim::easeOutCirc) , hiding(false) , _forwardRequest(0) , toTextWidth(0) , shadow(st::boxShadow) { init(); } HistoryHider::HistoryHider(MainWidget *parent, UserData *sharedContact) : QWidget(parent) , _sharedContact(sharedContact) , _forwardSelected(false) , _sendPath(false) , forwardButton(this, lang(lng_forward_send), st::btnSelectDone) , cancelButton(this, lang(lng_cancel), st::btnSelectCancel) , offered(0) , aOpacity(0, 1) , aOpacityFunc(anim::easeOutCirc) , hiding(false) , _forwardRequest(0) , toTextWidth(0) , shadow(st::boxShadow) { init(); } HistoryHider::HistoryHider(MainWidget *parent) : QWidget(parent) , _sharedContact(0) , _forwardSelected(false) , _sendPath(true) , forwardButton(this, lang(lng_forward_send), st::btnSelectDone) , cancelButton(this, lang(lng_cancel), st::btnSelectCancel) , offered(0) , aOpacity(0, 1) , aOpacityFunc(anim::easeOutCirc) , hiding(false) , _forwardRequest(0) , toTextWidth(0) , shadow(st::boxShadow) { init(); } void HistoryHider::init() { connect(&forwardButton, SIGNAL(clicked()), this, SLOT(forward())); connect(&cancelButton, SIGNAL(clicked()), this, SLOT(startHide())); connect(App::wnd()->getTitle(), SIGNAL(hiderClicked()), this, SLOT(startHide())); _chooseWidth = st::forwardFont->m.width(lang(lng_forward_choose)); resizeEvent(0); anim::start(this); } bool HistoryHider::animStep(float64 ms) { float64 dt = ms / 200; bool res = true; if (dt >= 1) { aOpacity.finish(); if (hiding) { QTimer::singleShot(0, this, SLOT(deleteLater())); } res = false; } else { aOpacity.update(dt, aOpacityFunc); } App::wnd()->getTitle()->setHideLevel(aOpacity.current()); forwardButton.setOpacity(aOpacity.current()); cancelButton.setOpacity(aOpacity.current()); update(); return res; } void HistoryHider::paintEvent(QPaintEvent *e) { QPainter p(this); if (!hiding || !cacheForAnim.isNull() || !offered) { p.setOpacity(aOpacity.current() * st::layerAlpha); p.fillRect(0, st::titleShadow, width(), height() - st::titleShadow, st::layerBG->b); p.setOpacity(aOpacity.current()); } if (cacheForAnim.isNull() || !offered) { p.setFont(st::forwardFont->f); if (offered) { shadow.paint(p, box); // fill bg p.fillRect(box, st::boxBG->b); // paint shadows p.fillRect(box.x(), box.y() + box.height() - st::btnSelectCancel.height - st::scrollDef.bottomsh, box.width(), st::scrollDef.bottomsh, st::scrollDef.shColor->b); // paint button sep p.fillRect(box.x() + st::btnSelectCancel.width, box.y() + box.height() - st::btnSelectCancel.height, st::lineWidth, st::btnSelectCancel.height, st::btnSelectSep->b); p.setPen(st::black->p); toText.drawElided(p, box.left() + (box.width() - toTextWidth) / 2, box.top() + st::boxPadding.top(), toTextWidth + 1); } else { p.setBrush(st::forwardBG->b); p.setPen(Qt::NoPen); int32 w = st::forwardMargins.left() + _chooseWidth + st::forwardMargins.right(), h = st::forwardMargins.top() + st::forwardFont->height + st::forwardMargins.bottom(); p.drawRoundedRect((width() - w) / 2, (height() - h) / 2, w, h, st::forwardRadius, st::forwardRadius); p.setPen(st::white->p); p.drawText(box, lang(lng_forward_choose), QTextOption(style::al_center)); } } else { p.drawPixmap(box.left(), box.top(), cacheForAnim); } } void HistoryHider::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { if (offered) { offered = 0; resizeEvent(0); update(); App::main()->dialogsActivate(); } else { startHide(); } } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { if (offered) { forward(); } } } void HistoryHider::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { if (!box.contains(e->pos())) { startHide(); } } } void HistoryHider::startHide() { if (hiding) return; hiding = true; if (cWideMode()) { if (offered) cacheForAnim = myGrab(this, box); if (_forwardRequest) MTP::cancel(_forwardRequest); aOpacity.start(0); anim::start(this); } else { QTimer::singleShot(0, this, SLOT(deleteLater())); } } void HistoryHider::forward() { if (!hiding && offered) { if (_sharedContact) { parent()->onShareContact(offered->id, _sharedContact); } else if (_sendPath) { parent()->onSendPaths(offered->id); } else { parent()->onForward(offered->id, _forwardSelected); } } } void HistoryHider::forwardDone() { _forwardRequest = 0; startHide(); } MainWidget *HistoryHider::parent() { return static_cast(parentWidget()); } void HistoryHider::resizeEvent(QResizeEvent *e) { int32 w = st::forwardWidth, h = st::boxPadding.top() + st::forwardFont->height + st::boxPadding.bottom(); if (offered) { forwardButton.show(); cancelButton.show(); h += forwardButton.height() + st::scrollDef.bottomsh; } else { forwardButton.hide(); cancelButton.hide(); } box = QRect((width() - w) / 2, (height() - h) / 2, w, h); cancelButton.move(box.x(), box.y() + h - cancelButton.height()); forwardButton.move(box.x() + box.width() - forwardButton.width(), cancelButton.y()); } bool HistoryHider::offerPeer(PeerId peer) { if (!peer) { offered = 0; toText.setText(st::boxFont, QString()); toTextWidth = 0; resizeEvent(0); return false; } offered = App::peer(peer); LangString phrase; QString recipient = offered->chat ? '\xAB' + offered->name + '\xBB' : offered->name; if (_sharedContact) { phrase = lng_forward_share_contact(lt_recipient, recipient); } else if (_sendPath) { if (cSendPaths().size() > 1) { phrase = lng_forward_send_files_confirm(lt_recipient, recipient); } else { QString name(QFileInfo(cSendPaths().front()).fileName()); if (name.size() > 10) { name = name.mid(0, 8) + '.' + '.'; } phrase = lng_forward_send_file_confirm(lt_name, name, lt_recipient, recipient); } } else { PeerId to = offered->id; offered = 0; parent()->onForward(to, _forwardSelected); startHide(); return false; } toText.setText(st::boxFont, phrase, _textNameOptions); toTextWidth = toText.maxWidth(); if (toTextWidth > box.width() - st::boxPadding.left() - st::boxPadding.right()) { toTextWidth = box.width() - st::boxPadding.left() - st::boxPadding.right(); } resizeEvent(0); update(); setFocus(); return true; } QString HistoryHider::offeredText() const { return toText.original(); } bool HistoryHider::wasOffered() const { return !!offered; } HistoryHider::~HistoryHider() { if (_sendPath) cSetSendPaths(QStringList()); if (App::wnd()) App::wnd()->getTitle()->setHideLevel(0); parent()->noHider(this); } HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent) , _replyToId(0) , _replyTo(0) , _replyToNameVersion(0) , _replyForwardCancel(this, st::replyCancel) , _replyReturn(0) , _lastStickersUpdate(0) , _stickersUpdateRequest(0) , _loadingMessages(false) , histRequestsCount(0) , histPeer(0) , _activeHist(0) , histPreloading(0) , _loadingAroundId(-1) , _loadingAroundRequest(0) , _scroll(this, st::historyScroll, false) , _list(0) , hist(0) , _histInited(false) , _toHistoryEnd(this, st::historyToEnd) , _attachMention(this) , _send(this, lang(lng_send_button), st::btnSend) , _attachDocument(this, st::btnAttachDocument) , _attachPhoto(this, st::btnAttachPhoto) , _attachEmoji(this, st::btnAttachEmoji) , _field(this, st::taMsgField, lang(lng_message_ph)) , _attachType(this) , _emojiPan(this) //, _stickerPan(this) , _attachDrag(DragStateNone) , _attachDragDocument(this) , _attachDragPhoto(this) , imageLoader(this) , _synthedTextUpdate(false) , serviceImageCacheSize(0) , confirmImageId(0) , confirmWithText(false) , titlePeerTextWidth(0) , hiderOffered(false) , _scrollDelta(0) , _typingRequest(0) , _saveDraftStart(0) , _saveDraftText(false) { _scroll.setFocusPolicy(Qt::NoFocus); setAcceptDrops(true); connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onListScroll())); connect(&_toHistoryEnd, SIGNAL(clicked()), this, SLOT(onHistoryToEnd())); connect(&_replyForwardCancel, SIGNAL(clicked()), this, SLOT(onReplyForwardCancel())); connect(&_send, SIGNAL(clicked()), this, SLOT(onSend())); connect(&_attachDocument, SIGNAL(clicked()), this, SLOT(onDocumentSelect())); connect(&_attachPhoto, SIGNAL(clicked()), this, SLOT(onPhotoSelect())); connect(&_field, SIGNAL(submitted(bool)), this, SLOT(onSend(bool))); connect(&_field, SIGNAL(cancelled()), this, SLOT(onCancel())); connect(&_field, SIGNAL(resized()), this, SLOT(onFieldResize())); connect(&_field, SIGNAL(focused()), this, SLOT(onFieldFocused())); connect(&imageLoader, SIGNAL(imageReady()), this, SLOT(onPhotoReady())); connect(&imageLoader, SIGNAL(imageFailed(quint64)), this, SLOT(onPhotoFailed(quint64))); connect(&_field, SIGNAL(changed()), this, SLOT(onTextChange())); connect(App::wnd()->windowHandle(), SIGNAL(visibleChanged(bool)), this, SLOT(onVisibleChanged())); connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); connect(&_emojiPan, SIGNAL(emojiSelected(EmojiPtr)), &_field, SLOT(onEmojiInsert(EmojiPtr))); connect(&_emojiPan, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*))); connect(&_emojiPan, SIGNAL(updateStickers()), this, SLOT(updateStickers())); // connect(&_stickerPan, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*))); connect(&_typingStopTimer, SIGNAL(timeout()), this, SLOT(cancelTyping())); _scrollTimer.setSingleShot(false); _typingStopTimer.setSingleShot(true); _animActiveTimer.setSingleShot(false); connect(&_animActiveTimer, SIGNAL(timeout()), this, SLOT(onAnimActiveStep())); _saveDraftTimer.setSingleShot(true); connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave())); connect(_field.verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed())); connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onFieldCursorChanged())); _replyForwardCancel.hide(); _scroll.hide(); _scroll.move(0, 0); updateScrollColors(); _toHistoryEnd.hide(); _attachMention.hide(); connect(&_attachMention, SIGNAL(chosen(QString)), &_field, SLOT(onMentionOrHashtagInsert(QString))); _field.installEventFilter(&_attachMention); _field.hide(); _field.resize(width() - _send.width() - _attachDocument.width() - _attachEmoji.width(), _send.height() - 2 * st::sendPadding); _send.hide(); _attachDocument.hide(); _attachPhoto.hide(); _attachEmoji.hide(); _attachDocument.installEventFilter(&_attachType); _attachPhoto.installEventFilter(&_attachType); _attachEmoji.installEventFilter(&_emojiPan); // _attachEmoji.installEventFilter(&_stickerPan); // _emojiPan.installEventFilter(&_stickerPan); // _stickerPan.installEventFilter(&_emojiPan); connect(_attachType.addButton(new IconedButton(this, st::dropdownAttachDocument, lang(lng_attach_file))), SIGNAL(clicked()), this, SLOT(onDocumentSelect())); connect(_attachType.addButton(new IconedButton(this, st::dropdownAttachPhoto, lang(lng_attach_photo))), SIGNAL(clicked()), this, SLOT(onPhotoSelect())); _attachType.hide(); _emojiPan.hide(); // _stickerPan.hide(); _attachDragDocument.hide(); _attachDragPhoto.hide(); connect(&_attachDragDocument, SIGNAL(dropped(QDropEvent*)), this, SLOT(onDocumentDrop(QDropEvent*))); connect(&_attachDragPhoto, SIGNAL(dropped(QDropEvent*)), this, SLOT(onPhotoDrop(QDropEvent*))); } void HistoryWidget::start() { updateRecentStickers(); connect(App::api(), SIGNAL(fullPeerLoaded(PeerData*)), this, SLOT(onPeerLoaded(PeerData*))); } void HistoryWidget::onTextChange() { updateTyping(); // updateStickerPan(); if (!hist || _synthedTextUpdate) return; _saveDraftText = true; onDraftSave(true); } void HistoryWidget::onDraftSaveDelayed() { // updateStickerPan(); if (!hist || _synthedTextUpdate) return; if (!_field.textCursor().anchor() && !_field.textCursor().position() && !_field.verticalScrollBar()->value()) { if (!Local::hasDraftPositions(hist->peer->id)) return; } onDraftSave(true); } void HistoryWidget::onDraftSave(bool delayed) { if (!hist) return; if (delayed) { uint64 ms = getms(); if (!_saveDraftStart) { _saveDraftStart = ms; return _saveDraftTimer.start(SaveDraftTimeout); } else if (ms - _saveDraftStart < SaveDraftAnywayTimeout) { return _saveDraftTimer.start(SaveDraftTimeout); } } writeDraft(); } void HistoryWidget::writeDraft(MsgId *replyTo, const QString *text, const MessageCursor *cursor) { bool save = hist && (_saveDraftStart > 0); _saveDraftStart = 0; _saveDraftTimer.stop(); if (_saveDraftText) { if (save) Local::writeDraft(hist->peer->id, Local::MessageDraft(replyTo ? (*replyTo) : _replyToId, text ? (*text) : _field.getText())); _saveDraftText = false; } if (save) Local::writeDraftPositions(hist->peer->id, cursor ? (*cursor) : MessageCursor(_field)); } void HistoryWidget::cancelTyping() { if (_typingRequest) { MTP::cancel(_typingRequest); _typingRequest = 0; } } void HistoryWidget::updateTyping(bool typing) { uint64 ms = getms(true) + 10000; if (_synthedTextUpdate || !hist || (typing && (hist->myTyping + 5000 > ms)) || (!typing && (hist->myTyping + 5000 <= ms))) return; hist->myTyping = typing ? ms : 0; cancelTyping(); if (typing) { _typingRequest = MTP::send(MTPmessages_SetTyping(histPeer->input, typing ? MTP_sendMessageTypingAction() : MTP_sendMessageCancelAction()), rpcDone(&HistoryWidget::typingDone)); _typingStopTimer.start(5000); } } //void HistoryWidget::updateStickerPan() { // EmojiPtr e = _field.getSingleEmoji(); // if (e) updateStickers(); // _stickerPan.setStickerPack(e, !_emojiPan.isHidden() && !_emojiPan.hiding()); //} void HistoryWidget::updateRecentStickers() { if (cEmojiTab() == dbietStickers) { _emojiPan.onTabChange(); } } void HistoryWidget::typingDone(const MTPBool &result, mtpRequestId req) { if (_typingRequest == req) { _typingRequest = 0; } } void HistoryWidget::activate() { if (hist && !_histInited) { checkUnreadLoaded(); } if (App::main()->selectingPeer()) { if (hiderOffered) { App::main()->focusPeerSelect(); return; } else { App::main()->dialogsActivate(); return; } } if (_list) { if (_selCount) { _list->setFocus(); } else { _field.setFocus(); } } } void HistoryWidget::updateStickers() { if (_lastStickersUpdate && getms(true) < _lastStickersUpdate + StickersUpdateTimeout) return; if (_stickersUpdateRequest) return; _stickersUpdateRequest = MTP::send(MTPmessages_GetAllStickers(MTP_string(cStickersHash())), rpcDone(&HistoryWidget::stickersGot), rpcFail(&HistoryWidget::stickersFailed)); } void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { _lastStickersUpdate = getms(true); _stickersUpdateRequest = 0; if (stickers.type() == mtpc_messages_allStickers) { const MTPDmessages_allStickers &d(stickers.c_messages_allStickers()); AllStickers all; EmojiStickersMap map; const QVector &docs(d.vdocuments.c_vector().v); QSet found; const RecentStickerPack &recent(cRecentStickers()); RecentStickerPack add; add.reserve(docs.size()); ushort addValue = recent.isEmpty() ? 1 : qAbs(recent.front().second); for (int32 i = 0, l = docs.size(); i < l; ++i) { DocumentData *doc = App::feedDocument(docs.at(i)); if (!doc) continue; int32 j = 0, s = recent.size(); for (; j < s; ++j) { if (doc == recent.at(j).first) { found.insert(doc); break; } } if (j < s) continue; add.push_back(qMakePair(doc, addValue)); } bool needRemove = false; for (int32 i = 0, l = recent.size(); i < l; ++i) { if (recent.at(i).second > 0 && !found.contains(recent.at(i).first)) { needRemove = true; break; } } if (!add.isEmpty() || needRemove) { if (needRemove) { for (int32 i = 0, l = recent.size(); i < l; ++i) { if (recent.at(i).second <= 0 || found.contains(recent.at(i).first)) { add.push_back(recent.at(i)); } } } else { add += recent; } cSetRecentStickers(add); Local::writeRecentStickers(); _emojiPan.onTabChange(); } const QVector &packs(d.vpacks.c_vector().v); for (int32 i = 0, l = packs.size(); i < l; ++i) { if (packs.at(i).type() == mtpc_stickerPack) { const MTPDstickerPack &p(packs.at(i).c_stickerPack()); QString emoticon(qs(p.vemoticon)); EmojiPtr e = 0; for (const QChar *ch = emoticon.constData(), *end = emoticon.constEnd(); ch != end; ++ch) { if (ch->isHighSurrogate()) { if (ch + 1 < end && (ch + 1)->isLowSurrogate()) { e = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode()); if (!e) { ++ch; } } } else { if (ch + 1 < end) { if (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3) { e = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode()); } else if ((ch + 1)->unicode() == 0xFE0F) { e = getEmoji(ch->unicode()); } } } if (e) break; } if (e) { const QVector docs(p.vdocuments.c_vector().v); if (!docs.isEmpty()) { StickerPack &pack(all[e]); pack.reserve(pack.size() + docs.size()); for (int32 j = 0, s = docs.size(); j < s; ++j) { DocumentData *doc = App::document(docs.at(j).v); pack.push_back(doc); map.insert(doc, e); } } } else { LOG(("Sticker Error: Could not find emoji for string: %1").arg(emoticon)); } } } cSetStickers(all); cSetStickersHash(qba(d.vhash)); cSetEmojiStickers(map); const DocumentItems &items(App::documentItems()); for (EmojiStickersMap::const_iterator i = map.cbegin(), e = map.cend(); i != e; ++i) { DocumentItems::const_iterator j = items.constFind(i.key()); if (j != items.cend()) { for (HistoryItemsMap::const_iterator k = j->cbegin(), end = j->cend(); k != end; ++k) { k.key()->updateStickerEmoji(); } } } // updateStickerPan(); _emojiPan.onTabChange(); } } bool HistoryWidget::stickersFailed(const RPCError &error) { _lastStickersUpdate = getms(true); _stickersUpdateRequest = 0; return true; } void HistoryWidget::clearLoadingAround() { _loadingAroundId = -1; if (_loadingAroundRequest) { MTP::cancel(_loadingAroundRequest); _loadingAroundRequest = 0; } } void HistoryWidget::clearReplyReturns() { _replyReturns.clear(); _replyReturn = 0; } void HistoryWidget::pushReplyReturn(HistoryItem *item) { if (!item) return; _replyReturn = item; _replyReturns.push_back(_replyReturn->id); updateControlsVisibility(); } QList HistoryWidget::replyReturns() { return _replyReturns; } void HistoryWidget::setReplyReturns(PeerId peer, const QList &replyReturns) { if (!histPeer || histPeer->id != peer) return; _replyReturns = replyReturns; _replyReturn = _replyReturns.isEmpty() ? 0 : App::histItemById(_replyReturns.back()); while (!_replyReturns.isEmpty() && !_replyReturn) { _replyReturns.pop_back(); _replyReturn = _replyReturns.isEmpty() ? 0 : App::histItemById(_replyReturns.back()); } updateControlsVisibility(); } void HistoryWidget::calcNextReplyReturn() { _replyReturn = 0; while (!_replyReturns.isEmpty() && !_replyReturn) { _replyReturns.pop_back(); _replyReturn = _replyReturns.isEmpty() ? 0 : App::histItemById(_replyReturns.back()); } if (!_replyReturn) updateControlsVisibility(); } void HistoryWidget::showPeer(const PeerId &peer, MsgId msgId, bool force, bool leaveActive) { if (App::main()->selectingPeer() && !force) { hiderOffered = true; App::main()->offerPeer(peer); return; } if (hist) { if (histPeer->id == peer) { if (msgId != hist->activeMsgId) { if (!force && !hist->canShowAround(msgId)) { if (_loadingAroundId != msgId) { clearLoadingAround(); _loadingAroundId = msgId; loadMessagesAround(); } return; } hist->loadAround(msgId); if (histPreloading) MTP::cancel(histPreloading); if (histPreloadingDown) MTP::cancel(histPreloadingDown); histPreloading = histPreloadingDown = 0; } if (_replyReturn && _replyReturn->id == msgId) calcNextReplyReturn(); if (hist->unreadBar) hist->unreadBar->destroy(); checkUnreadLoaded(); clearLoadingAround(); emit peerShown(histPeer); return App::wnd()->setInnerFocus(); } updateTyping(false); } stopGif(); clearLoadingAround(); clearReplyReturns(); if (_list) { if (!histPreload.isEmpty()) { _list->messagesReceived(histPreload); histPreload.clear(); } if (!histPreloadDown.isEmpty()) { _list->messagesReceivedDown(histPreloadDown); histPreloadDown.clear(); } } if (hist) { hist->draft = _field.getText(); hist->draftCursor.fillFrom(_field); hist->draftToId = _replyToId; writeDraft(&hist->draftToId, &hist->draft, &hist->draftCursor); if (hist->readyForWork() && _scroll.scrollTop() + 1 <= _scroll.scrollTopMax()) { hist->lastWidth = _list->width(); } else { hist->lastWidth = 0; } hist->lastScrollTop = _scroll.scrollTop(); if (hist->unreadBar) hist->unreadBar->destroy(); } if (_replyToId) { _replyTo = 0; _replyToId = 0; _replyForwardCancel.hide(); } _scroll.setWidget(0); if (_list) _list->deleteLater(); _list = 0; updateTopBarSelection(); if (leaveActive && hist) { _activeHist = hist; } else { if (!leaveActive) { _activeHist = 0; } if (hist) { App::main()->dlgUpdated(hist); } } histPeer = peer ? App::peer(peer) : 0; titlePeerText = QString(); titlePeerTextWidth = 0; histRequestsCount = 0; histPreload.clear(); histPreloadDown.clear(); if (histPreloading) MTP::cancel(histPreloading); if (histPreloadingDown) MTP::cancel(histPreloadingDown); histPreloading = histPreloadingDown = 0; hist = 0; _histInited = false; noSelectingScroll(); _selCount = 0; App::main()->topBar()->showSelected(0); App::hoveredItem(0); App::pressedItem(0); App::hoveredLinkItem(0); App::pressedLinkItem(0); App::contextItem(0); App::mousedItem(0); if (peer) { App::forgetMedia(); serviceImageCacheSize = imageCacheSize(); MTP::clearLoaderPriorities(); histInputPeer = histPeer->input; if (histInputPeer.type() == mtpc_inputPeerEmpty) { // maybe should load user } Histories::iterator i = App::histories().find(peer); if (i == App::histories().end()) { hist = new History(peer); i = App::histories().insert(peer, hist); } else { hist = i.value(); } if (hist->readyForWork()) { _scroll.show(); } if (hist) { App::main()->dlgUpdated(hist); } _list = new HistoryList(this, &_scroll, hist); hist->loadAround(msgId); _list->hide(); _scroll.setWidget(_list); _list->show(); checkUnreadLoaded(); App::main()->peerUpdated(histPeer); if (hist->draftToId > 0 || !hist->draft.isEmpty()) { setFieldText(hist->draft); _field.setFocus(); hist->draftCursor.applyTo(_field, &_synthedTextUpdate); _replyToId = App::main()->hasForwardingItems() ? 0 : hist->draftToId; } else { Local::MessageDraft draft = Local::readDraft(hist->peer->id); setFieldText(draft.text); _field.setFocus(); if (!draft.text.isEmpty()) { MessageCursor cur = Local::readDraftPositions(hist->peer->id); cur.applyTo(_field, &_synthedTextUpdate); } _replyToId = App::main()->hasForwardingItems() ? 0 : draft.replyTo; } if (_replyToId) { updateReplyTo(); if (!_replyTo) App::api()->requestReplyTo(0, _replyToId); resizeEvent(0); } connect(&_scroll, SIGNAL(geometryChanged()), _list, SLOT(onParentGeometryChanged())); connect(&_scroll, SIGNAL(scrolled()), _list, SLOT(onUpdateSelected())); } else { updateControlsVisibility(); } emit peerShown(histPeer); App::main()->topBar()->update(); update(); } void HistoryWidget::checkUnreadLoaded(bool checkOnlyShow) { if (!hist) return; if (hist->readyForWork()) { if (checkOnlyShow && !_scroll.isHidden()) return; if (!animating()) { if (_scroll.isHidden()) { _scroll.show(); if (!_field.isHidden()) update(); } } } else if (checkOnlyShow) { return; } updateListSize(0, true); if (!animating()) updateControlsVisibility(); if (hist->readyForWork()) { if (!_scroll.isHidden() && !_list->isHidden()) { onListScroll(); } } else { loadMessages(); } } void HistoryWidget::updateControlsVisibility() { if (!hist || animating()) { _scroll.hide(); _send.hide(); _toHistoryEnd.hide(); _attachMention.hide(); _field.hide(); _replyForwardCancel.hide(); _attachDocument.hide(); _attachPhoto.hide(); _attachEmoji.hide(); _attachType.hide(); _emojiPan.hide(); // _stickerPan.hide(); return; } if (hist->readyForWork()) { if (hist->loadedAtBottom() && !_replyReturn) { _toHistoryEnd.hide(); } else { _toHistoryEnd.show(); } if (!histPeer->chat || !histPeer->asChat()->forbidden) { _send.show(); if (cDefaultAttach() == dbidaPhoto) { _attachPhoto.show(); } else { _attachDocument.show(); } _attachEmoji.show(); if (_field.isHidden()) { _field.show(); } if ((_replyToId || App::main()->hasForwardingItems()) && _replyForwardCancel.isHidden()) { _replyForwardCancel.show(); resizeEvent(0); update(); } } else { _attachMention.hide(); _send.hide(); _attachDocument.hide(); _attachPhoto.hide(); _attachEmoji.hide(); _attachType.hide(); _emojiPan.hide(); // _stickerPan.hide(); if (!_field.isHidden()) { _field.hide(); resizeEvent(0); update(); } } if (hist->unreadCount && App::wnd()->historyIsActive()) { historyWasRead(); } } else { loadMessages(); if (!hist->readyForWork()) { _scroll.hide(); _attachMention.hide(); _send.hide(); _attachDocument.hide(); _attachPhoto.hide(); _attachEmoji.hide(); _attachType.hide(); _emojiPan.hide(); // _stickerPan.hide(); _toHistoryEnd.hide(); _replyForwardCancel.hide(); if (!_field.isHidden()) { _field.hide(); update(); } } } } void HistoryWidget::newUnreadMsg(History *history, HistoryItem *item) { if (App::wnd()->historyIsActive()) { if (hist == history && hist->readyForWork()) { historyWasRead(); if (_scroll.scrollTop() + 1 > _scroll.scrollTopMax()) { if (history->unreadBar) history->unreadBar->destroy(); } } else { if (hist != history) { App::wnd()->notifySchedule(history, item); } history->setUnreadCount(history->unreadCount + 1); } } else { if (hist == history && hist->readyForWork()) { if (_scroll.scrollTop() + 1 > _scroll.scrollTopMax()) { if (history->unreadBar) history->unreadBar->destroy(); } } App::wnd()->notifySchedule(history, item); history->setUnreadCount(history->unreadCount + 1); history->lastWidth = 0; } } void HistoryWidget::historyToDown(History *history) { history->lastScrollTop = History::ScrollMax; if (history == hist) { _scroll.scrollToY(_scroll.scrollTopMax()); } } void HistoryWidget::historyWasRead(bool force) { App::main()->readServerHistory(hist, force); } bool HistoryWidget::messagesFailed(const RPCError &e, mtpRequestId requestId) { LOG(("RPC Error: %1 %2: %3").arg(e.code()).arg(e.type()).arg(e.description())); if (histPreloading == requestId) { histPreloading = 0; } else if (histPreloadingDown == requestId) { histPreloadingDown = 0; } else if (_loadingAroundRequest == requestId) { _loadingAroundRequest = 0; } return true; } void HistoryWidget::messagesReceived(const MTPmessages_Messages &messages, mtpRequestId requestId) { if (!hist) { histPreloading = histPreloadingDown = _loadingAroundRequest = 0; return; } PeerId peer = 0; int32 count = 0; const QVector *histList = 0; switch (messages.type()) { case mtpc_messages_messages: { const MTPDmessages_messages &data(messages.c_messages_messages()); App::feedUsers(data.vusers); App::feedChats(data.vchats); histList = &data.vmessages.c_vector().v; count = histList->size(); } break; case mtpc_messages_messagesSlice: { const MTPDmessages_messagesSlice &data(messages.c_messages_messagesSlice()); App::feedUsers(data.vusers); App::feedChats(data.vchats); histList = &data.vmessages.c_vector().v; count = data.vcount.v; } break; } if (histList && !histList->isEmpty()) { const MTPmessage &msg(histList->front()); PeerId from_id(0), to_id(0); switch (msg.type()) { case mtpc_message: from_id = App::peerFromUser(msg.c_message().vfrom_id); to_id = App::peerFromMTP(msg.c_message().vto_id); break; case mtpc_messageService: from_id = App::peerFromUser(msg.c_messageService().vfrom_id); to_id = App::peerFromMTP(msg.c_messageService().vto_id); break; } peer = (to_id == App::peerFromUser(MTP::authedId())) ? from_id : to_id; } bool down = false; if (histPreloading == requestId) { histPreloading = 0; } else if (histPreloadingDown == requestId) { histPreloadingDown = 0; down = true; } else { if (_loadingAroundRequest == requestId) { _loadingAroundRequest = 0; hist->loadAround(_loadingAroundId); if (hist->isEmpty()) { addMessagesToFront(*histList); } if (histPreloading) MTP::cancel(histPreloading); if (histPreloadingDown) MTP::cancel(histPreloadingDown); histPreloading = histPreloadingDown = 0; showPeer(hist->peer->id, _loadingAroundId, true); } return; } if (peer && peer != histPeer->id) return; if (histList) { if (!hist->minMsgId() || histList->isEmpty()) { if (down) { addMessagesToBack(*histList); histPreloadDown.clear(); } else { addMessagesToFront(*histList); histPreload.clear(); } } else { if (down) { histPreloadDown = *histList; } else { histPreload = *histList; } } } else { if (down) { addMessagesToBack(QVector()); } else { addMessagesToFront(QVector()); } if (!hist->readyForWork()) { if (hist->activeMsgId) { hist->activeMsgId = 0; } if (!hist->readyForWork()) { hist->setUnreadCount(hist->msgCount); } } checkUnreadLoaded(true); return; } if (down && hist->loadedAtBottom() && histPreloadDown.size()) { addMessagesToBack(histPreloadDown); histPreloadDown.clear(); loadMessagesDown(); } else if (!down && hist->loadedAtTop() && histPreload.size()) { addMessagesToFront(histPreload); histPreload.clear(); loadMessages(); } else if ((down && histPreloadDown.size()) || (!down && histPreload.size())) { onListScroll(); } else if (down) { loadMessagesDown(); } else { loadMessages(); } } void HistoryWidget::windowShown() { if (hist && !_histInited) { checkUnreadLoaded(); } resizeEvent(0); } bool HistoryWidget::isActive() const { return !hist || hist->loadedAtBottom(); } void HistoryWidget::loadMessages() { if (!hist || _loadingMessages) return; if (hist->loadedAtTop()) { if (!hist->readyForWork()) { if (hist->activeMsgId) { hist->activeMsgId = 0; } if (!hist->readyForWork()) { hist->setUnreadCount(hist->msgCount); } } checkUnreadLoaded(true); return; } _loadingMessages = true; if (histPreload.size()) { bool loaded = hist->readyForWork(); addMessagesToFront(histPreload); histPreload.clear(); checkUnreadLoaded(true); if (!loaded && hist->readyForWork()) { _loadingMessages = false; return; } } if (!histPreloading && (!hist->readyForWork() || _scroll.scrollTop() < PreloadHeightsCount * _scroll.height())) { MsgId min = hist->minMsgId(); int32 offset = 0, loadCount = min ? MessagesPerPage : MessagesFirstLoad; if (!min && hist->activeMsgId) { min = hist->activeMsgId; offset = -loadCount / 2; } histPreloading = MTP::send(MTPmessages_GetHistory(histInputPeer, MTP_int(offset), MTP_int(min), MTP_int(loadCount)), rpcDone(&HistoryWidget::messagesReceived), rpcFail(&HistoryWidget::messagesFailed)); ++histRequestsCount; if (!hist->readyForWork()) update(); } else { checkUnreadLoaded(true); } _loadingMessages = false; } void HistoryWidget::loadMessagesDown() { if (!hist) return; if (hist->loadedAtBottom()) { return; } int32 dh = 0; if (histPreloadDown.size()) { bool loaded = hist->readyForWork(); addMessagesToBack(histPreloadDown); histPreloadDown.clear(); checkUnreadLoaded(true); if (!loaded && hist->readyForWork()) { return; } } if (!histPreloadingDown && hist->readyForWork() && (_scroll.scrollTop() + PreloadHeightsCount * _scroll.height() > _scroll.scrollTopMax())) { MsgId max = hist->maxMsgId(); if (max) { int32 loadCount = MessagesPerPage, offset = -loadCount; histPreloadingDown = MTP::send(MTPmessages_GetHistory(histInputPeer, MTP_int(offset), MTP_int(max + 1), MTP_int(loadCount)), rpcDone(&HistoryWidget::messagesReceived), rpcFail(&HistoryWidget::messagesFailed)); ++histRequestsCount; if (!hist->readyForWork()) update(); } } else { checkUnreadLoaded(true); } } void HistoryWidget::loadMessagesAround() { if (!hist || _loadingAroundRequest || _loadingAroundId < 0) return; int32 offset = 0, loadCount = MessagesPerPage; if (_loadingAroundId) { offset = -loadCount / 2; } _loadingAroundRequest = MTP::send(MTPmessages_GetHistory(histInputPeer, MTP_int(offset), MTP_int(_loadingAroundId), MTP_int(loadCount)), rpcDone(&HistoryWidget::messagesReceived), rpcFail(&HistoryWidget::messagesFailed)); } void HistoryWidget::onListScroll() { App::checkImageCacheSize(); if (histPreloading || !hist || ((_list->isHidden() || _scroll.isHidden() || !App::wnd()->windowHandle()->isVisible()) && hist->readyForWork())) { checkUnreadLoaded(true); return; } int st = _scroll.scrollTop(), stm = _scroll.scrollTopMax(), sh = _scroll.height(); if (hist->readyForWork() && (st + PreloadHeightsCount * sh > stm)) { loadMessagesDown(); } if (!hist->readyForWork() || st < PreloadHeightsCount * sh) { loadMessages(); } else { checkUnreadLoaded(true); } while (_replyReturn) { bool below = (_replyReturn->detached() && !hist->isEmpty() && _replyReturn->id < hist->back()->back()->id); if (!below && !_replyReturn->detached()) below = (st >= stm) || (_replyReturn->y + _replyReturn->block()->y < st + sh / 2); if (below) { calcNextReplyReturn(); } else { break; } } } void HistoryWidget::onVisibleChanged() { QTimer::singleShot(0, this, SLOT(onListScroll())); } QString HistoryWidget::prepareMessage(QString result) { result = result.replace('\t', qsl(" ")); result = result.replace(" --", QString::fromUtf8(" \xe2\x80\x94")); result = result.replace("-- ", QString::fromUtf8("\xe2\x80\x94 ")); result = result.replace("<<", QString::fromUtf8("\xc2\xab")); result = result.replace(">>", QString::fromUtf8("\xc2\xbb")); return (cReplaceEmojis() ? replaceEmojis(result) : result).trimmed(); } void HistoryWidget::onHistoryToEnd() { if (_replyReturn) { showPeer(histPeer->id, _replyReturn->id); } else { _toHistoryEnd.hide(); if (hist && !hist->loadedAtBottom()) { showPeer(histPeer->id, 0); } } } void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) { if (!hist) return; QString text = prepareMessage(_field.getText()); if (!text.isEmpty()) { App::main()->readServerHistory(hist, false); hist->loadAround(0); App::main()->sendPreparedText(hist, text, replyTo); setFieldText(QString()); _saveDraftText = true; _saveDraftStart = getms(); onDraftSave(); if (!_attachMention.isHidden()) _attachMention.hideStart(); if (!_attachType.isHidden()) _attachType.hideStart(); if (!_emojiPan.isHidden()) _emojiPan.hideStart(); // if (!_stickerPan.isHidden()) _stickerPan.hideStart(); } else if (App::main()->hasForwardingItems()) { App::main()->readServerHistory(hist, false); hist->loadAround(0); App::main()->finishForwarding(hist); } if (replyTo < 0) cancelReply(); _field.setFocus(); } void HistoryWidget::onShareContact(const PeerId &peer, UserData *contact) { if (!contact || contact->phone.isEmpty()) return; App::main()->showPeer(peer, 0, false, true); if (!hist) return; shareContact(peer, contact->phone, contact->firstName, contact->lastName, _replyToId, int32(contact->id & 0xFFFFFFFF)); } void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, int32 userId) { History *h = App::history(peer); App::main()->readServerHistory(h, false); uint64 randomId = MTP::nonce(); MsgId newId = clientMsgId(); h->loadAround(0); PeerData *p = App::peer(peer); int32 flags = (p->input.type() == mtpc_inputPeerSelf) ? 0 : (MTPDmessage_flag_unread | MTPDmessage_flag_out); // unread, out if (replyTo) flags |= MTPDmessage::flag_reply_to_msg_id; h->addToBack(MTP_message(MTP_int(flags), MTP_int(newId), MTP_int(MTP::authedId()), App::peerToMTP(peer), MTPint(), MTPint(), MTP_int(_replyToId), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname), MTP_int(userId)))); h->sendRequestId = MTP::send(MTPmessages_SendMedia(p->input, MTP_int(replyTo), MTP_inputMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname)), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentFullDataReceived, randomId), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); App::historyRegRandom(randomId, newId); if (hist && histPeer && peer == histPeer->id) { App::main()->historyToDown(hist); } App::main()->dialogsToUp(); peerMessagesUpdated(peer); App::main()->finishForwarding(h); cancelReply(); } void HistoryWidget::onSendPaths(const PeerId &peer) { App::main()->showPeer(peer, 0, false, true); if (!hist) return; uploadMedias(cSendPaths(), ToPrepareDocument); } PeerData *HistoryWidget::peer() const { return histPeer; } PeerData *HistoryWidget::activePeer() const { return histPeer ? histPeer : (_activeHist ? _activeHist->peer : 0); } MsgId HistoryWidget::activeMsgId() const { return hist ? hist->activeMsgId : (_activeHist ? _activeHist->activeMsgId : 0); } int32 HistoryWidget::lastWidth() const { return width(); } int32 HistoryWidget::lastScrollTop() const { return _scroll.scrollTop(); } void HistoryWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back) { _bgAnimCache = bgAnimCache; _bgAnimTopBarCache = bgAnimTopBarCache; _animCache = myGrab(this, rect()); App::main()->topBar()->stopAnim(); _animTopBarCache = myGrab(App::main()->topBar(), QRect(0, 0, width(), st::topBarHeight)); App::main()->topBar()->startAnim(); _scroll.hide(); _toHistoryEnd.hide(); _attachDocument.hide(); _attachPhoto.hide(); _attachEmoji.hide(); _field.hide(); _replyForwardCancel.hide(); _send.hide(); a_coord = back ? anim::ivalue(-st::introSlideShift, 0) : anim::ivalue(st::introSlideShift, 0); a_alpha = anim::fvalue(0, 1); a_bgCoord = back ? anim::ivalue(0, st::introSlideShift) : anim::ivalue(0, -st::introSlideShift); a_bgAlpha = anim::fvalue(1, 0); anim::start(this); App::main()->topBar()->update(); } bool HistoryWidget::animStep(float64 ms) { float64 fullDuration = st::introSlideDelta + st::introSlideDuration, dt = ms / fullDuration; float64 dt1 = (ms > st::introSlideDuration) ? 1 : (ms / st::introSlideDuration), dt2 = (ms > st::introSlideDelta) ? (ms - st::introSlideDelta) / (st::introSlideDuration) : 0; bool res = true; if (dt2 >= 1) { anim::stop(this); res = false; a_bgCoord.finish(); a_bgAlpha.finish(); a_coord.finish(); a_alpha.finish(); _bgAnimCache = _animCache = _animTopBarCache = _bgAnimTopBarCache = QPixmap(); App::main()->topBar()->stopAnim(); App::main()->topBar()->enableShadow(); updateControlsVisibility(); if (hist && hist->readyForWork()) { _scroll.show(); if (hist->lastScrollTop == History::ScrollMax) { _scroll.scrollToY(hist->lastScrollTop); } onListScroll(); } App::wnd()->setInnerFocus(); } else { a_bgCoord.update(dt1, st::introHideFunc); a_bgAlpha.update(dt1, st::introAlphaHideFunc); a_coord.update(dt2, st::introShowFunc); a_alpha.update(dt2, st::introAlphaShowFunc); } update(); App::main()->topBar()->update(); return res; } void HistoryWidget::animStop() { if (!animating()) return; anim::stop(this); } void HistoryWidget::onPhotoSelect() { if (!hist) return; _attachDocument.clearState(); _attachDocument.hide(); _attachPhoto.show(); _attachType.fastHide(); if (cDefaultAttach() != dbidaPhoto) { cSetDefaultAttach(dbidaPhoto); Local::writeUserSettings(); } QStringList photoExtensions(cPhotoExtensions()); QStringList imgExtensions(cImgExtensions()); QString filter(qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;Photo files (*") + photoExtensions.join(qsl(" *")) + qsl(");;All files (*.*)")); QStringList files; QByteArray file; if (filedialogGetOpenFiles(files, file, lang(lng_choose_images), filter)) { if (!file.isEmpty()) { uploadMedia(file, ToPreparePhoto); } else if (!files.isEmpty()) { uploadMedias(files, ToPreparePhoto); } } } void HistoryWidget::onDocumentSelect() { if (!hist) return; _attachPhoto.clearState(); _attachPhoto.hide(); _attachDocument.show(); _attachType.fastHide(); if (cDefaultAttach() != dbidaDocument) { cSetDefaultAttach(dbidaDocument); Local::writeUserSettings(); } QStringList photoExtensions(cPhotoExtensions()); QStringList imgExtensions(cImgExtensions()); QString filter(qsl("All files (*.*);;Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;Photo files (*") + photoExtensions.join(qsl(" *")) + qsl(")")); QStringList files; QByteArray file; if (filedialogGetOpenFiles(files, file, lang(lng_choose_images), filter)) { if (!file.isEmpty()) { uploadMedia(file, ToPrepareDocument); } else if (!files.isEmpty()) { uploadMedias(files, ToPrepareDocument); } } } void HistoryWidget::dragEnterEvent(QDragEnterEvent *e) { if (!hist) return; _attachDrag = getDragState(e->mimeData()); updateDragAreas(); if (_attachDrag) { e->setDropAction(Qt::IgnoreAction); e->accept(); } } void HistoryWidget::dragLeaveEvent(QDragLeaveEvent *e) { if (_attachDrag != DragStateNone || !_attachDragPhoto.isHidden() || !_attachDragDocument.isHidden()) { _attachDrag = DragStateNone; updateDragAreas(); } } void HistoryWidget::leaveEvent(QEvent *e) { if (_attachDrag != DragStateNone || !_attachDragPhoto.isHidden() || !_attachDragDocument.isHidden()) { _attachDrag = DragStateNone; updateDragAreas(); } } void HistoryWidget::mouseReleaseEvent(QMouseEvent *e) { if (_attachDrag != DragStateNone || !_attachDragPhoto.isHidden() || !_attachDragDocument.isHidden()) { _attachDrag = DragStateNone; updateDragAreas(); } } DragState HistoryWidget::getDragState(const QMimeData *d) { if (!d) return DragStateNone; if (d->hasImage()) return DragStateImage; QString uriListFormat(qsl("text/uri-list")); if (!d->hasFormat(uriListFormat)) return DragStateNone; QStringList imgExtensions(cImgExtensions()), files; const QList &urls(d->urls()); if (urls.isEmpty()) return DragStateNone; bool allAreSmallImages = true; for (QList::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) { if (!i->isLocalFile()) return DragStateNone; QString file(i->toLocalFile()); if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file); quint64 s = QFileInfo(file).size(); if (s >= MaxUploadDocumentSize) { return DragStateNone; } if (allAreSmallImages) { if (s >= MaxUploadPhotoSize) { allAreSmallImages = false; } else { bool foundImageExtension = false; for (QStringList::const_iterator j = imgExtensions.cbegin(), end = imgExtensions.cend(); j != end; ++j) { if (file.right(j->size()).toLower() == (*j).toLower()) { foundImageExtension = true; break; } } if (!foundImageExtension) { allAreSmallImages = false; } } } } return allAreSmallImages ? DragStatePhotoFiles : DragStateFiles; } void HistoryWidget::updateDragAreas() { _field.setAcceptDrops(!_attachDrag); switch (_attachDrag) { case DragStateNone: _attachDragDocument.otherLeave(); _attachDragPhoto.otherLeave(); break; case DragStateFiles: _attachDragDocument.otherEnter(); _attachDragDocument.setText(lang(lng_drag_files_here), lang(lng_drag_to_send_files)); _attachDragPhoto.fastHide(); break; case DragStatePhotoFiles: _attachDragDocument.otherEnter(); _attachDragDocument.setText(lang(lng_drag_images_here), lang(lng_drag_to_send_no_compression)); _attachDragPhoto.otherEnter(); _attachDragPhoto.setText(lang(lng_drag_photos_here), lang(lng_drag_to_send_quick)); break; case DragStateImage: _attachDragDocument.fastHide(); _attachDragPhoto.otherEnter(); _attachDragPhoto.setText(lang(lng_drag_images_here), lang(lng_drag_to_send_quick)); break; }; resizeEvent(0); } void HistoryWidget::dropEvent(QDropEvent *e) { _attachDrag = DragStateNone; updateDragAreas(); e->acceptProposedAction(); } void HistoryWidget::onDocumentDrop(QDropEvent *e) { if (!hist) return; QStringList files = getMediasFromMime(e->mimeData()); if (files.isEmpty()) return; uploadMedias(files, ToPrepareDocument); } void HistoryWidget::onPhotoDrop(QDropEvent *e) { if (!hist) return; if (e->mimeData()->hasImage()) { QImage image = qvariant_cast(e->mimeData()->imageData()); if (image.isNull()) return; uploadImage(image); } else { QStringList files = getMediasFromMime(e->mimeData()); if (files.isEmpty()) return; uploadMedias(files, ToPreparePhoto); } } void HistoryWidget::contextMenuEvent(QContextMenuEvent *e) { if (!_list) return; return _list->showContextMenu(e); } void HistoryWidget::deleteMessage() { HistoryItem *item = App::contextItem(); if (!item || item->itemType() != HistoryItem::MsgType) return; HistoryMessage *msg = dynamic_cast(item); App::main()->deleteLayer((msg && msg->uploading()) ? -2 : -1); } void HistoryWidget::forwardMessage() { HistoryItem *item = App::contextItem(); if (!item || item->itemType() != HistoryItem::MsgType) return; App::main()->forwardLayer(); } void HistoryWidget::selectMessage() { HistoryItem *item = App::contextItem(); if (!item || item->itemType() != HistoryItem::MsgType) return; if (_list) _list->selectItem(item); } void HistoryWidget::paintTopBar(QPainter &p, float64 over, int32 decreaseWidth) { if (animating()) { p.setOpacity(a_bgAlpha.current()); p.drawPixmap(a_bgCoord.current(), 0, _bgAnimTopBarCache); p.setOpacity(a_alpha.current()); p.drawPixmap(a_coord.current(), 0, _animTopBarCache); return; } if (!hist) return; int32 increaseLeft = cWideMode() ? 0 : (st::topBarForwardPadding.right() - st::topBarForwardPadding.left()); decreaseWidth += increaseLeft; QRect rectForName(st::topBarForwardPadding.left() + increaseLeft, st::topBarForwardPadding.top(), width() - decreaseWidth - st::topBarForwardPadding.left() - st::topBarForwardPadding.right(), st::msgNameFont->height); p.setFont(st::dlgHistFont->f); if (hist->typing.isEmpty()) { p.setPen(st::titleStatusColor->p); p.drawText(rectForName.x(), st::topBarHeight - st::topBarForwardPadding.bottom() - st::dlgHistFont->height + st::dlgHistFont->ascent, titlePeerText); } else { p.setPen(st::titleTypingColor->p); hist->typingText.drawElided(p, rectForName.x(), st::topBarHeight - st::topBarForwardPadding.bottom() - st::dlgHistFont->height, rectForName.width()); } p.setPen(st::dlgNameColor->p); hist->nameText.drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); if (cWideMode()) { p.setOpacity(st::topBarForwardAlpha + (1 - st::topBarForwardAlpha) * over); p.drawPixmap(QPoint(width() - (st::topBarForwardPadding.right() + st::topBarForwardImg.pxWidth()) / 2, (st::topBarHeight - st::topBarForwardImg.pxHeight()) / 2), App::sprite(), st::topBarForwardImg); } else { p.setOpacity(st::topBarForwardAlpha + (1 - st::topBarForwardAlpha) * over); p.drawPixmap(QPoint((st::topBarForwardPadding.right() - st::topBarBackwardImg.pxWidth()) / 2, (st::topBarHeight - st::topBarBackwardImg.pxHeight()) / 2), App::sprite(), st::topBarBackwardImg); } } void HistoryWidget::topBarShadowParams(int32 &x, float64 &o) { if (animating() && a_coord.current() >= 0) { x = a_coord.current(); o = a_alpha.current(); } } void HistoryWidget::topBarClick() { if (cWideMode()) { if (hist) App::main()->showPeerProfile(histPeer); } else { App::main()->onShowDialogs(); } } void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) { if (!hist) return; QString text; int32 t = unixtime(); if (histPeer->chat) { ChatData *chat = histPeer->asChat(); if (chat->forbidden) { text = lang(lng_chat_status_unaccessible); } else if (chat->participants.isEmpty()) { text = titlePeerText.isEmpty() ? lng_chat_status_members(lt_count, chat->count < 0 ? 0 : chat->count) : titlePeerText; } else { int32 onlineCount = 0; bool onlyMe = true; for (ChatData::Participants::const_iterator i = chat->participants.cbegin(), e = chat->participants.cend(); i != e; ++i) { if (i.key()->onlineTill > t) { ++onlineCount; if (onlyMe && i.key() != App::self()) onlyMe = false; } } if (onlineCount && !onlyMe) { text = lng_chat_status_members_online(lt_count, chat->participants.size(), lt_count_online, onlineCount); } else { text = lng_chat_status_members(lt_count, chat->participants.size()); } } } else { text = App::onlineText(histPeer->asUser(), t); } if (titlePeerText != text) { titlePeerText = text; titlePeerTextWidth = st::dlgHistFont->m.width(titlePeerText); if (App::main()) { App::main()->topBar()->update(); } } updateOnlineDisplayTimer(); } void HistoryWidget::updateOnlineDisplayTimer() { if (!hist) return; int32 t = unixtime(), minIn = 86400; if (histPeer->chat) { ChatData *chat = histPeer->asChat(); if (chat->participants.isEmpty()) return; for (ChatData::Participants::const_iterator i = chat->participants.cbegin(), e = chat->participants.cend(); i != e; ++i) { int32 onlineWillChangeIn = App::onlineWillChangeIn(i.key()->onlineTill, t); if (onlineWillChangeIn < minIn) { minIn = onlineWillChangeIn; } } } else { minIn = App::onlineWillChangeIn(histPeer->asUser()->onlineTill, t); } App::main()->updateOnlineDisplayIn(minIn * 1000); } void HistoryWidget::onFieldResize() { _field.move(_attachDocument.x() + _attachDocument.width(), height() - _field.height() - st::sendPadding); _replyForwardCancel.move(width() - _replyForwardCancel.width(), _field.y() - st::sendPadding - _replyForwardCancel.height()); updateListSize(); int backy = _scroll.y() + _scroll.height(); update(0, backy, width(), height() - backy); } void HistoryWidget::onFieldFocused() { if (_list) _list->clearSelectedItems(true); } void HistoryWidget::checkMentionDropdown() { if (!hist) return; QString start; _field.getMentionHashtagStart(start); if (!start.isEmpty()) { if (start.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtags(); if (start.at(0) == '@' && !hist->peer->chat) return; _attachMention.showFiltered(hist->peer->asChat(), start); } else if (!_attachMention.isHidden()) { _attachMention.hideStart(); } } void HistoryWidget::onFieldCursorChanged() { checkMentionDropdown(); onDraftSaveDelayed(); } void HistoryWidget::uploadImage(const QImage &img, bool withText) { if (!hist || confirmImageId) return; App::wnd()->activateWindow(); confirmImage = img; confirmWithText = withText; confirmImageId = imageLoader.append(img, histPeer->id, _replyToId, ToPreparePhoto); } void HistoryWidget::uploadFile(const QString &file, bool withText) { if (!hist || confirmImageId) return; App::wnd()->activateWindow(); confirmWithText = withText; confirmImageId = imageLoader.append(file, histPeer->id, _replyToId, ToPrepareDocument); } void HistoryWidget::shareContactConfirmation(const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, bool withText) { if (!hist || confirmImageId) return; App::wnd()->activateWindow(); confirmWithText = withText; confirmImageId = 0xFFFFFFFFFFFFFFFFL; App::wnd()->showLayer(new PhotoSendBox(phone, fname, lname, replyTo)); } void HistoryWidget::uploadConfirmImageUncompressed(bool ctrlShiftEnter, MsgId replyTo) { if (!hist || !confirmImageId || confirmImage.isNull()) return; App::wnd()->activateWindow(); PeerId peerId = histPeer->id; if (confirmWithText) { onSend(ctrlShiftEnter, replyTo); } imageLoader.append(confirmImage, peerId, replyTo, ToPrepareDocument, ctrlShiftEnter); confirmImageId = 0; confirmWithText = false; confirmImage = QImage(); cancelReply(); } void HistoryWidget::uploadMedias(const QStringList &files, ToPrepareMediaType type) { if (!hist) return; App::wnd()->activateWindow(); imageLoader.append(files, histPeer->id, _replyToId, type); cancelReply(); } void HistoryWidget::uploadMedia(const QByteArray &fileContent, ToPrepareMediaType type, PeerId peer) { if (!peer && !hist) return; App::wnd()->activateWindow(); imageLoader.append(fileContent, peer ? peer : histPeer->id, _replyToId, type); cancelReply(); } void HistoryWidget::onPhotoReady() { QMutexLocker lock(imageLoader.readyMutex()); ReadyLocalMedias &list(imageLoader.readyList()); for (ReadyLocalMedias::const_iterator i = list.cbegin(), e = list.cend(); i != e; ++i) { if (i->id == confirmImageId) { App::wnd()->showLayer(new PhotoSendBox(*i)); } else { confirmSendImage(*i); } } list.clear(); } void HistoryWidget::onPhotoFailed(quint64 id) { } void HistoryWidget::confirmShareContact(bool ctrlShiftEnter, const QString &phone, const QString &fname, const QString &lname, MsgId replyTo) { if (!histPeer) return; PeerId peerId = histPeer->id; if (0xFFFFFFFFFFFFFFFFL == confirmImageId) { if (confirmWithText) { onSend(ctrlShiftEnter, replyTo); } confirmImageId = 0; confirmWithText = false; confirmImage = QImage(); } shareContact(peerId, phone, fname, lname, replyTo); } void HistoryWidget::confirmSendImage(const ReadyLocalMedia &img) { if (img.id == confirmImageId) { if (confirmWithText) { onSend(img.ctrlShiftEnter, img.replyTo); } confirmImageId = 0; confirmWithText = false; confirmImage = QImage(); } MsgId newId = clientMsgId(); connect(App::uploader(), SIGNAL(photoReady(MsgId, const MTPInputFile &)), this, SLOT(onPhotoUploaded(MsgId, const MTPInputFile &)), Qt::UniqueConnection); connect(App::uploader(), SIGNAL(documentReady(MsgId, const MTPInputFile &)), this, SLOT(onDocumentUploaded(MsgId, const MTPInputFile &)), Qt::UniqueConnection); connect(App::uploader(), SIGNAL(thumbDocumentReady(MsgId, const MTPInputFile &, const MTPInputFile &)), this, SLOT(onThumbDocumentUploaded(MsgId, const MTPInputFile &, const MTPInputFile &)), Qt::UniqueConnection); // connect(App::uploader(), SIGNAL(photoProgress(MsgId)), this, SLOT(onPhotoProgress(MsgId)), Qt::UniqueConnection); connect(App::uploader(), SIGNAL(documentProgress(MsgId)), this, SLOT(onDocumentProgress(MsgId)), Qt::UniqueConnection); // connect(App::uploader(), SIGNAL(photoFailed(MsgId)), this, SLOT(onPhotoFailed(MsgId)), Qt::UniqueConnection); connect(App::uploader(), SIGNAL(documentFailed(MsgId)), this, SLOT(onDocumentFailed(MsgId)), Qt::UniqueConnection); App::uploader()->uploadMedia(newId, img); History *h = App::history(img.peer); if (img.type == ToPreparePhoto) { h->loadAround(0); int32 flags = (h->peer->input.type() == mtpc_inputPeerSelf) ? 0 : (MTPDmessage_flag_unread | MTPDmessage_flag_out); // unread, out if (img.replyTo) flags |= MTPDmessage::flag_reply_to_msg_id; h->addToBack(MTP_message(MTP_int(flags), MTP_int(newId), MTP_int(MTP::authedId()), App::peerToMTP(img.peer), MTPint(), MTPint(), MTP_int(img.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaPhoto(img.photo))); } else if (img.type == ToPrepareDocument) { h->loadAround(0); int32 flags = (h->peer->input.type() == mtpc_inputPeerSelf) ? 0 : (MTPDmessage_flag_unread | MTPDmessage_flag_out); // unread, out if (img.replyTo) flags |= MTPDmessage::flag_reply_to_msg_id; h->addToBack(MTP_message(MTP_int(flags), MTP_int(newId), MTP_int(MTP::authedId()), App::peerToMTP(img.peer), MTPint(), MTPint(), MTP_int(img.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaDocument(img.document))); } if (hist && histPeer && img.peer == histPeer->id) { App::main()->historyToDown(hist); } App::main()->dialogsToUp(); peerMessagesUpdated(img.peer); } void HistoryWidget::cancelSendImage() { if (confirmImageId && confirmWithText) setFieldText(QString()); confirmImageId = 0; confirmWithText = false; confirmImage = QImage(); } void HistoryWidget::onPhotoUploaded(MsgId newId, const MTPInputFile &file) { if (!MTP::authedId()) return; HistoryItem *item = App::histItemById(newId); if (item) { //App::main()->readServerHistory(item->history(), false); uint64 randomId = MTP::nonce(); App::historyRegRandom(randomId, newId); History *hist = item->history(); MsgId replyTo = item->toHistoryReply() ? item->toHistoryReply()->replyToId() : 0; hist->sendRequestId = MTP::send(MTPmessages_SendMedia(item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedPhoto(file), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentFullDataReceived, randomId), App::main()->rpcFail(&MainWidget::sendPhotoFailed, randomId), 0, 0, hist->sendRequestId); } } namespace { MTPVector _composeDocumentAttributes(DocumentData *document) { QVector attributes(1, MTP_documentAttributeFilename(MTP_string(document->name))); if (document->dimensions.width() > 0 && document->dimensions.height() > 0) { attributes.push_back(MTP_documentAttributeImageSize(MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height()))); } if (document->type == AnimatedDocument) { attributes.push_back(MTP_documentAttributeAnimated()); } else if (document->type == StickerDocument) { attributes.push_back(MTP_documentAttributeSticker(MTP_string(document->alt))); } return MTP_vector(attributes); } } void HistoryWidget::onDocumentUploaded(MsgId newId, const MTPInputFile &file) { if (!MTP::authedId()) return; HistoryMessage *item = dynamic_cast(App::histItemById(newId)); if (item) { DocumentData *document = 0; if (HistoryDocument *media = dynamic_cast(item->getMedia())) { document = media->document(); } else if (HistorySticker *media = dynamic_cast(item->getMedia())) { document = media->document(); } if (document) { //App::main()->readServerHistory(item->history(), false); uint64 randomId = MTP::nonce(); App::historyRegRandom(randomId, newId); History *hist = item->history(); MsgId replyTo = item->toHistoryReply() ? item->toHistoryReply()->replyToId() : 0; hist->sendRequestId = MTP::send(MTPmessages_SendMedia(item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedDocument(file, MTP_string(document->mime), _composeDocumentAttributes(document)), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentFullDataReceived, randomId), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); } } } void HistoryWidget::onThumbDocumentUploaded(MsgId newId, const MTPInputFile &file, const MTPInputFile &thumb) { if (!MTP::authedId()) return; HistoryMessage *item = dynamic_cast(App::histItemById(newId)); if (item) { DocumentData *document = 0; if (HistoryDocument *media = dynamic_cast(item->getMedia())) { document = media->document(); } else if (HistorySticker *media = dynamic_cast(item->getMedia())) { document = media->document(); } if (document) { //App::main()->readServerHistory(item->history(), false); uint64 randomId = MTP::nonce(); App::historyRegRandom(randomId, newId); History *hist = item->history(); MsgId replyTo = item->toHistoryReply() ? item->toHistoryReply()->replyToId() : 0; hist->sendRequestId = MTP::send(MTPmessages_SendMedia(item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedThumbDocument(file, thumb, MTP_string(document->mime), _composeDocumentAttributes(document)), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentFullDataReceived, randomId), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); } } } void HistoryWidget::onDocumentProgress(MsgId newId) { if (!MTP::authedId()) return; HistoryItem *item = App::histItemById(newId); if (item) { msgUpdated(item->history()->peer->id, item); } } void HistoryWidget::onDocumentFailed(MsgId newId) { if (!MTP::authedId()) return; HistoryItem *item = App::histItemById(newId); if (item) { msgUpdated(item->history()->peer->id, item); } } void HistoryWidget::peerMessagesUpdated(PeerId peer) { if (histPeer && _list && peer == histPeer->id) { updateListSize(); } } void HistoryWidget::peerMessagesUpdated() { if (_list) updateListSize(); } void HistoryWidget::msgUpdated(PeerId peer, const HistoryItem *msg) { if (histPeer && _list && peer == histPeer->id) { _list->updateMsg(msg); } } void HistoryWidget::resizeEvent(QResizeEvent *e) { _attachDocument.move(0, height() - _attachDocument.height()); _attachPhoto.move(_attachDocument.x(), _attachDocument.y()); _field.move(_attachDocument.x() + _attachDocument.width(), height() - _field.height() - st::sendPadding); _replyForwardCancel.move(width() - _replyForwardCancel.width(), _field.y() - st::sendPadding - _replyForwardCancel.height()); updateListSize(); _field.resize(width() - _send.width() - _attachDocument.width() - _attachEmoji.width(), _field.height()); _toHistoryEnd.move((width() - _toHistoryEnd.width()) / 2, _scroll.y() + _scroll.height() - _toHistoryEnd.height() - st::historyToEndSkip); _attachEmoji.move(_field.x() + _field.width(), height() - _attachEmoji.height()); _send.move(width() - _send.width(), _attachDocument.y()); _attachType.move(0, _attachDocument.y() - _attachType.height()); _emojiPan.move(width() - _emojiPan.width(), _attachEmoji.y() - _emojiPan.height()); // _stickerPan.move(width() - _emojiPan.width() - _stickerPan.width() + st::dropdownPadding.left(), _attachEmoji.y() - _stickerPan.height()); switch (_attachDrag) { case DragStateFiles: _attachDragDocument.resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom()); _attachDragDocument.move(st::dragMargin.left(), st::dragMargin.top()); break; case DragStatePhotoFiles: _attachDragDocument.resize(width() - st::dragMargin.left() - st::dragMargin.right(), (height() - st::dragMargin.top() - st::dragMargin.bottom()) / 2); _attachDragDocument.move(st::dragMargin.left(), st::dragMargin.top()); _attachDragPhoto.resize(_attachDragDocument.width(), _attachDragDocument.height()); _attachDragPhoto.move(st::dragMargin.left(), height() - _attachDragPhoto.height() - st::dragMargin.bottom()); break; case DragStateImage: _attachDragPhoto.resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom()); _attachDragPhoto.move(st::dragMargin.left(), st::dragMargin.top()); break; } } void HistoryWidget::itemRemoved(HistoryItem *item) { if (_list) _list->itemRemoved(item); if (item == _replyTo) { cancelReply(); } if (item == _replyReturn) { calcNextReplyReturn(); } } void HistoryWidget::itemReplaced(HistoryItem *oldItem, HistoryItem *newItem) { if (_list) _list->itemReplaced(oldItem, newItem); if (_replyTo == oldItem) _replyTo = newItem; if (_replyReturn == oldItem) _replyReturn = newItem; } void HistoryWidget::itemResized(HistoryItem *row) { updateListSize(0, false, false, row); } void HistoryWidget::updateScrollColors() { if (!App::historyScrollBarColor()) return; _scroll.updateColors(App::historyScrollBarColor(), App::historyScrollBgColor(), App::historyScrollBarOverColor(), App::historyScrollBgOverColor()); } MsgId HistoryWidget::replyToId() const { return _replyToId; } void HistoryWidget::updateListSize(int32 addToY, bool initial, bool loadedDown, HistoryItem *resizedItem) { if (!hist || (!_histInited && !initial)) return; if (!isVisible()) { if (initial) _histInited = false; return; // scrollTopMax etc are not working after recountHeight() } int32 newScrollHeight = height() - (hist->readyForWork() && (!histPeer->chat || !histPeer->asChat()->forbidden) ? (_field.height() + 2 * st::sendPadding) : 0); if (_replyToId || App::main()->hasForwardingItems()) { newScrollHeight -= st::replyHeight; } bool wasAtBottom = _scroll.scrollTop() + 1 > _scroll.scrollTopMax(), needResize = _scroll.width() != width() || _scroll.height() != newScrollHeight; if (needResize) { _scroll.resize(width(), newScrollHeight); _attachMention.setBoundings(_scroll.geometry()); _toHistoryEnd.move((width() - _toHistoryEnd.width()) / 2, _scroll.y() + _scroll.height() - _toHistoryEnd.height() - st::historyToEndSkip); } if (!initial) { hist->lastScrollTop = _scroll.scrollTop(); } int32 newSt = _list->recountHeight(!!resizedItem); bool washidden = _scroll.isHidden(); if (washidden) { _scroll.show(); } _list->updateSize(); if (resizedItem && !resizedItem->detached()) { int32 firstItemY = _list->height() - hist->height - st::historyPadding; if (newSt + _scroll.height() < firstItemY + resizedItem->block()->y + resizedItem->y + resizedItem->height()) { newSt = firstItemY + resizedItem->block()->y + resizedItem->y + resizedItem->height() - _scroll.height(); } if (newSt > firstItemY + resizedItem->block()->y + resizedItem->y) { newSt = firstItemY + resizedItem->block()->y + resizedItem->y; } wasAtBottom = false; } if (washidden) { _scroll.hide(); } if (!hist->readyForWork()) return; if ((!initial && !wasAtBottom) || loadedDown) { _scroll.scrollToY(newSt + addToY); return; } if (initial) { _histInited = true; } int32 toY = History::ScrollMax; if (initial && hist->activeMsgId && !hist->lastWidth) { HistoryItem *item = App::histItemById(hist->activeMsgId); if (!item || item->detached()) { hist->activeMsgId = 0; return updateListSize(addToY, initial); } else { toY = (_scroll.height() > item->height()) ? qMax(item->y + item->block()->y - (_scroll.height() - item->height()) / 2, 0) : (item->y + item->block()->y); _animActiveStart = getms(); _animActiveTimer.start(AnimationTimerDelta); } } else if (initial && hist->unreadBar) { toY = hist->unreadBar->y + hist->unreadBar->block()->y; } else if (hist->showFrom) { toY = hist->showFrom->y + hist->showFrom->block()->y; if (toY < _scroll.scrollTopMax() + st::unreadBarHeight) { hist->addUnreadBar(); if (hist->unreadBar) { hist->activeMsgId = 0; return updateListSize(0, true); } } } else if (initial && hist->lastWidth) { toY = newSt; hist->lastWidth = 0; } else { } _scroll.scrollToY(toY); } void HistoryWidget::addMessagesToFront(const QVector &messages) { int32 oldH = hist->height; _list->messagesReceived(messages); updateListSize(hist->height - oldH); checkUnreadLoaded(true); } void HistoryWidget::addMessagesToBack(const QVector &messages) { int32 sliceFrom = 0; _list->messagesReceivedDown(messages); updateListSize(0, false, true); checkUnreadLoaded(true); } void HistoryWidget::mousePressEvent(QMouseEvent *e) { } void HistoryWidget::keyPressEvent(QKeyEvent *e) { if (!hist) return; if (e->key() == Qt::Key_Escape) { e->ignore(); } else if (e->key() == Qt::Key_Back) { onCancel(); } else if (e->key() == Qt::Key_PageDown) { if ((e->modifiers() & Qt::ControlModifier) || (e->modifiers() & Qt::MetaModifier)) { PeerData *after = 0; MsgId afterMsgId = 0; App::main()->peerAfter(histPeer, hist ? hist->activeMsgId : 0, after, afterMsgId); if (after) App::main()->showPeer(after->id, afterMsgId); } else { _scroll.keyPressEvent(e); } } else if (e->key() == Qt::Key_PageUp) { if ((e->modifiers() & Qt::ControlModifier) || (e->modifiers() & Qt::MetaModifier)) { PeerData *before = 0; MsgId beforeMsgId = 0; App::main()->peerBefore(histPeer, hist ? hist->activeMsgId : 0, before, beforeMsgId); if (before) App::main()->showPeer(before->id, beforeMsgId); } else { _scroll.keyPressEvent(e); } } else if (e->key() == Qt::Key_Down) { if (e->modifiers() & Qt::AltModifier) { PeerData *after = 0; MsgId afterMsgId = 0; App::main()->peerAfter(histPeer, hist ? hist->activeMsgId : 0, after, afterMsgId); if (after) App::main()->showPeer(after->id, afterMsgId); } else if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) { _scroll.keyPressEvent(e); } } else if (e->key() == Qt::Key_Up) { if (e->modifiers() & Qt::AltModifier) { PeerData *before = 0; MsgId beforeMsgId = 0; App::main()->peerBefore(histPeer, hist ? hist->activeMsgId : 0, before, beforeMsgId); if (before) App::main()->showPeer(before->id, beforeMsgId); } else if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) { _scroll.keyPressEvent(e); } } else if ((e->key() == Qt::Key_Tab || e->key() == Qt::Key_Backtab) && ((e->modifiers() & Qt::ControlModifier) || (e->modifiers() & Qt::MetaModifier))) { PeerData *p = 0; MsgId m = 0; if ((e->modifiers() & Qt::ShiftModifier) || e->key() == Qt::Key_Backtab) { App::main()->peerBefore(histPeer, hist ? hist->activeMsgId : 0, p, m); } else { App::main()->peerAfter(histPeer, hist ? hist->activeMsgId : 0, p, m); } if (p) App::main()->showPeer(p->id, m); } else { e->ignore(); } } void HistoryWidget::onStickerSend(DocumentData *sticker) { if (!hist || !sticker) return; App::main()->readServerHistory(hist, false); uint64 randomId = MTP::nonce(); MsgId newId = clientMsgId(); hist->loadAround(0); bool out = (histPeer->input.type() != mtpc_inputPeerSelf), unread = (histPeer->input.type() != mtpc_inputPeerSelf); int32 flags = (histPeer->input.type() != mtpc_inputPeerSelf) ? (MTPDmessage_flag_out | MTPDmessage_flag_unread) : 0; if (_replyToId) flags |= MTPDmessage::flag_reply_to_msg_id; hist->addToBackDocument(newId, flags, _replyToId, date(MTP_int(unixtime())), MTP::authedId(), sticker); hist->sendRequestId = MTP::send(MTPmessages_SendMedia(histPeer->input, MTP_int(_replyToId), MTP_inputMediaDocument(MTP_inputDocument(MTP_long(sticker->id), MTP_long(sticker->access))), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentFullDataReceived, randomId), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); App::main()->finishForwarding(hist); cancelReply(); App::historyRegRandom(randomId, newId); App::main()->historyToDown(hist); App::main()->dialogsToUp(); peerMessagesUpdated(histPeer->id); if (!_attachMention.isHidden()) _attachMention.hideStart(); if (!_attachType.isHidden()) _attachType.hideStart(); if (!_emojiPan.isHidden()) _emojiPan.hideStart(); // if (!_stickerPan.isHidden()) _stickerPan.hideStart(); // _field.removeSingleEmoji(); _field.setFocus(); } void HistoryWidget::setFieldText(const QString &text) { _synthedTextUpdate = true; _field.setPlainText(text); _synthedTextUpdate = false; } void HistoryWidget::onReplyToMessage() { HistoryItem *to = App::contextItem(); if (!to || to->id <= 0) return; App::main()->cancelForwarding(); _replyTo = to; _replyToId = to->id; _replyToText.setText(st::msgFont, _replyTo->inDialogsText(), _textDlgOptions); if (!_field.isHidden()) _replyForwardCancel.show(); updateReplyToName(); resizeEvent(0); update(); _saveDraftText = true; _saveDraftStart = getms(); onDraftSave(); _field.setFocus(); } void HistoryWidget::cancelReply() { if (!_replyToId) return; _replyTo = 0; _replyToId = 0; if (!App::main()->hasForwardingItems()) _replyForwardCancel.hide(); resizeEvent(0); update(); _saveDraftText = true; _saveDraftStart = getms(); onDraftSave(); } void HistoryWidget::cancelForwarding() { _replyForwardCancel.hide(); resizeEvent(0); update(); } void HistoryWidget::onReplyForwardCancel() { App::main()->cancelForwarding(); cancelReply(); } void HistoryWidget::onCancel() { if (App::main()) App::main()->showPeer(0); emit cancelled(); } void HistoryWidget::onPeerLoaded(PeerData *data) { peerUpdated(data); if (data == histPeer) { checkMentionDropdown(); } } void HistoryWidget::peerUpdated(PeerData *data) { if (data && data == histPeer) { updateListSize(); if (!animating()) updateControlsVisibility(); if (data->chat && data->asChat()->count > 0 && data->asChat()->participants.isEmpty()) { App::api()->requestFullPeer(data); } App::main()->updateOnlineDisplay(); } } void HistoryWidget::onForwardSelected() { if (!_list) return; App::main()->forwardLayer(true); } void HistoryWidget::onDeleteSelected() { if (!_list) return; SelectedItemSet sel; _list->fillSelectedItems(sel); if (sel.isEmpty()) return; App::main()->deleteLayer(sel.size()); } void HistoryWidget::onDeleteSelectedSure() { if (!_list) return; SelectedItemSet sel; _list->fillSelectedItems(sel); if (sel.isEmpty()) return; QVector ids; for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) { if (i.value()->id > 0) { ids.push_back(MTP_int(i.value()->id)); } } if (!ids.isEmpty()) { App::main()->deleteMessages(ids); } onClearSelected(); for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) { i.value()->destroy(); } if (App::main() && App::main()->peer() == peer()) { App::main()->itemResized(0); } App::wnd()->hideLayer(); } void HistoryWidget::onDeleteContextSure() { HistoryItem *item = App::contextItem(); if (!item || item->itemType() != HistoryItem::MsgType) { return; } if (item->id > 0) { App::main()->deleteMessages(QVector(1, MTP_int(item->id))); } item->destroy(); if (App::main() && App::main()->peer() == peer()) { App::main()->itemResized(0); } App::wnd()->hideLayer(); } void HistoryWidget::onClearSelected() { if (_list) _list->clearSelectedItems(); } void HistoryWidget::onAnimActiveStep() { if (!hist || !hist->activeMsgId) return _animActiveTimer.stop(); HistoryItem *item = App::histItemById(hist->activeMsgId); if (!item || item->detached()) return _animActiveTimer.stop(); App::main()->msgUpdated(histPeer->id, item); } uint64 HistoryWidget::animActiveTime() const { return _animActiveTimer.isActive() ? (getms() - _animActiveStart) : 0; } void HistoryWidget::stopAnimActive() { _animActiveTimer.stop(); } void HistoryWidget::fillSelectedItems(SelectedItemSet &sel, bool forDelete) { if (_list) _list->fillSelectedItems(sel, forDelete); } void HistoryWidget::updateTopBarSelection() { if (!_list) { App::main()->topBar()->showSelected(0); return; } int32 selectedForForward, selectedForDelete; _list->getSelectionState(selectedForForward, selectedForDelete); _selCount = selectedForDelete ? selectedForDelete : selectedForForward; App::main()->topBar()->showSelected(_selCount > 0 ? _selCount : 0); updateControlsVisibility(); updateListSize(); if (!App::wnd()->layerShown()) { if (_selCount) { _list->setFocus(); } else { _field.setFocus(); } } App::main()->topBar()->update(); update(); } void HistoryWidget::updateReplyTo(bool force) { if (!_replyToId || _replyTo) return; _replyTo = App::histItemById(_replyToId); if (_replyTo) { _replyToText.setText(st::msgFont, _replyTo->inDialogsText(), _textDlgOptions); if (!_field.isHidden()) _replyForwardCancel.show(); updateReplyToName(); int backy = _scroll.y() + _scroll.height(); update(0, backy, width(), height() - backy); } else if (force) { cancelReply(); } } void HistoryWidget::updateForwarding(bool force) { if (App::main()->hasForwardingItems()) { _replyForwardCancel.show(); } resizeEvent(0); update(); } void HistoryWidget::updateReplyToName() { if (!_replyTo) return; _replyToName.setText(st::msgServiceNameFont, App::peerName(_replyTo->from()), _textNameOptions); _replyToNameVersion = _replyTo->from()->nameVersion; } void HistoryWidget::drawFieldBackground(QPainter &p) { int32 backy = _field.y() - st::sendPadding, backh = _field.height() + 2 * st::sendPadding; Text *from = 0, *text = 0; bool serviceColor = false; ImagePtr preview; if (_replyToId) { if (_replyTo && _replyTo->from()->nameVersion > _replyToNameVersion) { updateReplyToName(); } backy -= st::replyHeight; backh += st::replyHeight; } else if (App::main()->hasForwardingItems()) { App::main()->fillForwardingInfo(from, text, serviceColor, preview); backy -= st::replyHeight; backh += st::replyHeight; } p.fillRect(0, backy, width(), backh, st::taMsgField.bgColor->b); if (_replyToId) { int32 replyLeft = st::replySkip; p.drawPixmap(QPoint(st::replyIconPos.x(), backy + st::replyIconPos.y()), App::sprite(), st::replyIcon); if (_replyTo) { if (_replyTo->getMedia() && _replyTo->getMedia()->hasReplyPreview()) { ImagePtr replyPreview = _replyTo->getMedia()->replyPreview(); if (!replyPreview->isNull()) { QRect to(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); if (replyPreview->width() == replyPreview->height()) { p.drawPixmap(to.x(), to.y(), replyPreview->pix()); } else { QRect from = (replyPreview->width() > replyPreview->height()) ? QRect((replyPreview->width() - replyPreview->height()) / 2, 0, replyPreview->height(), replyPreview->height()) : QRect(0, (replyPreview->height() - replyPreview->width()) / 2, replyPreview->width(), replyPreview->width()); p.drawPixmap(to, replyPreview->pix(), from); } } replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); } p.setPen(st::replyColor->p); _replyToName.drawElided(p, replyLeft, backy + st::msgReplyPadding.top(), width() - replyLeft - _replyForwardCancel.width() - st::msgReplyPadding.right()); p.setPen(((_replyTo->getMedia() || _replyTo->serviceMsg()) ? st::msgInDateColor : st::msgColor)->p); _replyToText.drawElided(p, replyLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - replyLeft - _replyForwardCancel.width() - st::msgReplyPadding.right()); } else { p.setFont(st::msgDateFont->f); p.setPen(st::msgInDateColor->p); p.drawText(replyLeft, backy + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->m.elidedText(lang(lng_profile_loading), Qt::ElideRight, width() - replyLeft - _replyForwardCancel.width() - st::msgReplyPadding.right())); } } else if (from && text) { int32 forwardLeft = st::replySkip; p.drawPixmap(QPoint(st::replyIconPos.x(), backy + st::replyIconPos.y()), App::sprite(), st::forwardIcon); if (!preview->isNull()) { QRect to(forwardLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); if (preview->width() == preview->height()) { p.drawPixmap(to.x(), to.y(), preview->pix()); } else { QRect from = (preview->width() > preview->height()) ? QRect((preview->width() - preview->height()) / 2, 0, preview->height(), preview->height()) : QRect(0, (preview->height() - preview->width()) / 2, preview->width(), preview->width()); p.drawPixmap(to, preview->pix(), from); } forwardLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); } p.setPen(st::replyColor->p); from->drawElided(p, forwardLeft, backy + st::msgReplyPadding.top(), width() - forwardLeft - _replyForwardCancel.width() - st::msgReplyPadding.right()); p.setPen((serviceColor ? st::msgInDateColor : st::msgColor)->p); text->drawElided(p, forwardLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - forwardLeft - _replyForwardCancel.width() - st::msgReplyPadding.right()); } } void HistoryWidget::paintEvent(QPaintEvent *e) { QPainter p(this); QRect r(e->rect()); if (r != rect()) { p.setClipRect(r); } if (animating()) { p.setOpacity(a_bgAlpha.current()); p.drawPixmap(a_bgCoord.current(), 0, _bgAnimCache); p.setOpacity(a_alpha.current()); p.drawPixmap(a_coord.current(), 0, _animCache); return; } bool hasTopBar = !App::main()->topBar()->isHidden(); QRect fill(0, 0, width(), App::main()->height()); int fromy = hasTopBar ? (-st::topBarHeight) : 0, x = 0, y = 0; QPixmap cached = App::main()->cachedBackground(fill, x, y); if (cached.isNull()) { const QPixmap &pix(*cChatBackground()); if (cTileBackground()) { int left = r.left(), top = r.top(), right = r.left() + r.width(), bottom = r.top() + r.height(); float64 w = pix.width() / cRetinaFactor(), h = pix.height() / cRetinaFactor(); int sx = qFloor(left / w), sy = qFloor((top - fromy) / h), cx = qCeil(right / w), cy = qCeil((bottom - fromy) / h); for (int i = sx; i < cx; ++i) { for (int j = sy; j < cy; ++j) { p.drawPixmap(QPointF(i * w, fromy + j * h), pix); } } } else { bool smooth = p.renderHints().testFlag(QPainter::SmoothPixmapTransform); p.setRenderHint(QPainter::SmoothPixmapTransform); QRect to, from; App::main()->backgroundParams(fill, to, from); to.moveTop(to.top() + fromy); p.drawPixmap(to, pix, from); if (!smooth) p.setRenderHint(QPainter::SmoothPixmapTransform, false); } } else { p.drawPixmap(x, fromy + y, cached); } if (_list) { if (!_scroll.isHidden()) { if (!_field.isHidden()) { drawFieldBackground(p); } } else { QPoint dogPos((width() - st::msgDogImg.pxWidth()) / 2, ((height() - _field.height() - 2 * st::sendPadding - st::msgDogImg.pxHeight()) * 4) / 9); p.drawPixmap(dogPos, *cChatDogImage()); int32 pointsCount = 8, w = pointsCount * (st::introPointWidth + 2 * st::introPointDelta), h = st::introPointHeight; int32 pointsLeft = (width() - w) / 2 + st::introPointDelta - st::introPointLeft, pointsTop = dogPos.y() + (st::msgDogImg.pxHeight() * 6) / 5; int32 curPoint = histRequestsCount % pointsCount; p.fillRect(pointsLeft + curPoint * (st::introPointWidth + 2 * st::introPointDelta), pointsTop, st::introPointHoverWidth, st::introPointHoverHeight, App::introPointHoverColor()->b); // points p.setOpacity(st::introPointAlpha); int32 x = pointsLeft + st::introPointLeft; for (int32 i = 0; i < pointsCount; ++i) { p.fillRect(x, pointsTop + st::introPointTop, st::introPointWidth, st::introPointHeight, st::introPointColor->b); x += (st::introPointWidth + 2 * st::introPointDelta); } } } else { style::font font(st::msgServiceFont); int32 w = font->m.width(lang(lng_willbe_history)) + st::msgPadding.left() + st::msgPadding.right(), h = font->height + st::msgServicePadding.top() + st::msgServicePadding.bottom() + 2; QRect tr((width() - w) / 2, (height() - _field.height() - 2 * st::sendPadding - h) / 2, w, h); p.setPen(Qt::NoPen); p.setBrush(App::msgServiceBG()->b); p.drawRoundedRect(tr, st::msgServiceRadius, st::msgServiceRadius); p.setPen(st::msgServiceColor->p); p.setFont(font->f); p.drawText(tr.left() + st::msgPadding.left(), tr.top() + st::msgServicePadding.top() + 1 + font->ascent, lang(lng_willbe_history)); } } QRect HistoryWidget::historyRect() const { return _scroll.geometry(); } void HistoryWidget::destroyData() { showPeer(0); } QStringList HistoryWidget::getMediasFromMime(const QMimeData *d) { QString uriListFormat(qsl("text/uri-list")); QStringList photoExtensions(cPhotoExtensions()), files; if (!d->hasFormat(uriListFormat)) return QStringList(); const QList &urls(d->urls()); if (urls.isEmpty()) return QStringList(); files.reserve(urls.size()); for (QList::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) { if (!i->isLocalFile()) return QStringList(); QString file(i->toLocalFile()); if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file); QFileInfo info(file); uint64 s = info.size(); if (s >= MaxUploadDocumentSize) { if (s >= MaxUploadPhotoSize) { continue; } else { bool foundGoodExtension = false; for (QStringList::const_iterator j = photoExtensions.cbegin(), end = photoExtensions.cend(); j != end; ++j) { if (file.right(j->size()).toLower() == (*j).toLower()) { foundGoodExtension = true; } } if (!foundGoodExtension) { continue; } } } files.push_back(file); } return files; } QPoint HistoryWidget::clampMousePosition(QPoint point) { if (point.x() < 0) { point.setX(0); } else if (point.x() >= _scroll.width()) { point.setX(_scroll.width() - 1); } if (point.y() < _scroll.scrollTop()) { point.setY(_scroll.scrollTop()); } else if (point.y() >= _scroll.scrollTop() + _scroll.height()) { point.setY(_scroll.scrollTop() + _scroll.height() - 1); } return point; } void HistoryWidget::onScrollTimer() { int32 d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed)); _scroll.scrollToY(_scroll.scrollTop() + d); } void HistoryWidget::checkSelectingScroll(QPoint point) { if (point.y() < _scroll.scrollTop()) { _scrollDelta = point.y() - _scroll.scrollTop(); } else if (point.y() >= _scroll.scrollTop() + _scroll.height()) { _scrollDelta = point.y() - _scroll.scrollTop() - _scroll.height() + 1; } else { _scrollDelta = 0; } if (_scrollDelta) { _scrollTimer.start(15); } else { _scrollTimer.stop(); } } void HistoryWidget::noSelectingScroll() { _scrollTimer.stop(); } bool HistoryWidget::touchScroll(const QPoint &delta) { int32 scTop = _scroll.scrollTop(), scMax = _scroll.scrollTopMax(), scNew = snap(scTop - delta.y(), 0, scMax); if (scNew == scTop) return false; _scroll.scrollToY(scNew); return true; } HistoryWidget::~HistoryWidget() { delete _list; }