masked_input_field.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "ui/widgets/fields/masked_input_field.h"
  8. #include "base/qt/qt_common_adapters.h"
  9. #include "ui/painter.h"
  10. #include "ui/qt_weak_factory.h"
  11. #include "ui/widgets/popup_menu.h"
  12. #include "ui/integration.h"
  13. #include "styles/palette.h"
  14. #include "styles/style_widgets.h"
  15. #include <QtWidgets/QCommonStyle>
  16. #include <QtWidgets/QApplication>
  17. #include <QtGui/QClipboard>
  18. namespace Ui {
  19. namespace {
  20. class InputStyle final : public QCommonStyle {
  21. public:
  22. InputStyle() {
  23. setParent(QCoreApplication::instance());
  24. }
  25. void drawPrimitive(
  26. PrimitiveElement element,
  27. const QStyleOption *option,
  28. QPainter *painter,
  29. const QWidget *widget = nullptr) const override {
  30. }
  31. static InputStyle *instance() {
  32. if (!_instance) {
  33. if (!QGuiApplication::instance()) {
  34. return nullptr;
  35. }
  36. _instance = new InputStyle();
  37. }
  38. return _instance;
  39. }
  40. ~InputStyle() {
  41. _instance = nullptr;
  42. }
  43. private:
  44. static InputStyle *_instance;
  45. };
  46. InputStyle *InputStyle::_instance = nullptr;
  47. } // namespace
  48. MaskedInputField::MaskedInputField(
  49. QWidget *parent,
  50. const style::InputField &st,
  51. rpl::producer<QString> placeholder,
  52. const QString &val)
  53. : Parent(val, parent)
  54. , _st(st)
  55. , _oldtext(val)
  56. , _placeholderFull(std::move(placeholder)) {
  57. resize(_st.width, _st.heightMin);
  58. setFont(_st.style.font);
  59. setAlignment(_st.textAlign);
  60. _placeholderFull.value(
  61. ) | rpl::start_with_next([=](const QString &text) {
  62. refreshPlaceholder(text);
  63. }, lifetime());
  64. style::PaletteChanged(
  65. ) | rpl::start_with_next([=] {
  66. updatePalette();
  67. }, lifetime());
  68. updatePalette();
  69. if (_st.textBg->c.alphaF() >= 1. && !_st.borderRadius) {
  70. setAttribute(Qt::WA_OpaquePaintEvent);
  71. }
  72. connect(this, SIGNAL(textChanged(QString)), this, SLOT(onTextChange(QString)));
  73. connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(onCursorPositionChanged(int,int)));
  74. connect(this, SIGNAL(textEdited(QString)), this, SLOT(onTextEdited()));
  75. connect(this, &MaskedInputField::selectionChanged, [] {
  76. Integration::Instance().textActionsUpdated();
  77. });
  78. setStyle(InputStyle::instance());
  79. QLineEdit::setTextMargins(0, 0, 0, 0);
  80. setContentsMargins(_textMargins + QMargins(-2, -1, -2, -1));
  81. setFrame(false);
  82. setAttribute(Qt::WA_AcceptTouchEvents);
  83. _touchTimer.setSingleShot(true);
  84. connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
  85. setTextMargins(_st.textMargins);
  86. startPlaceholderAnimation();
  87. startBorderAnimation();
  88. finishAnimating();
  89. }
  90. void MaskedInputField::updatePalette() {
  91. auto p = palette();
  92. p.setColor(QPalette::Text, _st.textFg->c);
  93. p.setColor(QPalette::Highlight, st::msgInBgSelected->c);
  94. p.setColor(QPalette::HighlightedText, st::historyTextInFgSelected->c);
  95. setPalette(p);
  96. }
  97. void MaskedInputField::setCorrectedText(QString &now, int &nowCursor, const QString &newText, int newPos) {
  98. if (newPos < 0 || newPos > newText.size()) {
  99. newPos = newText.size();
  100. }
  101. auto updateText = (newText != now);
  102. if (updateText) {
  103. now = newText;
  104. setText(now);
  105. startPlaceholderAnimation();
  106. }
  107. auto updateCursorPosition = (newPos != nowCursor) || updateText;
  108. if (updateCursorPosition) {
  109. nowCursor = newPos;
  110. setCursorPosition(nowCursor);
  111. }
  112. }
  113. void MaskedInputField::customUpDown(bool custom) {
  114. _customUpDown = custom;
  115. }
  116. int MaskedInputField::borderAnimationStart() const {
  117. return _borderAnimationStart;
  118. }
  119. void MaskedInputField::setTextMargins(const QMargins &mrg) {
  120. _textMargins = mrg;
  121. setContentsMargins(_textMargins + QMargins(-2, -1, -2, -1));
  122. refreshPlaceholder(_placeholderFull.current());
  123. }
  124. void MaskedInputField::onTouchTimer() {
  125. _touchRightButton = true;
  126. }
  127. bool MaskedInputField::eventHook(QEvent *e) {
  128. auto type = e->type();
  129. if (type == QEvent::TouchBegin
  130. || type == QEvent::TouchUpdate
  131. || type == QEvent::TouchEnd
  132. || type == QEvent::TouchCancel) {
  133. auto event = static_cast<QTouchEvent*>(e);
  134. if (event->device()->type() == base::TouchDevice::TouchScreen) {
  135. touchEvent(event);
  136. }
  137. }
  138. return Parent::eventHook(e);
  139. }
  140. void MaskedInputField::touchEvent(QTouchEvent *e) {
  141. switch (e->type()) {
  142. case QEvent::TouchBegin: {
  143. if (_touchPress || e->touchPoints().isEmpty()) {
  144. return;
  145. }
  146. _touchTimer.start(QApplication::startDragTime());
  147. _touchPress = true;
  148. _touchMove = _touchRightButton = _mousePressedInTouch = false;
  149. _touchStart = e->touchPoints().cbegin()->screenPos().toPoint();
  150. } break;
  151. case QEvent::TouchUpdate: {
  152. if (!e->touchPoints().isEmpty()) {
  153. touchUpdate(e->touchPoints().cbegin()->screenPos().toPoint());
  154. }
  155. } break;
  156. case QEvent::TouchEnd: {
  157. touchFinish();
  158. } break;
  159. case QEvent::TouchCancel: {
  160. _touchPress = false;
  161. _touchTimer.stop();
  162. } break;
  163. }
  164. }
  165. void MaskedInputField::touchUpdate(QPoint globalPosition) {
  166. if (_touchPress
  167. && !_touchMove
  168. && ((globalPosition - _touchStart).manhattanLength()
  169. >= QApplication::startDragDistance())) {
  170. _touchMove = true;
  171. }
  172. }
  173. void MaskedInputField::touchFinish() {
  174. if (!_touchPress) {
  175. return;
  176. }
  177. const auto weak = MakeWeak(this);
  178. if (!_touchMove && window()) {
  179. QPoint mapped(mapFromGlobal(_touchStart));
  180. if (_touchRightButton) {
  181. QContextMenuEvent contextEvent(
  182. QContextMenuEvent::Mouse,
  183. mapped,
  184. _touchStart);
  185. contextMenuEvent(&contextEvent);
  186. } else {
  187. QGuiApplication::inputMethod()->show();
  188. }
  189. }
  190. if (weak) {
  191. _touchTimer.stop();
  192. _touchPress
  193. = _touchMove
  194. = _touchRightButton
  195. = _mousePressedInTouch = false;
  196. }
  197. }
  198. void MaskedInputField::paintEvent(QPaintEvent *e) {
  199. auto p = QPainter(this);
  200. auto r = rect().intersected(e->rect());
  201. p.fillRect(r, _st.textBg);
  202. if (_st.border) {
  203. p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b);
  204. }
  205. auto errorDegree = _a_error.value(_error ? 1. : 0.);
  206. auto focusedDegree = _a_focused.value(_focused ? 1. : 0.);
  207. auto borderShownDegree = _a_borderShown.value(1.);
  208. auto borderOpacity = _a_borderOpacity.value(_borderVisible ? 1. : 0.);
  209. if (_st.borderActive && (borderOpacity > 0.)) {
  210. auto borderStart = std::clamp(_borderAnimationStart, 0, width());
  211. auto borderFrom = qRound(borderStart * (1. - borderShownDegree));
  212. auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree);
  213. if (borderTo > borderFrom) {
  214. auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree);
  215. p.setOpacity(borderOpacity);
  216. p.fillRect(borderFrom, height() - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg);
  217. p.setOpacity(1);
  218. }
  219. }
  220. p.setClipRect(r);
  221. if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) {
  222. auto placeholderShiftDegree = _a_placeholderShifted.value(_placeholderShifted ? 1. : 0.);
  223. p.save();
  224. p.setClipRect(r);
  225. auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree);
  226. QRect r(rect().marginsRemoved(_textMargins + _st.placeholderMargins));
  227. r.moveTop(r.top() + placeholderTop);
  228. if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
  229. auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree;
  230. auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, focusedDegree);
  231. placeholderFg = anim::color(placeholderFg, _st.placeholderFgError, errorDegree);
  232. PainterHighQualityEnabler hq(p);
  233. p.setPen(Qt::NoPen);
  234. p.setBrush(placeholderFg);
  235. p.translate(r.topLeft());
  236. p.scale(placeholderScale, placeholderScale);
  237. p.drawPath(_placeholderPath);
  238. p.restore();
  239. } else if (!_placeholder.isEmpty()) {
  240. auto placeholderHiddenDegree = _a_placeholderShifted.value(_placeholderShifted ? 1. : 0.);
  241. if (placeholderHiddenDegree < 1.) {
  242. p.setOpacity(1. - placeholderHiddenDegree);
  243. p.save();
  244. p.setClipRect(r);
  245. auto placeholderLeft = anim::interpolate(0, -_st.placeholderShift, placeholderHiddenDegree);
  246. QRect r(rect().marginsRemoved(_textMargins + _st.placeholderMargins));
  247. r.moveLeft(r.left() + placeholderLeft);
  248. if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
  249. p.setFont(_st.placeholderFont);
  250. p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree));
  251. p.drawText(r, _placeholder, _st.placeholderAlign);
  252. p.restore();
  253. p.setOpacity(1.);
  254. }
  255. }
  256. paintAdditionalPlaceholder(p);
  257. QLineEdit::paintEvent(e);
  258. }
  259. void MaskedInputField::mousePressEvent(QMouseEvent *e) {
  260. if (_touchPress && e->button() == Qt::LeftButton) {
  261. _mousePressedInTouch = true;
  262. _touchStart = e->globalPos();
  263. }
  264. return QLineEdit::mousePressEvent(e);
  265. }
  266. void MaskedInputField::mouseReleaseEvent(QMouseEvent *e) {
  267. if (_mousePressedInTouch) {
  268. touchFinish();
  269. }
  270. return QLineEdit::mouseReleaseEvent(e);
  271. }
  272. void MaskedInputField::mouseMoveEvent(QMouseEvent *e) {
  273. if (_mousePressedInTouch) {
  274. touchUpdate(e->globalPos());
  275. }
  276. return QLineEdit::mouseMoveEvent(e);
  277. }
  278. QString MaskedInputField::getDisplayedText() const {
  279. auto result = getLastText();
  280. if (!_lastPreEditText.isEmpty()) {
  281. result = result.mid(0, _oldcursor)
  282. + _lastPreEditText
  283. + result.mid(_oldcursor);
  284. }
  285. return result;
  286. }
  287. void MaskedInputField::startBorderAnimation() {
  288. auto borderVisible = (_error || _focused);
  289. if (_borderVisible != borderVisible) {
  290. _borderVisible = borderVisible;
  291. if (_borderVisible) {
  292. if (_a_borderOpacity.animating()) {
  293. _a_borderOpacity.start([this] { update(); }, 0., 1., _st.duration);
  294. } else {
  295. _a_borderShown.start([this] { update(); }, 0., 1., _st.duration);
  296. }
  297. } else if (qFuzzyCompare(_a_borderShown.value(1.), 0.)) {
  298. _a_borderShown.stop();
  299. _a_borderOpacity.stop();
  300. } else {
  301. _a_borderOpacity.start([this] { update(); }, 1., 0., _st.duration);
  302. }
  303. }
  304. }
  305. void MaskedInputField::focusInEvent(QFocusEvent *e) {
  306. _borderAnimationStart = (e->reason() == Qt::MouseFocusReason) ? mapFromGlobal(QCursor::pos()).x() : (width() / 2);
  307. setFocused(true);
  308. QLineEdit::focusInEvent(e);
  309. focused();
  310. }
  311. void MaskedInputField::focusOutEvent(QFocusEvent *e) {
  312. setFocused(false);
  313. QLineEdit::focusOutEvent(e);
  314. blurred();
  315. }
  316. void MaskedInputField::setFocused(bool focused) {
  317. if (_focused != focused) {
  318. _focused = focused;
  319. _a_focused.start([this] { update(); }, _focused ? 0. : 1., _focused ? 1. : 0., _st.duration);
  320. startPlaceholderAnimation();
  321. startBorderAnimation();
  322. }
  323. }
  324. void MaskedInputField::resizeEvent(QResizeEvent *e) {
  325. refreshPlaceholder(_placeholderFull.current());
  326. _borderAnimationStart = width() / 2;
  327. QLineEdit::resizeEvent(e);
  328. }
  329. void MaskedInputField::refreshPlaceholder(const QString &text) {
  330. const auto availableWidth = width() - _textMargins.left() - _textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1;
  331. if (_st.placeholderScale > 0.) {
  332. auto placeholderFont = _st.placeholderFont->f;
  333. placeholderFont.setStyleStrategy(QFont::PreferMatch);
  334. const auto metrics = QFontMetrics(placeholderFont);
  335. _placeholder = metrics.elidedText(text, Qt::ElideRight, availableWidth);
  336. _placeholderPath = QPainterPath();
  337. if (!_placeholder.isEmpty()) {
  338. const auto result = style::FindAdjustResult(placeholderFont);
  339. const auto ascent = result ? result->iascent : metrics.ascent();
  340. _placeholderPath.addText(0, ascent, placeholderFont, _placeholder);
  341. }
  342. } else {
  343. _placeholder = _st.placeholderFont->elided(text, availableWidth);
  344. }
  345. update();
  346. }
  347. void MaskedInputField::setPlaceholder(rpl::producer<QString> placeholder) {
  348. _placeholderFull = std::move(placeholder);
  349. }
  350. void MaskedInputField::contextMenuEvent(QContextMenuEvent *e) {
  351. if (const auto menu = createStandardContextMenu()) {
  352. _contextMenu = base::make_unique_q<PopupMenu>(this, menu);
  353. _contextMenu->popup(e->globalPos());
  354. }
  355. }
  356. void MaskedInputField::inputMethodEvent(QInputMethodEvent *e) {
  357. QLineEdit::inputMethodEvent(e);
  358. _lastPreEditText = e->preeditString();
  359. update();
  360. }
  361. void MaskedInputField::showError() {
  362. showErrorNoFocus();
  363. if (!hasFocus()) {
  364. setFocus();
  365. }
  366. }
  367. void MaskedInputField::showErrorNoFocus() {
  368. setErrorShown(true);
  369. }
  370. void MaskedInputField::hideError() {
  371. setErrorShown(false);
  372. }
  373. void MaskedInputField::setErrorShown(bool error) {
  374. if (_error != error) {
  375. _error = error;
  376. _a_error.start([this] { update(); }, _error ? 0. : 1., _error ? 1. : 0., _st.duration);
  377. startBorderAnimation();
  378. }
  379. }
  380. QSize MaskedInputField::sizeHint() const {
  381. return geometry().size();
  382. }
  383. QSize MaskedInputField::minimumSizeHint() const {
  384. return geometry().size();
  385. }
  386. void MaskedInputField::setDisplayFocused(bool focused) {
  387. setFocused(focused);
  388. finishAnimating();
  389. }
  390. void MaskedInputField::finishAnimating() {
  391. _a_focused.stop();
  392. _a_error.stop();
  393. _a_placeholderShifted.stop();
  394. _a_borderShown.stop();
  395. _a_borderOpacity.stop();
  396. update();
  397. }
  398. void MaskedInputField::setPlaceholderHidden(bool forcePlaceholderHidden) {
  399. _forcePlaceholderHidden = forcePlaceholderHidden;
  400. startPlaceholderAnimation();
  401. }
  402. void MaskedInputField::startPlaceholderAnimation() {
  403. auto placeholderShifted = _forcePlaceholderHidden || (_focused && _st.placeholderScale > 0.) || !getLastText().isEmpty();
  404. if (_placeholderShifted != placeholderShifted) {
  405. _placeholderShifted = placeholderShifted;
  406. _a_placeholderShifted.start([this] { update(); }, _placeholderShifted ? 0. : 1., _placeholderShifted ? 1. : 0., _st.duration);
  407. }
  408. }
  409. QRect MaskedInputField::placeholderRect() const {
  410. return rect().marginsRemoved(_textMargins + _st.placeholderMargins);
  411. }
  412. style::font MaskedInputField::phFont() {
  413. return _st.style.font;
  414. }
  415. void MaskedInputField::placeholderAdditionalPrepare(QPainter &p) {
  416. p.setFont(_st.style.font);
  417. p.setPen(_st.placeholderFg);
  418. }
  419. void MaskedInputField::keyPressEvent(QKeyEvent *e) {
  420. QString wasText(_oldtext);
  421. int32 wasCursor(_oldcursor);
  422. if (_customUpDown && (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down || e->key() == Qt::Key_PageUp || e->key() == Qt::Key_PageDown)) {
  423. e->ignore();
  424. } else if (e == QKeySequence::DeleteStartOfWord && hasSelectedText()) {
  425. e->accept();
  426. backspace();
  427. } else {
  428. QLineEdit::keyPressEvent(e);
  429. }
  430. auto newText = text();
  431. auto newCursor = cursorPosition();
  432. if (wasText == newText && wasCursor == newCursor) { // call correct manually
  433. correctValue(wasText, wasCursor, newText, newCursor);
  434. _oldtext = newText;
  435. _oldcursor = newCursor;
  436. if (wasText != _oldtext) changed();
  437. startPlaceholderAnimation();
  438. }
  439. if (e->key() == Qt::Key_Escape) {
  440. e->ignore();
  441. cancelled();
  442. } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
  443. submitted(e->modifiers());
  444. #ifdef Q_OS_MAC
  445. } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) {
  446. auto selected = selectedText();
  447. if (!selected.isEmpty() && echoMode() == QLineEdit::Normal) {
  448. QGuiApplication::clipboard()->setText(selected, QClipboard::FindBuffer);
  449. }
  450. #endif // Q_OS_MAC
  451. }
  452. }
  453. void MaskedInputField::onTextEdited() {
  454. QString wasText(_oldtext), newText(text());
  455. int32 wasCursor(_oldcursor), newCursor(cursorPosition());
  456. correctValue(wasText, wasCursor, newText, newCursor);
  457. _oldtext = newText;
  458. _oldcursor = newCursor;
  459. if (wasText != _oldtext) changed();
  460. startPlaceholderAnimation();
  461. Integration::Instance().textActionsUpdated();
  462. }
  463. void MaskedInputField::onTextChange(const QString &text) {
  464. _oldtext = QLineEdit::text();
  465. setErrorShown(false);
  466. Integration::Instance().textActionsUpdated();
  467. }
  468. void MaskedInputField::onCursorPositionChanged(int oldPosition, int position) {
  469. _oldcursor = position;
  470. }
  471. } // namespace Ui