2014-05-30 08:53:19 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2014-12-01 10:47:38 +00:00
|
|
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
2014-05-30 08:53:19 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2015-10-03 13:16:42 +00:00
|
|
|
In addition, as a special exception, the copyright holders give permission
|
|
|
|
to link the code of portions of this program with the OpenSSL library.
|
|
|
|
|
2014-05-30 08:53:19 +00:00
|
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
2016-02-08 10:56:18 +00:00
|
|
|
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
2014-05-30 08:53:19 +00:00
|
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
|
|
|
2016-04-07 18:05:28 +00:00
|
|
|
#include "ui/flatlabel.h"
|
2016-05-26 15:31:20 +00:00
|
|
|
#include "mainwindow.h"
|
|
|
|
#include "lang.h"
|
2014-05-30 08:53:19 +00:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
TextParseOptions _labelOptions = {
|
|
|
|
TextParseMultiline, // flags
|
|
|
|
0, // maxw
|
|
|
|
0, // maxh
|
|
|
|
Qt::LayoutDirectionAuto, // dir
|
|
|
|
};
|
2016-05-31 19:27:11 +00:00
|
|
|
TextParseOptions _labelMarkedOptions = {
|
2016-07-28 17:01:08 +00:00
|
|
|
TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands, // flags
|
2016-05-31 19:27:11 +00:00
|
|
|
0, // maxw
|
|
|
|
0, // maxh
|
|
|
|
Qt::LayoutDirectionAuto, // dir
|
|
|
|
};
|
2014-05-30 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
2016-05-31 19:27:11 +00:00
|
|
|
FlatLabel::FlatLabel(QWidget *parent, const style::flatLabel &st, const style::textStyle &tst) : TWidget(parent)
|
|
|
|
, _text(st.width ? st.width : QFIXED_MAX)
|
|
|
|
, _st(st)
|
|
|
|
, _tst(tst)
|
|
|
|
, _contextCopyText(lang(lng_context_copy_text)) {
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
FlatLabel::FlatLabel(QWidget *parent, const QString &text, InitType initType, const style::flatLabel &st, const style::textStyle &tst) : TWidget(parent)
|
|
|
|
, _text(st.width ? st.width : QFIXED_MAX)
|
|
|
|
, _st(st)
|
|
|
|
, _tst(tst)
|
|
|
|
, _contextCopyText(lang(lng_context_copy_text)) {
|
|
|
|
if (initType == InitType::Rich) {
|
|
|
|
setRichText(text);
|
|
|
|
} else {
|
|
|
|
setText(text);
|
|
|
|
}
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::init() {
|
2016-05-26 15:31:20 +00:00
|
|
|
_trippleClickTimer.setSingleShot(true);
|
|
|
|
|
|
|
|
_touchSelectTimer.setSingleShot(true);
|
|
|
|
connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect()));
|
2014-05-30 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::setText(const QString &text) {
|
|
|
|
textstyleSet(&_tst);
|
|
|
|
_text.setText(_st.font, text, _labelOptions);
|
2016-05-26 15:31:20 +00:00
|
|
|
refreshSize();
|
2015-01-23 10:20:40 +00:00
|
|
|
textstyleRestore();
|
2016-05-26 15:31:20 +00:00
|
|
|
setMouseTracking(_selectable || _text.hasLinks());
|
2014-05-30 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::setRichText(const QString &text) {
|
|
|
|
textstyleSet(&_tst);
|
|
|
|
_text.setRichText(_st.font, text, _labelOptions);
|
2016-05-26 15:31:20 +00:00
|
|
|
refreshSize();
|
2015-01-23 10:20:40 +00:00
|
|
|
textstyleRestore();
|
2016-05-26 15:31:20 +00:00
|
|
|
setMouseTracking(_selectable || _text.hasLinks());
|
|
|
|
}
|
|
|
|
|
2016-05-31 19:27:11 +00:00
|
|
|
void FlatLabel::setMarkedText(const TextWithEntities &textWithEntities) {
|
|
|
|
textstyleSet(&_tst);
|
|
|
|
_text.setMarkedText(_st.font, textWithEntities, _labelMarkedOptions);
|
|
|
|
refreshSize();
|
|
|
|
textstyleRestore();
|
|
|
|
setMouseTracking(_selectable || _text.hasLinks());
|
|
|
|
}
|
|
|
|
|
2016-05-26 15:31:20 +00:00
|
|
|
void FlatLabel::setSelectable(bool selectable) {
|
|
|
|
_selectable = selectable;
|
|
|
|
setMouseTracking(_selectable || _text.hasLinks());
|
|
|
|
}
|
|
|
|
|
2016-05-31 19:27:11 +00:00
|
|
|
void FlatLabel::setDoubleClickSelectsParagraph(bool doubleClickSelectsParagraph) {
|
|
|
|
_doubleClickSelectsParagraph = doubleClickSelectsParagraph;
|
|
|
|
}
|
|
|
|
|
2016-05-26 15:31:20 +00:00
|
|
|
void FlatLabel::setContextCopyText(const QString ©Text) {
|
|
|
|
_contextCopyText = copyText;
|
2014-05-30 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
2016-05-31 19:27:11 +00:00
|
|
|
void FlatLabel::setExpandLinksMode(ExpandLinksMode mode) {
|
|
|
|
_contextExpandLinksMode = mode;
|
|
|
|
}
|
|
|
|
|
2016-06-01 16:40:51 +00:00
|
|
|
void FlatLabel::setBreakEverywhere(bool breakEverywhere) {
|
|
|
|
_breakEverywhere = breakEverywhere;
|
|
|
|
}
|
|
|
|
|
2016-08-22 17:16:21 +00:00
|
|
|
int FlatLabel::resizeGetHeight(int newWidth) {
|
|
|
|
_allowedWidth = newWidth;
|
2016-03-10 10:15:21 +00:00
|
|
|
textstyleSet(&_tst);
|
2016-08-22 17:16:21 +00:00
|
|
|
int textWidth = countTextWidth();
|
|
|
|
int textHeight = countTextHeight(textWidth);
|
2016-03-10 10:15:21 +00:00
|
|
|
textstyleRestore();
|
2016-08-22 17:16:21 +00:00
|
|
|
return _st.margin.top() + textHeight + _st.margin.bottom();
|
2016-05-26 15:31:20 +00:00
|
|
|
}
|
|
|
|
|
2016-05-31 19:27:11 +00:00
|
|
|
int FlatLabel::naturalWidth() const {
|
|
|
|
return _text.maxWidth();
|
|
|
|
}
|
|
|
|
|
2016-05-26 15:31:20 +00:00
|
|
|
int FlatLabel::countTextWidth() const {
|
|
|
|
return _allowedWidth ? (_allowedWidth - _st.margin.left() - _st.margin.right()) : (_st.width ? _st.width : _text.maxWidth());
|
|
|
|
}
|
|
|
|
|
|
|
|
int FlatLabel::countTextHeight(int textWidth) {
|
|
|
|
_fullTextHeight = _text.countHeight(textWidth);
|
|
|
|
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);
|
2016-03-10 10:15:21 +00:00
|
|
|
}
|
|
|
|
|
2016-03-29 17:17:00 +00:00
|
|
|
void FlatLabel::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) {
|
2014-05-30 08:53:19 +00:00
|
|
|
_text.setLink(lnkIndex, lnk);
|
|
|
|
}
|
|
|
|
|
2016-05-31 19:27:11 +00:00
|
|
|
void FlatLabel::setClickHandlerHook(ClickHandlerHook &&hook) {
|
|
|
|
_clickHandlerHook = std_::move(hook);
|
|
|
|
}
|
|
|
|
|
2014-05-30 08:53:19 +00:00
|
|
|
void FlatLabel::mouseMoveEvent(QMouseEvent *e) {
|
|
|
|
_lastMousePos = e->globalPos();
|
2016-05-26 15:31:20 +00:00
|
|
|
dragActionUpdate();
|
2014-05-30 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::mousePressEvent(QMouseEvent *e) {
|
2016-05-26 15:31:20 +00:00
|
|
|
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;
|
|
|
|
|
2016-03-29 17:17:00 +00:00
|
|
|
ClickHandler::pressed();
|
2016-05-26 15:31:20 +00:00
|
|
|
_dragAction = NoDrag;
|
|
|
|
_dragWasInactive = App::wnd()->inactivePress();
|
|
|
|
if (_dragWasInactive) App::wnd()->inactivePress(false);
|
|
|
|
|
|
|
|
if (ClickHandler::getPressed()) {
|
2016-05-26 16:05:39 +00:00
|
|
|
_dragStartPosition = mapFromGlobal(_lastMousePos);
|
2016-05-26 15:31:20 +00:00
|
|
|
_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.start(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) {
|
2016-05-26 16:05:39 +00:00
|
|
|
_dragStartPosition = mapFromGlobal(_lastMousePos);
|
2016-05-26 15:31:20 +00:00
|
|
|
_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();
|
|
|
|
|
|
|
|
ClickHandlerPtr activated = ClickHandler::unpressed();
|
|
|
|
if (_dragAction == Dragging) {
|
|
|
|
activated.clear();
|
|
|
|
} else if (_dragAction == PrepareDrag) {
|
|
|
|
_selection = { 0, 0 };
|
|
|
|
_savedSelection = { 0, 0 };
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
_dragAction = NoDrag;
|
|
|
|
_selectionType = TextSelectType::Letters;
|
|
|
|
|
|
|
|
if (activated) {
|
2016-05-31 19:27:11 +00:00
|
|
|
if (_clickHandlerHook.isNull() || _clickHandlerHook.call(activated, button)) {
|
|
|
|
App::activateClickHandler(activated, button);
|
|
|
|
}
|
2016-05-26 15:31:20 +00:00
|
|
|
}
|
2016-06-14 16:26:41 +00:00
|
|
|
|
|
|
|
#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64
|
|
|
|
if (!_selection.empty()) {
|
|
|
|
QApplication::clipboard()->setText(_text.originalText(_selection, _contextExpandLinksMode), QClipboard::Selection);
|
|
|
|
}
|
|
|
|
#endif // Q_OS_LINUX32 || Q_OS_LINUX64
|
|
|
|
|
2016-05-26 15:31:20 +00:00
|
|
|
return state;
|
2014-05-30 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::mouseReleaseEvent(QMouseEvent *e) {
|
2016-05-26 15:31:20 +00:00
|
|
|
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;
|
2016-05-31 19:27:11 +00:00
|
|
|
_selectionType = _doubleClickSelectsParagraph ? TextSelectType::Paragraphs : TextSelectType::Words;
|
2016-05-26 15:31:20 +00:00
|
|
|
if (_dragAction == NoDrag) {
|
|
|
|
_dragAction = Selecting;
|
|
|
|
_selection = { state.symbol, state.symbol };
|
|
|
|
_savedSelection = { 0, 0 };
|
|
|
|
}
|
|
|
|
mouseMoveEvent(e);
|
|
|
|
|
|
|
|
_trippleClickPoint = e->globalPos();
|
|
|
|
_trippleClickTimer.start(QApplication::doubleClickInterval());
|
|
|
|
}
|
2014-05-30 08:53:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-30 08:03:29 +00:00
|
|
|
void FlatLabel::enterEvent(QEvent *e) {
|
|
|
|
_lastMousePos = QCursor::pos();
|
2016-05-26 15:31:20 +00:00
|
|
|
dragActionUpdate();
|
2016-03-30 08:03:29 +00:00
|
|
|
}
|
|
|
|
|
2014-05-30 08:53:19 +00:00
|
|
|
void FlatLabel::leaveEvent(QEvent *e) {
|
2016-03-29 17:17:00 +00:00
|
|
|
ClickHandler::clearActive(this);
|
|
|
|
}
|
|
|
|
|
2016-05-26 15:31:20 +00:00
|
|
|
void FlatLabel::focusOutEvent(QFocusEvent *e) {
|
|
|
|
if (!_selection.empty()) {
|
2016-05-31 19:27:11 +00:00
|
|
|
if (_contextMenu) {
|
|
|
|
_savedSelection = _selection;
|
|
|
|
}
|
2016-05-26 15:31:20 +00:00
|
|
|
_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()) {
|
|
|
|
onCopySelectedText();
|
|
|
|
e->accept();
|
|
|
|
}
|
2016-06-28 18:27:23 +00:00
|
|
|
#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()) {
|
|
|
|
QApplication::clipboard()->setText(_text.originalText(selection, _contextExpandLinksMode), QClipboard::FindBuffer);
|
|
|
|
}
|
|
|
|
#endif // Q_OS_MAC
|
2016-05-26 15:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::contextMenuEvent(QContextMenuEvent *e) {
|
2016-06-03 21:46:45 +00:00
|
|
|
if (!_selectable) return;
|
|
|
|
|
2016-05-26 15:31:20 +00:00
|
|
|
showContextMenu(e, ContextMenuReason::FromEvent);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FlatLabel::event(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() == QTouchDevice::TouchScreen) {
|
|
|
|
touchEvent(ev);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return QWidget::event(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::touchEvent(QTouchEvent *e) {
|
|
|
|
const Qt::TouchPointStates &states(e->touchPointStates());
|
|
|
|
if (e->type() == QEvent::TouchCancel) { // cancel
|
|
|
|
if (!_touchInProgress) return;
|
|
|
|
_touchInProgress = false;
|
|
|
|
_touchSelectTimer.stop();
|
|
|
|
_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.start(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;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
_touchSelectTimer.stop();
|
|
|
|
_touchSelect = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason) {
|
|
|
|
if (_contextMenu) {
|
|
|
|
_contextMenu->deleteLater();
|
|
|
|
_contextMenu = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e->reason() == QContextMenuEvent::Mouse) {
|
|
|
|
_lastMousePos = e->globalPos();
|
|
|
|
} else {
|
|
|
|
_lastMousePos = QCursor::pos();
|
|
|
|
}
|
|
|
|
auto state = dragActionUpdate();
|
|
|
|
|
|
|
|
bool hasSelection = !_selection.empty();
|
|
|
|
bool uponSelection = state.uponSymbol && (state.symbol >= _selection.from) && (state.symbol < _selection.to);
|
|
|
|
bool fullSelection = _text.isFullSelection(_selection);
|
|
|
|
if (reason == ContextMenuReason::FromTouch && hasSelection && !uponSelection) {
|
|
|
|
uponSelection = hasSelection;
|
|
|
|
}
|
|
|
|
|
|
|
|
_contextMenu = new PopupMenu();
|
|
|
|
|
|
|
|
_contextMenuClickHandler = ClickHandler::getActive();
|
|
|
|
|
2016-05-31 19:27:11 +00:00
|
|
|
if (fullSelection && !_contextCopyText.isEmpty()) {
|
|
|
|
_contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText()))->setEnabled(true);
|
|
|
|
} else if (uponSelection && !fullSelection) {
|
2016-05-26 15:31:20 +00:00
|
|
|
_contextMenu->addAction(lang(lng_context_copy_selected), this, SLOT(onCopySelectedText()))->setEnabled(true);
|
2016-05-31 19:27:11 +00:00
|
|
|
} else if (!hasSelection && !_contextCopyText.isEmpty()) {
|
|
|
|
_contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText()))->setEnabled(true);
|
2016-05-26 15:31:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QString linkCopyToClipboardText = _contextMenuClickHandler ? _contextMenuClickHandler->copyToClipboardContextItemText() : QString();
|
|
|
|
if (!linkCopyToClipboardText.isEmpty()) {
|
|
|
|
_contextMenu->addAction(linkCopyToClipboardText, this, SLOT(onCopyContextUrl()))->setEnabled(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_contextMenu->actions().isEmpty()) {
|
|
|
|
delete _contextMenu;
|
|
|
|
_contextMenu = nullptr;
|
|
|
|
} else {
|
|
|
|
connect(_contextMenu, SIGNAL(destroyed(QObject*)), this, SLOT(onContextMenuDestroy(QObject*)));
|
|
|
|
_contextMenu->popup(e->globalPos());
|
|
|
|
e->accept();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::onCopySelectedText() {
|
|
|
|
auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection;
|
|
|
|
if (!selection.empty()) {
|
2016-05-31 19:27:11 +00:00
|
|
|
QApplication::clipboard()->setText(_text.originalText(selection, _contextExpandLinksMode));
|
2016-05-26 15:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::onCopyContextText() {
|
2016-05-31 19:27:11 +00:00
|
|
|
QApplication::clipboard()->setText(_text.originalText({ 0, 0xFFFF }, _contextExpandLinksMode));
|
2016-05-26 15:31:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::onCopyContextUrl() {
|
|
|
|
if (_contextMenuClickHandler) {
|
|
|
|
_contextMenuClickHandler->copyToClipboard();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::onTouchSelect() {
|
|
|
|
_touchSelect = true;
|
|
|
|
dragActionStart(_touchPos, Qt::LeftButton);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::onContextMenuDestroy(QObject *obj) {
|
|
|
|
if (obj == _contextMenu) {
|
|
|
|
_contextMenu = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-26 16:05:39 +00:00
|
|
|
void FlatLabel::onExecuteDrag() {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ClickHandlerPtr pressedHandler = ClickHandler::getPressed();
|
|
|
|
QString selectedText;
|
|
|
|
if (uponSelected) {
|
|
|
|
selectedText = _text.originalText(_selection, ExpandLinksAll);
|
|
|
|
} else if (pressedHandler) {
|
|
|
|
selectedText = pressedHandler->dragText();
|
|
|
|
}
|
|
|
|
if (!selectedText.isEmpty()) {
|
|
|
|
auto mimeData = new QMimeData();
|
|
|
|
mimeData->setText(selectedText);
|
|
|
|
auto drag = new QDrag(App::wnd());
|
|
|
|
drag->setMimeData(mimeData);
|
2016-06-21 17:02:45 +00:00
|
|
|
drag->exec(Qt::CopyAction);
|
2016-06-01 16:40:51 +00:00
|
|
|
|
|
|
|
// We don't receive mouseReleaseEvent when drag is finished.
|
|
|
|
ClickHandler::unpressed();
|
2016-05-26 16:05:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-29 17:17:00 +00:00
|
|
|
void FlatLabel::clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) {
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::clickHandlerPressedChanged(const ClickHandlerPtr &action, bool active) {
|
|
|
|
update();
|
2014-05-30 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
2016-05-26 15:31:20 +00:00
|
|
|
Text::StateResult FlatLabel::dragActionUpdate() {
|
2016-05-26 16:05:39 +00:00
|
|
|
auto m = mapFromGlobal(_lastMousePos);
|
2016-05-26 15:31:20 +00:00
|
|
|
auto state = getTextState(m);
|
|
|
|
updateHover(state);
|
2016-05-26 16:05:39 +00:00
|
|
|
|
|
|
|
if (_dragAction == PrepareDrag && (m - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
|
|
|
|
_dragAction = Dragging;
|
|
|
|
QTimer::singleShot(1, this, SLOT(onExecuteDrag()));
|
|
|
|
}
|
|
|
|
|
2016-05-26 15:31:20 +00:00
|
|
|
return state;
|
2014-05-30 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
2016-05-26 15:31:20 +00:00
|
|
|
void FlatLabel::updateHover(const Text::StateResult &state) {
|
|
|
|
bool lnkChanged = ClickHandler::setActive(state.link, this);
|
2016-03-29 17:17:00 +00:00
|
|
|
|
2016-05-26 15:31:20 +00:00
|
|
|
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;
|
2016-04-13 18:29:32 +00:00
|
|
|
request.align = _st.align;
|
2016-05-26 15:31:20 +00:00
|
|
|
if (_selectable) {
|
|
|
|
request.flags |= Text::StateRequest::Flag::LookupSymbol;
|
|
|
|
}
|
|
|
|
int textWidth = width() - _st.margin.left() - _st.margin.right();
|
|
|
|
|
|
|
|
textstyleSet(&_tst);
|
|
|
|
Text::StateResult state;
|
2016-06-01 16:40:51 +00:00
|
|
|
bool heightExceeded = _st.maxHeight && (_st.maxHeight < _fullTextHeight || textWidth < _text.maxWidth());
|
|
|
|
bool renderElided = _breakEverywhere || heightExceeded;
|
|
|
|
if (renderElided) {
|
2016-05-26 15:31:20 +00:00
|
|
|
auto lineHeight = qMax(_tst.lineHeight, _st.font->height);
|
2016-06-01 16:40:51 +00:00
|
|
|
auto lines = _st.maxHeight ? qMax(_st.maxHeight / lineHeight, 1) : ((height() / lineHeight) + 2);
|
|
|
|
request.lines = lines;
|
|
|
|
if (_breakEverywhere) {
|
|
|
|
request.flags |= Text::StateRequest::Flag::BreakEverywhere;
|
|
|
|
}
|
2016-05-26 15:31:20 +00:00
|
|
|
state = _text.getStateElided(m.x() - _st.margin.left(), m.y() - _st.margin.top(), textWidth, request);
|
|
|
|
} else {
|
|
|
|
state = _text.getState(m.x() - _st.margin.left(), m.y() - _st.margin.top(), textWidth, request);
|
|
|
|
}
|
2014-05-30 08:53:19 +00:00
|
|
|
textstyleRestore();
|
2016-03-29 17:17:00 +00:00
|
|
|
|
2016-05-26 15:31:20 +00:00
|
|
|
return state;
|
2014-05-30 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::setOpacity(float64 o) {
|
|
|
|
_opacity = o;
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatLabel::paintEvent(QPaintEvent *e) {
|
2016-05-26 15:31:20 +00:00
|
|
|
Painter p(this);
|
2014-05-30 08:53:19 +00:00
|
|
|
p.setOpacity(_opacity);
|
2016-05-26 15:31:20 +00:00
|
|
|
p.setPen(_st.textFg);
|
2014-05-30 08:53:19 +00:00
|
|
|
textstyleSet(&_tst);
|
2016-05-26 15:31:20 +00:00
|
|
|
int textWidth = width() - _st.margin.left() - _st.margin.right();
|
|
|
|
auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection;
|
2016-06-01 16:40:51 +00:00
|
|
|
bool heightExceeded = _st.maxHeight && (_st.maxHeight < _fullTextHeight || textWidth < _text.maxWidth());
|
|
|
|
bool renderElided = _breakEverywhere || heightExceeded;
|
|
|
|
if (renderElided) {
|
2016-05-26 15:31:20 +00:00
|
|
|
auto lineHeight = qMax(_tst.lineHeight, _st.font->height);
|
2016-06-01 16:40:51 +00:00
|
|
|
auto lines = _st.maxHeight ? qMax(_st.maxHeight / lineHeight, 1) : ((height() / lineHeight) + 2);
|
|
|
|
_text.drawElided(p, _st.margin.left(), _st.margin.top(), textWidth, lines, _st.align, e->rect().y(), e->rect().bottom(), 0, _breakEverywhere, selection);
|
2016-05-26 15:31:20 +00:00
|
|
|
} else {
|
|
|
|
_text.draw(p, _st.margin.left(), _st.margin.top(), textWidth, _st.align, e->rect().y(), e->rect().bottom(), selection);
|
|
|
|
}
|
2014-05-30 08:53:19 +00:00
|
|
|
textstyleRestore();
|
|
|
|
}
|