| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022 |
- // This file is part of Desktop App Toolkit,
- // a set of libraries for developing nice desktop applications.
- //
- // For license and copyright information please follow this link:
- // https://github.com/desktop-app/legal/blob/master/LEGAL
- //
- #include "ui/widgets/labels.h"
- #include "base/invoke_queued.h"
- #include "ui/text/text_entity.h"
- #include "ui/effects/animation_value.h"
- #include "ui/widgets/popup_menu.h"
- #include "ui/widgets/box_content_divider.h"
- #include "ui/basic_click_handlers.h" // UrlClickHandler
- #include "ui/inactive_press.h"
- #include "ui/painter.h"
- #include "ui/qt_weak_factory.h"
- #include "ui/integration.h"
- #include "ui/ui_utility.h"
- #include "base/qt/qt_common_adapters.h"
- #include "styles/style_layers.h"
- #include "styles/palette.h"
- #include <QtWidgets/QApplication>
- #include <QtGui/QClipboard>
- #include <QtGui/QDrag>
- #include <QtGui/QtEvents>
- #include <QtCore/QMimeData>
- namespace Ui {
- namespace {
- TextParseOptions _labelOptions = {
- TextParseMultiline, // flags
- 0, // maxw
- 0, // maxh
- Qt::LayoutDirectionAuto, // dir
- };
- TextParseOptions _labelMarkedOptions = {
- TextParseMultiline | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMarkdown, // flags
- 0, // maxw
- 0, // maxh
- Qt::LayoutDirectionAuto, // dir
- };
- } // namespace
- CrossFadeAnimation::CrossFadeAnimation(
- style::color bg,
- Data &&was,
- Data &&now)
- : _bg(bg) {
- const auto maxLines = qMax(was.lineWidths.size(), now.lineWidths.size());
- auto fillDataTill = [&](Data &data) {
- for (auto i = data.lineWidths.size(); i != maxLines; ++i) {
- data.lineWidths.push_back(-1);
- }
- };
- fillDataTill(was);
- fillDataTill(now);
- auto preparePart = [](const Data &data, int index, const Data &other) {
- auto result = CrossFadeAnimation::Part();
- auto lineWidth = data.lineWidths[index];
- if (lineWidth < 0) {
- lineWidth = other.lineWidths[index];
- }
- const auto pixelRatio = style::DevicePixelRatio();
- auto fullWidth = data.full.width() / pixelRatio;
- auto top = index * data.lineHeight + data.lineAddTop;
- auto left = 0;
- if (data.align & Qt::AlignHCenter) {
- left += (fullWidth - lineWidth) / 2;
- } else if (data.align & Qt::AlignRight) {
- left += (fullWidth - lineWidth);
- }
- auto snapshotRect = data.full.rect().intersected(QRect(left * pixelRatio, top * pixelRatio, lineWidth * pixelRatio, data.font->height * pixelRatio));
- if (!snapshotRect.isEmpty()) {
- result.snapshot = PixmapFromImage(data.full.copy(snapshotRect));
- result.snapshot.setDevicePixelRatio(pixelRatio);
- }
- result.position = data.position + QPoint(data.margin.left() + left, data.margin.top() + top);
- return result;
- };
- for (int i = 0; i != maxLines; ++i) {
- addLine(preparePart(was, i, now), preparePart(now, i, was));
- }
- }
- void CrossFadeAnimation::addLine(Part was, Part now) {
- _lines.push_back(Line(std::move(was), std::move(now)));
- }
- void CrossFadeAnimation::paintFrame(QPainter &p, float64 dt) {
- auto progress = anim::linear(1., dt);
- paintFrame(p, progress, 1. - progress, progress);
- }
- void CrossFadeAnimation::paintFrame(
- QPainter &p,
- float64 positionReady,
- float64 alphaWas,
- float64 alphaNow) {
- if (_lines.isEmpty()) return;
- for (const auto &line : std::as_const(_lines)) {
- paintLine(p, line, positionReady, alphaWas, alphaNow);
- }
- }
- void CrossFadeAnimation::paintLine(
- QPainter &p,
- const Line &line,
- float64 positionReady,
- float64 alphaWas,
- float64 alphaNow) {
- auto &snapshotWas = line.was.snapshot;
- auto &snapshotNow = line.now.snapshot;
- if (snapshotWas.isNull() && snapshotNow.isNull()) {
- // This can happen if both labels have an empty line or if one
- // label has an empty line where the second one already ended.
- // In this case lineWidth is zero and snapshot is null.
- return;
- }
- const auto pixelRatio = style::DevicePixelRatio();
- auto positionWas = line.was.position;
- auto positionNow = line.now.position;
- auto left = anim::interpolate(positionWas.x(), positionNow.x(), positionReady);
- auto topDelta = (snapshotNow.height() / pixelRatio) - (snapshotWas.height() / pixelRatio);
- auto widthDelta = (snapshotNow.width() / pixelRatio) - (snapshotWas.width() / pixelRatio);
- auto topWas = anim::interpolate(positionWas.y(), positionNow.y() + topDelta, positionReady);
- auto topNow = topWas - topDelta;
- p.setOpacity(alphaWas);
- if (!snapshotWas.isNull()) {
- p.drawPixmap(left, topWas, snapshotWas);
- if (topDelta > 0) {
- p.fillRect(left, topWas - topDelta, snapshotWas.width() / pixelRatio, topDelta, _bg);
- }
- if (widthDelta > 0) {
- p.fillRect(left + (snapshotWas.width() / pixelRatio), topNow, widthDelta, snapshotNow.height() / pixelRatio, _bg);
- }
- }
- p.setOpacity(alphaNow);
- if (!snapshotNow.isNull()) {
- p.drawPixmap(left, topNow, snapshotNow);
- if (topDelta < 0) {
- p.fillRect(left, topNow + topDelta, snapshotNow.width() / pixelRatio, -topDelta, _bg);
- }
- if (widthDelta < 0) {
- p.fillRect(left + (snapshotNow.width() / pixelRatio), topWas, -widthDelta, snapshotWas.height() / pixelRatio, _bg);
- }
- }
- }
- LabelSimple::LabelSimple(
- QWidget *parent,
- const style::LabelSimple &st,
- const QString &value)
- : RpWidget(parent)
- , _st(st) {
- setText(value);
- }
- void LabelSimple::setText(const QString &value, bool *outTextChanged) {
- if (_fullText == value) {
- if (outTextChanged) *outTextChanged = false;
- return;
- }
- _fullText = value;
- _fullTextWidth = _st.font->width(_fullText);
- if (!_st.maxWidth || _fullTextWidth <= _st.maxWidth) {
- _text = _fullText;
- _textWidth = _fullTextWidth;
- } else {
- auto newText = _st.font->elided(_fullText, _st.maxWidth);
- if (newText == _text) {
- if (outTextChanged) *outTextChanged = false;
- return;
- }
- _text = newText;
- _textWidth = _st.font->width(_text);
- }
- resize(_textWidth, _st.font->height);
- update();
- if (outTextChanged) *outTextChanged = true;
- }
- void LabelSimple::paintEvent(QPaintEvent *e) {
- Painter p(this);
- p.setFont(_st.font);
- p.setPen(_st.textFg);
- p.drawTextLeft(0, 0, width(), _text, _textWidth);
- }
- FlatLabel::FlatLabel(
- QWidget *parent,
- const style::FlatLabel &st,
- const style::PopupMenu &stMenu)
- : RpWidget(parent)
- , _text(st.minWidth ? st.minWidth : kQFixedMax)
- , _st(st)
- , _stMenu(stMenu) {
- init();
- }
- FlatLabel::FlatLabel(
- QWidget *parent,
- const QString &text,
- const style::FlatLabel &st,
- const style::PopupMenu &stMenu)
- : RpWidget(parent)
- , _text(st.minWidth ? st.minWidth : kQFixedMax)
- , _st(st)
- , _stMenu(stMenu) {
- setText(text);
- init();
- }
- FlatLabel::FlatLabel(
- QWidget *parent,
- rpl::producer<QString> &&text,
- const style::FlatLabel &st,
- const style::PopupMenu &stMenu)
- : RpWidget(parent)
- , _text(st.minWidth ? st.minWidth : kQFixedMax)
- , _st(st)
- , _stMenu(stMenu) {
- textUpdated();
- std::move(
- text
- ) | rpl::start_with_next([this](const QString &value) {
- setText(value);
- }, lifetime());
- init();
- }
- FlatLabel::FlatLabel(
- QWidget *parent,
- rpl::producer<TextWithEntities> &&text,
- const style::FlatLabel &st,
- const style::PopupMenu &stMenu,
- const Text::MarkedContext &context)
- : RpWidget(parent)
- , _text(st.minWidth ? st.minWidth : kQFixedMax)
- , _st(st)
- , _stMenu(stMenu)
- , _touchSelectTimer([=] { touchSelect(); }) {
- textUpdated();
- std::move(
- text
- ) | rpl::start_with_next([=](const TextWithEntities &value) {
- setMarkedText(value, context);
- }, lifetime());
- init();
- }
- void FlatLabel::init() {
- _contextCopyText = Integration::Instance().phraseContextCopyText();
- }
- void FlatLabel::textUpdated() {
- refreshSize();
- setMouseTracking(_selectable || _text.hasLinks());
- if (_text.hasSpoilers()) {
- _text.setSpoilerLinkFilter([weak = Ui::MakeWeak(this)](
- const ClickContext &context) {
- return (context.button == Qt::LeftButton) && weak;
- });
- }
- update();
- }
- void FlatLabel::setText(const QString &text) {
- _text.setText(_st.style, text, _labelOptions);
- textUpdated();
- }
- void FlatLabel::setMarkedText(
- const TextWithEntities &textWithEntities,
- Text::MarkedContext context) {
- context.repaint = [=] { update(); };
- _text.setMarkedText(
- _st.style,
- textWithEntities,
- _labelMarkedOptions,
- context);
- textUpdated();
- }
- void FlatLabel::setSelectable(bool selectable) {
- if (_selectable != selectable) {
- _selection = { 0, 0 };
- _savedSelection = { 0, 0 };
- _selectable = selectable;
- setMouseTracking(_selectable || _text.hasLinks());
- }
- }
- void FlatLabel::setDoubleClickSelectsParagraph(bool doubleClickSelectsParagraph) {
- _doubleClickSelectsParagraph = doubleClickSelectsParagraph;
- }
- void FlatLabel::setContextCopyText(const QString ©Text) {
- _contextCopyText = copyText;
- }
- void FlatLabel::setBreakEverywhere(bool breakEverywhere) {
- _breakEverywhere = breakEverywhere;
- }
- void FlatLabel::setTryMakeSimilarLines(bool tryMakeSimilarLines) {
- _tryMakeSimilarLines = tryMakeSimilarLines;
- }
- int FlatLabel::resizeGetHeight(int newWidth) {
- _allowedWidth = newWidth;
- _textWidth = countTextWidth();
- return countTextHeight(_textWidth);
- }
- int FlatLabel::textMaxWidth() const {
- return _text.maxWidth();
- }
- int FlatLabel::naturalWidth() const {
- return (_st.align == style::al_top) ? -1 : textMaxWidth();
- }
- QMargins FlatLabel::getMargins() const {
- return _st.margin;
- }
- int FlatLabel::countTextWidth() const {
- const auto available = _allowedWidth
- ? _allowedWidth
- : (_st.minWidth ? _st.minWidth : _text.maxWidth());
- if (_allowedWidth > 0
- && _allowedWidth < _text.maxWidth()
- && _tryMakeSimilarLines) {
- auto large = _allowedWidth;
- auto small = _allowedWidth / 2;
- const auto largeHeight = _text.countHeight(large, _breakEverywhere);
- while (large - small > 1) {
- const auto middle = (large + small) / 2;
- if (largeHeight == _text.countHeight(middle, _breakEverywhere)) {
- large = middle;
- } else {
- small = middle;
- }
- }
- return large;
- }
- return available;
- }
- int FlatLabel::countTextHeight(int textWidth) {
- _fullTextHeight = _text.countHeight(textWidth, _breakEverywhere);
- return _st.maxHeight
- ? qMin(_fullTextHeight, _st.maxHeight)
- : _fullTextHeight;
- }
- void FlatLabel::refreshSize() {
- int textWidth = countTextWidth();
- int textHeight = countTextHeight(textWidth);
- int fullWidth = _st.margin.left() + textWidth + _st.margin.right();
- int fullHeight = _st.margin.top() + textHeight + _st.margin.bottom();
- resize(fullWidth, fullHeight);
- }
- void FlatLabel::setLink(uint16 index, const ClickHandlerPtr &lnk) {
- _text.setLink(index, lnk);
- }
- void FlatLabel::setLinksTrusted() {
- static const auto TrustedLinksFilter = [](
- const ClickHandlerPtr &link,
- Qt::MouseButton button) {
- if (const auto url = dynamic_cast<UrlClickHandler*>(link.get())) {
- url->UrlClickHandler::onClick({ button });
- return false;
- }
- return true;
- };
- setClickHandlerFilter(TrustedLinksFilter);
- }
- void FlatLabel::setClickHandlerFilter(ClickHandlerFilter &&filter) {
- _clickHandlerFilter = std::move(filter);
- }
- void FlatLabel::overrideLinkClickHandler(Fn<void()> handler) {
- setClickHandlerFilter([=](
- const ClickHandlerPtr &link,
- Qt::MouseButton button) {
- if (button != Qt::LeftButton) {
- return true;
- }
- handler();
- return false;
- });
- }
- void FlatLabel::overrideLinkClickHandler(Fn<void(QString url)> handler) {
- setClickHandlerFilter([=](
- const ClickHandlerPtr &link,
- Qt::MouseButton button) {
- if (button != Qt::LeftButton) {
- return true;
- }
- handler(link->url());
- return false;
- });
- }
- void FlatLabel::setContextMenuHook(Fn<void(ContextMenuRequest)> hook) {
- _contextMenuHook = std::move(hook);
- }
- void FlatLabel::mouseMoveEvent(QMouseEvent *e) {
- _lastMousePos = e->globalPos();
- dragActionUpdate();
- }
- void FlatLabel::mousePressEvent(QMouseEvent *e) {
- if (_contextMenu) {
- e->accept();
- return; // ignore mouse press, that was hiding context menu
- }
- dragActionStart(e->globalPos(), e->button());
- }
- Text::StateResult FlatLabel::dragActionStart(const QPoint &p, Qt::MouseButton button) {
- _lastMousePos = p;
- auto state = dragActionUpdate();
- if (button != Qt::LeftButton) return state;
- ClickHandler::pressed();
- _dragAction = NoDrag;
- _dragWasInactive = WasInactivePress(window());
- if (_dragWasInactive) {
- MarkInactivePress(window(), false);
- }
- if (ClickHandler::getPressed()) {
- _dragStartPosition = mapFromGlobal(_lastMousePos);
- _dragAction = PrepareDrag;
- }
- if (!_selectable || _dragAction != NoDrag) {
- return state;
- }
- if (_trippleClickTimer.isActive() && (_lastMousePos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) {
- if (state.uponSymbol) {
- _selection = { state.symbol, state.symbol };
- _savedSelection = { 0, 0 };
- _dragSymbol = state.symbol;
- _dragAction = Selecting;
- _selectionType = TextSelectType::Paragraphs;
- updateHover(state);
- _trippleClickTimer.callOnce(QApplication::doubleClickInterval());
- update();
- }
- }
- if (_selectionType != TextSelectType::Paragraphs) {
- _dragSymbol = state.symbol;
- bool uponSelected = state.uponSymbol;
- if (uponSelected) {
- if (_dragSymbol < _selection.from || _dragSymbol >= _selection.to) {
- uponSelected = false;
- }
- }
- if (uponSelected) {
- _dragStartPosition = mapFromGlobal(_lastMousePos);
- _dragAction = PrepareDrag; // start text drag
- } else if (!_dragWasInactive) {
- if (state.afterSymbol) ++_dragSymbol;
- _selection = { _dragSymbol, _dragSymbol };
- _savedSelection = { 0, 0 };
- _dragAction = Selecting;
- update();
- }
- }
- return state;
- }
- Text::StateResult FlatLabel::dragActionFinish(const QPoint &p, Qt::MouseButton button) {
- _lastMousePos = p;
- auto state = dragActionUpdate();
- auto activated = ClickHandler::unpressed();
- if (_dragAction == Dragging) {
- activated = nullptr;
- } else if (_dragAction == PrepareDrag) {
- _selection = { 0, 0 };
- _savedSelection = { 0, 0 };
- update();
- }
- _dragAction = NoDrag;
- _selectionType = TextSelectType::Letters;
- if (activated) {
- // _clickHandlerFilter may delete `this`. In that case we don't want
- // to try to show a context menu or smth like that.
- crl::on_main(this, [=] {
- const auto guard = window();
- if (!_clickHandlerFilter
- || _clickHandlerFilter(activated, button)) {
- ActivateClickHandler(guard, activated, button);
- }
- });
- }
- if (QGuiApplication::clipboard()->supportsSelection()
- && !_selection.empty()) {
- TextUtilities::SetClipboardText(
- _text.toTextForMimeData(_selection),
- QClipboard::Selection);
- }
- return state;
- }
- void FlatLabel::mouseReleaseEvent(QMouseEvent *e) {
- dragActionFinish(e->globalPos(), e->button());
- if (!rect().contains(e->pos())) {
- leaveEvent(e);
- }
- }
- void FlatLabel::mouseDoubleClickEvent(QMouseEvent *e) {
- auto state = dragActionStart(e->globalPos(), e->button());
- if (((_dragAction == Selecting) || (_dragAction == NoDrag)) && _selectionType == TextSelectType::Letters) {
- if (state.uponSymbol) {
- _dragSymbol = state.symbol;
- _selectionType = _doubleClickSelectsParagraph ? TextSelectType::Paragraphs : TextSelectType::Words;
- if (_dragAction == NoDrag) {
- _dragAction = Selecting;
- _selection = { state.symbol, state.symbol };
- _savedSelection = { 0, 0 };
- }
- mouseMoveEvent(e);
- _trippleClickPoint = e->globalPos();
- _trippleClickTimer.callOnce(QApplication::doubleClickInterval());
- }
- }
- }
- void FlatLabel::enterEventHook(QEnterEvent *e) {
- _lastMousePos = QCursor::pos();
- dragActionUpdate();
- }
- void FlatLabel::leaveEventHook(QEvent *e) {
- ClickHandler::clearActive(this);
- }
- void FlatLabel::focusOutEvent(QFocusEvent *e) {
- if (!_selection.empty()) {
- if (_contextMenu) {
- _savedSelection = _selection;
- }
- _selection = { 0, 0 };
- update();
- }
- }
- void FlatLabel::focusInEvent(QFocusEvent *e) {
- if (!_savedSelection.empty()) {
- _selection = _savedSelection;
- _savedSelection = { 0, 0 };
- update();
- }
- }
- void FlatLabel::keyPressEvent(QKeyEvent *e) {
- e->ignore();
- if (e->key() == Qt::Key_Copy || (e->key() == Qt::Key_C && e->modifiers().testFlag(Qt::ControlModifier))) {
- if (!_selection.empty()) {
- copySelectedText();
- e->accept();
- }
- #ifdef Q_OS_MAC
- } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) {
- auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection;
- if (!selection.empty()) {
- TextUtilities::SetClipboardText(_text.toTextForMimeData(selection), QClipboard::FindBuffer);
- }
- #endif // Q_OS_MAC
- }
- }
- void FlatLabel::contextMenuEvent(QContextMenuEvent *e) {
- if (!_contextMenuHook && !_selectable && !_text.hasLinks()) {
- return;
- }
- showContextMenu(e, ContextMenuReason::FromEvent);
- }
- bool FlatLabel::eventHook(QEvent *e) {
- if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
- QTouchEvent *ev = static_cast<QTouchEvent*>(e);
- if (ev->device()->type() == base::TouchDevice::TouchScreen) {
- touchEvent(ev);
- return true;
- }
- }
- return RpWidget::eventHook(e);
- }
- void FlatLabel::touchEvent(QTouchEvent *e) {
- if (e->type() == QEvent::TouchCancel) { // cancel
- if (!_touchInProgress) return;
- _touchInProgress = false;
- _touchSelectTimer.cancel();
- _touchSelect = false;
- _dragAction = NoDrag;
- return;
- }
- if (!e->touchPoints().isEmpty()) {
- _touchPrevPos = _touchPos;
- _touchPos = e->touchPoints().cbegin()->screenPos().toPoint();
- }
- switch (e->type()) {
- case QEvent::TouchBegin: {
- if (_contextMenu) {
- e->accept();
- return; // ignore mouse press, that was hiding context menu
- }
- if (_touchInProgress) return;
- if (e->touchPoints().isEmpty()) return;
- _touchInProgress = true;
- _touchSelectTimer.callOnce(QApplication::startDragTime());
- _touchSelect = false;
- _touchStart = _touchPrevPos = _touchPos;
- } break;
- case QEvent::TouchUpdate: {
- if (!_touchInProgress) return;
- if (_touchSelect) {
- _lastMousePos = _touchPos;
- dragActionUpdate();
- }
- } break;
- case QEvent::TouchEnd: {
- if (!_touchInProgress) return;
- _touchInProgress = false;
- auto weak = MakeWeak(this);
- if (_touchSelect) {
- dragActionFinish(_touchPos, Qt::RightButton);
- QContextMenuEvent contextMenu(QContextMenuEvent::Mouse, mapFromGlobal(_touchPos), _touchPos);
- showContextMenu(&contextMenu, ContextMenuReason::FromTouch);
- } else { // one short tap -- like mouse click
- dragActionStart(_touchPos, Qt::LeftButton);
- dragActionFinish(_touchPos, Qt::LeftButton);
- }
- if (weak) {
- _touchSelectTimer.cancel();
- _touchSelect = false;
- }
- } break;
- }
- }
- void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason) {
- if (e->reason() == QContextMenuEvent::Mouse) {
- _lastMousePos = e->globalPos();
- } else {
- _lastMousePos = QCursor::pos();
- }
- const auto state = dragActionUpdate();
- const auto hasSelection = _selectable && !_selection.empty();
- const auto uponSelection = _selectable
- && ((reason == ContextMenuReason::FromTouch && hasSelection)
- || (state.uponSymbol
- && (state.symbol >= _selection.from)
- && (state.symbol < _selection.to)));
- _contextMenu = base::make_unique_q<PopupMenu>(this, _stMenu);
- const auto request = ContextMenuRequest{
- .menu = _contextMenu.get(),
- .link = ClickHandler::getActive(),
- .selection = _selectable ? _selection : TextSelection(),
- .uponSelection = uponSelection,
- .fullSelection = _selectable && _text.isFullSelection(_selection),
- };
- if (_contextMenuHook) {
- _contextMenuHook(request);
- } else {
- fillContextMenu(request);
- }
- if (_contextMenu->empty()) {
- _contextMenu = nullptr;
- } else {
- _contextMenu->popup(e->globalPos());
- e->accept();
- }
- }
- void FlatLabel::fillContextMenu(ContextMenuRequest request) {
- if (request.fullSelection && !_contextCopyText.isEmpty()) {
- request.menu->addAction(
- _contextCopyText,
- [=] { copyContextText(); });
- } else if (request.uponSelection && !request.fullSelection) {
- request.menu->addAction(
- Integration::Instance().phraseContextCopySelected(),
- [=] { copySelectedText(); });
- } else if (_selectable
- && request.selection.empty()
- && !_contextCopyText.isEmpty()) {
- request.menu->addAction(
- _contextCopyText,
- [=] { copyContextText(); });
- }
- if (request.link) {
- const auto label = request.link->copyToClipboardContextItemText();
- if (!label.isEmpty()) {
- request.menu->addAction(
- label,
- [text = request.link->copyToClipboardText()] {
- QGuiApplication::clipboard()->setText(text);
- });
- }
- }
- }
- void FlatLabel::copySelectedText() {
- const auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection;
- if (!selection.empty()) {
- TextUtilities::SetClipboardText(_text.toTextForMimeData(selection));
- }
- }
- void FlatLabel::copyContextText() {
- TextUtilities::SetClipboardText(_text.toTextForMimeData());
- }
- void FlatLabel::touchSelect() {
- _touchSelect = true;
- dragActionStart(_touchPos, Qt::LeftButton);
- }
- void FlatLabel::executeDrag() {
- if (_dragAction != Dragging) return;
- auto state = getTextState(_dragStartPosition);
- bool uponSelected = state.uponSymbol && _selection.from <= state.symbol;
- if (uponSelected) {
- if (_dragSymbol < _selection.from || _dragSymbol >= _selection.to) {
- uponSelected = false;
- }
- }
- const auto pressedHandler = ClickHandler::getPressed();
- const auto selectedText = [&] {
- if (uponSelected) {
- return _text.toTextForMimeData(_selection);
- } else if (pressedHandler) {
- return TextForMimeData::Simple(pressedHandler->dragText());
- }
- return TextForMimeData();
- }();
- if (auto mimeData = TextUtilities::MimeDataFromText(selectedText)) {
- auto drag = new QDrag(window());
- drag->setMimeData(mimeData.release());
- drag->exec(Qt::CopyAction);
- // We don't receive mouseReleaseEvent when drag is finished.
- ClickHandler::unpressed();
- }
- }
- void FlatLabel::clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) {
- update();
- }
- void FlatLabel::clickHandlerPressedChanged(const ClickHandlerPtr &action, bool active) {
- update();
- }
- CrossFadeAnimation::Data FlatLabel::crossFadeData(
- style::color bg,
- QPoint basePosition) {
- auto result = CrossFadeAnimation::Data();
- result.full = GrabWidgetToImage(this, QRect(), bg->c);
- const auto textWidth = width() - _st.margin.left() - _st.margin.right();
- result.lineWidths = _text.countLineWidths(textWidth, {
- .breakEverywhere = _breakEverywhere,
- });
- result.lineHeight = _st.style.font->height;
- const auto addedHeight = (_st.style.lineHeight - result.lineHeight);
- if (addedHeight > 0) {
- result.lineAddTop = addedHeight / 2;
- result.lineHeight += addedHeight;
- }
- result.position = basePosition + pos();
- result.align = _st.align;
- result.font = _st.style.font;
- result.margin = _st.margin;
- return result;
- }
- std::unique_ptr<CrossFadeAnimation> FlatLabel::CrossFade(
- not_null<FlatLabel*> from,
- not_null<FlatLabel*> to,
- style::color bg,
- QPoint fromPosition,
- QPoint toPosition) {
- return std::make_unique<CrossFadeAnimation>(
- bg,
- from->crossFadeData(bg, fromPosition),
- to->crossFadeData(bg, toPosition));
- }
- Text::StateResult FlatLabel::dragActionUpdate() {
- auto m = mapFromGlobal(_lastMousePos);
- auto state = getTextState(m);
- updateHover(state);
- if (_dragAction == PrepareDrag && (m - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
- _dragAction = Dragging;
- InvokeQueued(this, [=] { executeDrag(); });
- }
- return state;
- }
- void FlatLabel::updateHover(const Text::StateResult &state) {
- bool lnkChanged = ClickHandler::setActive(state.link, this);
- if (!_selectable) {
- refreshCursor(state.uponSymbol);
- return;
- }
- Qt::CursorShape cur = style::cur_default;
- if (_dragAction == NoDrag) {
- if (state.link) {
- cur = style::cur_pointer;
- } else if (state.uponSymbol) {
- cur = style::cur_text;
- }
- } else {
- if (_dragAction == Selecting) {
- uint16 second = state.symbol;
- if (state.afterSymbol && _selectionType == TextSelectType::Letters) {
- ++second;
- }
- auto selection = _text.adjustSelection({ qMin(second, _dragSymbol), qMax(second, _dragSymbol) }, _selectionType);
- if (_selection != selection) {
- _selection = selection;
- _savedSelection = { 0, 0 };
- setFocus();
- update();
- }
- } else if (_dragAction == Dragging) {
- }
- if (ClickHandler::getPressed()) {
- cur = style::cur_pointer;
- } else if (_dragAction == Selecting) {
- cur = style::cur_text;
- }
- }
- if (_dragAction == Selecting) {
- // checkSelectingScroll();
- } else {
- // noSelectingScroll();
- }
- if (_dragAction == NoDrag && (lnkChanged || cur != _cursor)) {
- setCursor(_cursor = cur);
- }
- }
- void FlatLabel::refreshCursor(bool uponSymbol) {
- if (_dragAction != NoDrag) {
- return;
- }
- bool needTextCursor = _selectable && uponSymbol;
- style::cursor newCursor = needTextCursor ? style::cur_text : style::cur_default;
- if (ClickHandler::getActive()) {
- newCursor = style::cur_pointer;
- }
- if (newCursor != _cursor) {
- _cursor = newCursor;
- setCursor(_cursor);
- }
- }
- Text::StateResult FlatLabel::getTextState(const QPoint &m) const {
- Text::StateRequestElided request;
- request.align = _st.align;
- if (_selectable) {
- request.flags |= Text::StateRequest::Flag::LookupSymbol;
- }
- const auto textWidth = _textWidth
- ? _textWidth
- : (width() - _st.margin.left() - _st.margin.right());
- const auto useWidth = !(_st.align & Qt::AlignLeft)
- ? textWidth
- : std::min(textWidth, _text.maxWidth());
- Text::StateResult state;
- bool heightExceeded = _st.maxHeight && (_st.maxHeight < _fullTextHeight || useWidth < _text.maxWidth());
- bool renderElided = _breakEverywhere || heightExceeded;
- if (renderElided) {
- auto lineHeight = qMax(_st.style.lineHeight, _st.style.font->height);
- auto lines = _st.maxHeight ? qMax(_st.maxHeight / lineHeight, 1) : ((height() / lineHeight) + 2);
- request.lines = lines;
- if (_breakEverywhere) {
- request.flags |= Text::StateRequest::Flag::BreakEverywhere;
- }
- state = _text.getStateElided(m - QPoint(_st.margin.left(), _st.margin.top()), useWidth, request);
- } else {
- state = _text.getState(m - QPoint(_st.margin.left(), _st.margin.top()), useWidth, request);
- }
- return state;
- }
- void FlatLabel::setOpacity(float64 o) {
- _opacity = o;
- update();
- }
- void FlatLabel::setTextColorOverride(std::optional<QColor> color) {
- _textColorOverride = color;
- update();
- }
- void FlatLabel::paintEvent(QPaintEvent *e) {
- Painter p(this);
- p.setOpacity(_opacity);
- if (_textColorOverride) {
- p.setPen(*_textColorOverride);
- } else {
- p.setPen(_st.textFg);
- }
- p.setTextPalette(_st.palette);
- const auto textWidth = _textWidth
- ? _textWidth
- : (width() - _st.margin.left() - _st.margin.right());
- const auto textLeft = _textWidth
- ? ((_st.align & Qt::AlignLeft)
- ? _st.margin.left()
- : (_st.align & Qt::AlignHCenter)
- ? ((width() - _textWidth) / 2)
- : (width() - _st.margin.right() - _textWidth))
- : _st.margin.left();
- const auto selection = !_selection.empty()
- ? _selection
- : _contextMenu
- ? _savedSelection
- : _selection;
- const auto heightExceeded = _st.maxHeight
- && (_st.maxHeight < _fullTextHeight || textWidth < _text.maxWidth());
- const auto renderElided = _breakEverywhere || heightExceeded;
- const auto lineHeight = qMax(_st.style.lineHeight, _st.style.font->height);
- const auto elisionHeight = !renderElided
- ? 0
- : _st.maxHeight
- ? qMax(_st.maxHeight, lineHeight)
- : height();
- const auto paused = _animationsPausedCallback
- ? _animationsPausedCallback()
- : WhichAnimationsPaused::None;
- _text.draw(p, {
- .position = { textLeft, _st.margin.top() },
- .availableWidth = textWidth,
- .align = _st.align,
- .clip = e->rect(),
- .palette = &_st.palette,
- .spoiler = Text::DefaultSpoilerCache(),
- .now = crl::now(),
- .pausedEmoji = (paused == WhichAnimationsPaused::CustomEmoji
- || paused == WhichAnimationsPaused::All),
- .pausedSpoiler = (paused == WhichAnimationsPaused::Spoiler
- || paused == WhichAnimationsPaused::All),
- .selection = selection,
- .elisionHeight = elisionHeight,
- .elisionBreakEverywhere = renderElided && _breakEverywhere,
- });
- }
- DividerLabel::DividerLabel(
- QWidget *parent,
- object_ptr<RpWidget> &&child,
- const style::margins &padding,
- RectParts parts)
- : PaddingWrap(parent, std::move(child), padding)
- , _background(this, st::boxDividerHeight, st::boxDividerBg, parts) {
- }
- int DividerLabel::naturalWidth() const {
- return -1;
- }
- void DividerLabel::resizeEvent(QResizeEvent *e) {
- _background->lower();
- _background->setGeometry(rect());
- return PaddingWrap::resizeEvent(e);
- }
- } // namespace Ui
|