From 3186e1e495d0f7a6573c05746ef3c0c06e90ed37 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 15 Nov 2016 14:56:49 +0300 Subject: [PATCH] Ripple animations done for IconButton, FlatButton and RoundButton. Also moved input field classes to ui/widgets/input_fields module. --- Telegram/Resources/basic.style | 289 ---- Telegram/Resources/basic_types.style | 183 -- Telegram/Resources/colors.palette | 8 + Telegram/Resources/sample.tdesktop-theme | 7 + Telegram/Resources/winrc/Telegram.rc | 8 +- Telegram/Resources/winrc/Updater.rc | 8 +- Telegram/SourceFiles/application.cpp | 6 +- Telegram/SourceFiles/boxes/addcontactbox.cpp | 1 + Telegram/SourceFiles/boxes/addcontactbox.h | 24 +- Telegram/SourceFiles/boxes/autolockbox.cpp | 1 + Telegram/SourceFiles/boxes/backgroundbox.cpp | 1 + Telegram/SourceFiles/boxes/boxes.style | 88 +- Telegram/SourceFiles/boxes/confirmbox.h | 5 + .../SourceFiles/boxes/confirmphonebox.cpp | 7 +- Telegram/SourceFiles/boxes/confirmphonebox.h | 7 +- Telegram/SourceFiles/boxes/connectionbox.cpp | 2 + Telegram/SourceFiles/boxes/connectionbox.h | 11 +- Telegram/SourceFiles/boxes/languagebox.cpp | 1 + Telegram/SourceFiles/boxes/passcodebox.cpp | 1 + Telegram/SourceFiles/boxes/passcodebox.h | 14 +- Telegram/SourceFiles/boxes/photocropbox.cpp | 1 + Telegram/SourceFiles/boxes/photosendbox.cpp | 8 +- Telegram/SourceFiles/boxes/photosendbox.h | 5 +- Telegram/SourceFiles/boxes/report_box.cpp | 5 +- Telegram/SourceFiles/boxes/report_box.h | 3 +- Telegram/SourceFiles/boxes/usernamebox.cpp | 1 + Telegram/SourceFiles/boxes/usernamebox.h | 3 +- .../SourceFiles/codegen/style/generator.cpp | 15 +- Telegram/SourceFiles/core/version.h | 2 +- Telegram/SourceFiles/data/data_drafts.cpp | 8 + Telegram/SourceFiles/data/data_drafts.h | 12 +- Telegram/SourceFiles/dialogs/dialogs.style | 22 +- Telegram/SourceFiles/dialogswidget.cpp | 3 +- Telegram/SourceFiles/dialogswidget.h | 3 +- Telegram/SourceFiles/history.h | 25 +- Telegram/SourceFiles/history/history.style | 40 +- Telegram/SourceFiles/historywidget.cpp | 44 +- Telegram/SourceFiles/historywidget.h | 9 +- Telegram/SourceFiles/intro/intro.style | 12 +- Telegram/SourceFiles/intro/introcode.cpp | 4 +- Telegram/SourceFiles/intro/introcode.h | 7 +- Telegram/SourceFiles/intro/introphone.cpp | 6 +- Telegram/SourceFiles/intro/introphone.h | 6 +- Telegram/SourceFiles/intro/intropwdcheck.cpp | 6 +- Telegram/SourceFiles/intro/intropwdcheck.h | 7 +- Telegram/SourceFiles/intro/introsignup.cpp | 5 +- Telegram/SourceFiles/intro/introsignup.h | 6 +- Telegram/SourceFiles/intro/introwidget.cpp | 1 + Telegram/SourceFiles/localimageloader.cpp | 8 +- Telegram/SourceFiles/localstorage.cpp | 9 +- Telegram/SourceFiles/localstorage.h | 1 - Telegram/SourceFiles/mainwidget.cpp | 2 + Telegram/SourceFiles/mainwindow.cpp | 1 + .../SourceFiles/media/media_clip_reader.h | 11 +- Telegram/SourceFiles/overview/overview.style | 2 +- Telegram/SourceFiles/overviewwidget.cpp | 43 +- Telegram/SourceFiles/overviewwidget.h | 3 +- Telegram/SourceFiles/passcodewidget.cpp | 1 + Telegram/SourceFiles/passcodewidget.h | 3 +- .../platform/mac/main_window_mac.mm | 12 +- Telegram/SourceFiles/profile/profile.style | 2 + .../profile/profile_actions_widget.cpp | 1 + .../profile/profile_actions_widget.h | 8 + .../profile/profile_block_widget.cpp | 1 + Telegram/SourceFiles/profile/profile_cover.h | 4 + .../SourceFiles/profile/profile_fixed_bar.cpp | 1 + .../profile/profile_settings_widget.cpp | 4 +- Telegram/SourceFiles/stdafx.h | 2 - Telegram/SourceFiles/stickers/stickers.style | 7 + .../ui/buttons/left_outline_button.h | 1 + .../ui/buttons/peer_avatar_button.h | 1 + Telegram/SourceFiles/ui/countryinput.cpp | 2 +- Telegram/SourceFiles/ui/countryinput.h | 1 - .../ui/effects/panel_animation.cpp | 4 +- .../ui/effects/ripple_animation.cpp | 157 ++ .../SourceFiles/ui/effects/ripple_animation.h | 62 + Telegram/SourceFiles/ui/flattextarea.cpp | 1426 ---------------- Telegram/SourceFiles/ui/flattextarea.h | 261 --- Telegram/SourceFiles/ui/widgets/buttons.cpp | 156 +- Telegram/SourceFiles/ui/widgets/buttons.h | 23 + Telegram/SourceFiles/ui/widgets/checkbox.h | 1 + .../input_fields.cpp} | 1487 ++++++++++++++++- .../{flatinput.h => widgets/input_fields.h} | 248 ++- .../SourceFiles/ui/widgets/multi_select.cpp | 1 + .../SourceFiles/ui/widgets/multi_select.h | 5 +- Telegram/SourceFiles/ui/widgets/widgets.style | 380 +++++ Telegram/SourceFiles/window/main_window.cpp | 1 + .../window/notifications_manager_default.cpp | 16 +- .../window/notifications_manager_default.h | 3 +- .../SourceFiles/window/slide_animation.cpp | 2 + .../SourceFiles/window/top_bar_widget.cpp | 29 +- Telegram/SourceFiles/window/window.style | 75 +- Telegram/build/version | 2 +- Telegram/gyp/Telegram.gyp | 8 +- 94 files changed, 2970 insertions(+), 2449 deletions(-) create mode 100644 Telegram/SourceFiles/ui/effects/ripple_animation.cpp create mode 100644 Telegram/SourceFiles/ui/effects/ripple_animation.h delete mode 100644 Telegram/SourceFiles/ui/flattextarea.cpp delete mode 100644 Telegram/SourceFiles/ui/flattextarea.h rename Telegram/SourceFiles/ui/{flatinput.cpp => widgets/input_fields.cpp} (63%) rename Telegram/SourceFiles/ui/{flatinput.h => widgets/input_fields.h} (68%) diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index eaf924394..6efd12541 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -57,156 +57,12 @@ boxTitlePosition: point(26px, 28px); boxTitleHeight: 54px; boxButtonFont: font(boxFontSize semibold); -defaultBoxButton: RoundButton { - textFg: #2f9fea; - textFgOver: #2f9fea; - secondaryTextFg: #2f9fea; - secondaryTextFgOver: #2f9fea; - textBg: boxBg; - textBgOver: lightButtonBgOver; - - width: -24px; - height: 36px; - padding: margins(0px, 0px, 0px, 0px); - - textTop: 8px; - downTextTop: 9px; - - font: boxButtonFont; -} -cancelBoxButton: RoundButton(defaultBoxButton) { - textFg: #aeaeae; -} -attentionBoxButton: RoundButton(defaultBoxButton) { - textFg: #ea4b2f; - textFgOver: #ea4b2f; - textBgOver: #fff0ed; -} boxButtonPadding: margins(12px, 16px, 22px, 16px); boxLabel: flatLabel(labelDefFlat) { font: font(boxFontSize); align: align(topleft); } -defaultLeftOutlineButton: OutlineButton { - outlineWidth: 3px; - outlineFg: windowBg; - outlineFgOver: windowActiveBg; - - textBg: windowBg; - textBgOver: #f2f7fa; - - textFg: windowActiveTextFg; - textFgOver: windowActiveTextFg; - - font: normalFont; - padding: margins(11px, 5px, 11px, 5px); -} -attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) { - outlineFgOver: #e43f3f; - - textBgOver: #faf2f2; - - textFg: #d15948; - textFgOver: #d15948; -} - -defaultInputArea: InputArea { - textBg: windowBg; - textFg: windowTextFg; - textMargins: margins(5px, 6px, 5px, 4px); - - placeholderFg: #999999; - placeholderFgActive: #aaaaaa; - placeholderMargins: margins(2px, 0px, 2px, 0px); - placeholderAlign: align(topleft); - placeholderShift: 50px; - duration: 120; - - borderFg: #e0e0e0; - borderFgActive: #62c0f7; - borderFgError: #e48383; - - border: 1px; - borderActive: 2px; - borderError: 2px; - - font: boxTextFont; - - heightMin: 32px; - heightMax: 128px; -} -defaultInputField: InputField { - textBg: windowBg; - textFg: windowTextFg; - textMargins: margins(0px, 6px, 0px, 4px); - textAlign: align(topleft); - - placeholderFg: #999999; - placeholderFgActive: #aaaaaa; - placeholderMargins: margins(2px, 0px, 2px, 0px); - placeholderAlign: align(topleft); - placeholderShift: 50px; - duration: 120; - - borderFg: #e0e0e0; - borderFgActive: #62c0f7; - borderFgError: #e48383; - - border: 1px; - borderActive: 2px; - borderError: 2px; - - font: boxTextFont; - - height: 32px; -} -defaultCheckboxIcon: icon {{ "default_checkbox_check", windowActiveFg, point(4px, 7px) }}; -defaultCheckbox: Checkbox { - textFg: windowTextFg; - textBg: windowBg; - - checkBg: #ffffff; - checkFg: #b3b3b3; - checkFgOver: #b3b3b3; - checkFgActive: windowActiveBg; - - width: -44px; - height: 22px; - - textPosition: point(32px, 2px); - diameter: 22px; - thickness: 2px; - checkIcon: defaultCheckboxIcon; - - font: normalFont; - duration: 120; -} -defaultBoxCheckbox: Checkbox(defaultCheckbox) { - width: -46px; - textPosition: point(34px, 1px); - font: boxTextFont; -} -defaultRadiobutton: Radiobutton { - textFg: windowTextFg; - textBg: windowBg; - - checkBg: #ffffff; - checkFg: #b3b3b3; - checkFgOver: #bfbfbf; - checkFgActive: #4eb3ee; - - width: -46px; - height: 22px; - - textPosition: point(34px, 0px); - diameter: 22px; - thickness: 2px; - checkSkip: 65px; // * 0.1 - - font: boxTextFont; - duration: 120; -} solidScroll: flatScroll { barColor: #3f729734; bgColor: #214f751a; @@ -279,40 +135,6 @@ linkCropLimit: 360px; linkFont: normalFont; linkOverFont: font(fsize underline); -inpDefFont: font(17px); -inpDefFlat: flatInput { - textColor: #000000; - bgColor: #ffffff; - bgActive: #ffffff; - width: 210px; - height: 40px; - align: align(left); - textMrg: margins(5px, 5px, 5px, 5px); - font: inpDefFont; - cursor: cursor(text); - - borderWidth: 0px; - borderColor: transparent; - borderActive: transparent; - borderError: transparent; - - phColor: #949494; - phFocusColor: #aaaaaa; - phAlign: align(left); - phPos: point(2px, 0px); - phShift: 50px; - phDuration: 100; -} - -inpDefGray: flatInput(inpDefFlat) { - bgColor: #f2f2f2; - borderWidth: 2px; - borderColor: #f2f2f2; - borderActive: #54c3f3; - borderError: #ed8080; - phColor: #808080; -} - scrollDef: flatScroll { barColor: #00000053; bgColor: #0000001a; @@ -375,63 +197,6 @@ noContactsHeight: 100px; noContactsFont: font(fsize); noContactsColor: #777777; -topBarHeight: 54px; -topBarDuration: 200; -topBarBackward: icon {{ "title_back", #a3a3a3 }}; -topBarForwardAlpha: 0.6; -topBarBack: icon {{ "title_back", #259fd8 }}; -topBarBackAlpha: 0.8; -topBarBackColor: #005faf; -topBarBackFont: font(16px); -topBarArrowPadding: margins(39px, 8px, 17px, 8px); -topBarMinPadding: 5px; -topBarButton: RoundButton { - textFg: btnYesColor; - textFgOver: btnYesColor; - secondaryTextFg: btnYesColor; - secondaryTextFgOver: btnYesColor; - textBg: windowBg; - textBgOver: #edf4f7; - - width: -22px; - height: 28px; - padding: margins(0px, 14px, 12px, 12px); - - textTop: 6px; - downTextTop: 7px; - - font: font(fsize); -} -defaultActiveButton: RoundButton { - textFg: activeButtonFg; - textFgOver: activeButtonFgOver; - secondaryTextFg: activeButtonSecondaryFg; - secondaryTextFgOver: activeButtonSecondaryFgOver; - textBg: activeButtonBg; - textBgOver: activeButtonBgOver; - - secondarySkip: 7px; - - width: -34px; - height: 34px; - padding: margins(0px, 0px, 0px, 0px); - - textTop: 8px; - downTextTop: 9px; - - font: semiboldFont; -} -defaultLightButton: RoundButton(defaultActiveButton) { - textFg: lightButtonFg; - textFgOver: lightButtonFgOver; - textBg: lightButtonBg; - textBgOver: lightButtonBgOver; -} -topBarClearButton: RoundButton(defaultLightButton) { - width: -18px; -} -topBarActionSkip: 10px; - activeFadeInDuration: 500; activeFadeOutDuration: 3000; @@ -704,13 +469,6 @@ boxPhotoTextFg: #808080; cropPointSize: 10px; cropSkip: 13px; cropMinSize: 20px; -confirmCaptionArea: InputArea(defaultInputArea) { - textMargins: margins(1px, 6px, 1px, 4px); - heightMax: 56px; -} -confirmBg: #f2f2f2; -confirmMaxHeight: 245px; -confirmCompressedSkip: 10px; profileMaxWidth: 410px; profilePadding: margins(28px, 30px, 28px, 0px); @@ -725,20 +483,6 @@ forwardFont: font(16px); forwardBg: #0000004c; forwardFg: #ffffff; -connectionHostInputField: InputField(defaultInputField) { - width: 160px; -} -connectionPortInputField: InputField(defaultInputField) { - width: 55px; -} -connectionUserInputField: InputField(defaultInputField) { - width: 95px; -} -connectionPasswordInputField: InputField(defaultInputField) { - width: 120px; -} -connectionIPv6Skip: 11px; - emojiTextFont: font(15px); emojiReplaceWidth: 52px; emojiReplaceHeight: 56px; @@ -784,12 +528,6 @@ botKbScroll: flatScroll(solidScroll) { deltax: 3px; width: 10px; } -switchPmButton: RoundButton(defaultBoxButton) { - width: 320px; - height: 34px; - textTop: 7px; - downTextTop: 8px; -} minPhotoSize: 100px; maxMediaSize: 420px; @@ -867,23 +605,6 @@ videoIcon: icon { }; locationSize: size(320px, 240px); -boxOptionListPadding: margins(2px, 20px, 2px, 2px); - -langsWidth: 256px; -langsButton: Radiobutton(defaultRadiobutton) { - width: 200px; -} - -backgroundPadding: 10px; -backgroundSize: size(108px, 193px); -backgroundScroll: flatScroll(boxScroll) { - round: 2px; - width: 10px; - deltax: 3px; - deltat: 10px; - deltab: 0px; -} - mentionHeight: 40px; mentionScroll: flatScroll(scrollDef) { topsh: 0px; @@ -934,11 +655,6 @@ inlineRowFileDescriptionTop: 23px; inlineResultsMinWidth: 64px; inlineDurationMargin: 3px; -editTextArea: InputArea(defaultInputArea) { - textMargins: margins(1px, 6px, 1px, 4px); - heightMax: 256px; -} - toastFont: normalFont; toastMaxWidth: 480px; toastMinMargin: 13px; @@ -948,11 +664,6 @@ toastPadding: margins(19px, 13px, 19px, 12px); toastFadeInDuration: 200; toastFadeOutDuration: 1000; -infoButton: PeerAvatarButton { - size: topBarHeight; - photoSize: 42px; -} - // forward declaration for single "title_back" usage. profileTopBarBackIconFg: #0290d7; profileTopBarBackIcon: icon {{ "title_back", profileTopBarBackIconFg }}; diff --git a/Telegram/Resources/basic_types.style b/Telegram/Resources/basic_types.style index 82a6f294e..09967422d 100644 --- a/Telegram/Resources/basic_types.style +++ b/Telegram/Resources/basic_types.style @@ -30,49 +30,6 @@ textStyle { lineHeight: pixels; } -flatInput { - textColor: color; - bgColor: color; - bgActive: color; - width: pixels; - height: pixels; - textMrg: margins; - align: align; - font: font; - cursor: cursor; - - icon: icon; - - borderWidth: pixels; - borderColor: color; - borderActive: color; - borderError: color; - - phColor: color; - phFocusColor: color; - phPos: point; - phAlign: align; - phShift: pixels; - phDuration: int; -} - -flatTextarea { - textColor: color; - bgColor: color; - width: pixels; - textMrg: margins; - align: align; - font: font; - cursor: cursor; - - phColor: color; - phFocusColor: color; - phPos: point; - phAlign: align; - phShift: pixels; - phDuration: int; -} - flatScroll { barColor: color; bgColor: color; @@ -125,143 +82,3 @@ botKeyboardButton { textTop: pixels; downTextTop: pixels; } - -RoundButton { - textFg: color; - textFgOver: color; - textBg: color; // rect of textBg with rounded rect of textBgOver upon it - textBgOver: color; - - secondaryTextFg: color; - secondaryTextFgOver: color; - secondarySkip: pixels; - - width: pixels; - height: pixels; - padding: margins; - - textTop: pixels; - downTextTop: pixels; - - icon: icon; - - font: font; -} - -Checkbox { - textFg: color; - textBg: color; - - checkBg: color; - checkFg: color; - checkFgOver: color; - checkFgActive: color; - - width: pixels; - height: pixels; - - textPosition: point; - diameter: pixels; - thickness: pixels; - checkIcon: icon; - - font: font; - duration: int; -} - -Radiobutton { - textFg: color; - textBg: color; - - checkBg: color; - checkFg: color; - checkFgOver: color; - checkFgActive: color; - - width: pixels; - height: pixels; - - textPosition: point; - diameter: pixels; - thickness: pixels; - checkSkip: pixels; - - font: font; - duration: int; -} - -InputArea { - textBg: color; - textFg: color; - textMargins: margins; - - placeholderFg: color; - placeholderFgActive: color; - placeholderMargins: margins; - placeholderAlign: align; - placeholderShift: pixels; - - duration: int; - - borderFg: color; - borderFgActive: color; - borderFgError: color; - - border: pixels; - borderActive: pixels; - borderError: pixels; - - font: font; - - width: pixels; - heightMin: pixels; - heightMax: pixels; -} - -InputField { - textBg: color; - textFg: color; - textMargins: margins; - textAlign: align; - - placeholderFg: color; - placeholderFgActive: color; - placeholderMargins: margins; - placeholderAlign: align; - placeholderShift: pixels; - - duration: int; - - borderFg: color; - borderFgActive: color; - borderFgError: color; - - border: pixels; - borderActive: pixels; - borderError: pixels; - - font: font; - - width: pixels; - height: pixels; -} - -PeerAvatarButton { - size: pixels; - photoSize: pixels; -} - -OutlineButton { - outlineWidth: pixels; - outlineFg: color; - outlineFgOver: color; - - textBg: color; - textBgOver: color; - - textFg: color; - textFgOver: color; - - font: font; - padding: margins; -} diff --git a/Telegram/Resources/colors.palette b/Telegram/Resources/colors.palette index 80a2b9a87..14b8d9d20 100644 --- a/Telegram/Resources/colors.palette +++ b/Telegram/Resources/colors.palette @@ -37,6 +37,7 @@ imageBgTransparent: #ffffff; // widgets activeButtonBg: windowActiveBg; activeButtonBgOver: #46b4eb; +activeButtonBgRipple: #177eb2; activeButtonFg: windowActiveFg; activeButtonFgOver: activeButtonFg; activeButtonSecondaryFg: #cceeff; @@ -44,6 +45,7 @@ activeButtonSecondaryFgOver: activeButtonSecondaryFg; lightButtonBg: windowBg; lightButtonBgOver: #edf7ff; +lightButtonBgRipple: #c7e1f6; lightButtonFg: #2b99d5; lightButtonFgOver: lightButtonFg; @@ -92,6 +94,11 @@ boxBlockTitleAdditionalFg: #808080; boxBlockTitleCloseFg: cancelIconFg; boxBlockTitleCloseFgOver: cancelIconFgOver; +attentionBoxButtonFg: #ea4b2f; +attentionBoxButtonFgOver: #ea4b2f; +attentionBoxButtonBgOver: #fff0ed; +attentionBoxButtonBgRipple: #efbcb2; + membersAboutLimitFg: windowSubTextFg; contactsBg: windowBg; @@ -188,6 +195,7 @@ historyReplyCancelFgOver: cancelIconFgOver; historyComposeButtonBg: historyComposeAreaBg; historyComposeButtonBgOver: #f5f5f5; +historyComposeButtonBgRipple: #e7e7e7; historyTextInFg: windowTextFg; historyTextOutFg: windowTextFg; diff --git a/Telegram/Resources/sample.tdesktop-theme b/Telegram/Resources/sample.tdesktop-theme index 533981196..e1f7da4ea 100644 --- a/Telegram/Resources/sample.tdesktop-theme +++ b/Telegram/Resources/sample.tdesktop-theme @@ -35,12 +35,14 @@ imageBg: #000000; imageBgTransparent: #ffffff; activeButtonBg: windowActiveBg; activeButtonBgOver: #46b4eb; +activeButtonBgRipple: #177eb2; activeButtonFg: windowActiveFg; activeButtonFgOver: activeButtonFg; activeButtonSecondaryFg: #cceeff; activeButtonSecondaryFgOver: activeButtonSecondaryFg; lightButtonBg: windowBg; lightButtonBgOver: #edf7ff; +lightButtonBgRipple: #c7e1f6; lightButtonFg: #2b99d5; lightButtonFgOver: lightButtonFg; menuBg: windowBg; @@ -77,6 +79,10 @@ boxBlockTitleFg: boxTitleFg; boxBlockTitleAdditionalFg: #808080; boxBlockTitleCloseFg: cancelIconFg; boxBlockTitleCloseFgOver: cancelIconFgOver; +attentionBoxButtonFg: #ea4b2f; +attentionBoxButtonFgOver: #ea4b2f; +attentionBoxButtonBgOver: #fff0ed; +attentionBoxButtonBgRipple: #efbcb2; membersAboutLimitFg: windowSubTextFg; contactsBg: windowBg; contactsBgOver: windowOverBg; @@ -155,6 +161,7 @@ historyReplyCancelFg: cancelIconFg; historyReplyCancelFgOver: cancelIconFgOver; historyComposeButtonBg: historyComposeAreaBg; historyComposeButtonBgOver: #f5f5f5; +historyComposeButtonBgRipple: #e7e7e7; historyTextInFg: windowTextFg; historyTextOutFg: windowTextFg; historyCaptionInFg: historyTextInFg; diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 4b0dbe013..f0e42625a 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,10,19,6 - PRODUCTVERSION 0,10,19,6 + FILEVERSION 0,10,19,7 + PRODUCTVERSION 0,10,19,7 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.10.19.6" + VALUE "FileVersion", "0.10.19.7" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.10.19.6" + VALUE "ProductVersion", "0.10.19.7" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 99ce4ed04..16592aafb 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,10,19,6 - PRODUCTVERSION 0,10,19,6 + FILEVERSION 0,10,19,7 + PRODUCTVERSION 0,10,19,7 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "0.10.19.6" + VALUE "FileVersion", "0.10.19.7" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.10.19.6" + VALUE "ProductVersion", "0.10.19.7" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 08b1669d1..05a25793a 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -1037,15 +1037,15 @@ void AppClass::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) { PreparedPhotoThumbs photoThumbs; QVector photoSizes; - QPixmap thumb = App::pixmapFromImageInPlace(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + auto thumb = App::pixmapFromImageInPlace(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation)); photoThumbs.insert('a', thumb); photoSizes.push_back(MTP_photoSize(MTP_string("a"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0))); - QPixmap medium = App::pixmapFromImageInPlace(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + auto medium = App::pixmapFromImageInPlace(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation)); photoThumbs.insert('b', medium); photoSizes.push_back(MTP_photoSize(MTP_string("b"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0))); - QPixmap full = QPixmap::fromImage(tosend, Qt::ColorOnly); + auto full = QPixmap::fromImage(tosend, Qt::ColorOnly); photoThumbs.insert('c', full); photoSizes.push_back(MTP_photoSize(MTP_string("c"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0))); diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 4e63886b7..1d9dcc0ea 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -31,6 +31,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/filedialog.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "mainwidget.h" #include "mainwindow.h" #include "apiwrap.h" diff --git a/Telegram/SourceFiles/boxes/addcontactbox.h b/Telegram/SourceFiles/boxes/addcontactbox.h index 6ce931baa..70f77c745 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.h +++ b/Telegram/SourceFiles/boxes/addcontactbox.h @@ -28,6 +28,10 @@ class FlatLabel; class ConfirmBox; namespace Ui { +class InputField; +class PhoneInput; +class InputArea; +class UsernameInput; class Checkbox; class Radiobutton; class LinkButton; @@ -64,9 +68,9 @@ private: UserData *_user = nullptr; QString _boxTitle; - ChildWidget _first; - ChildWidget _last; - ChildWidget _phone; + ChildWidget _first; + ChildWidget _last; + ChildWidget _phone; ChildWidget _save; ChildWidget _cancel; @@ -119,8 +123,8 @@ private: Animation _a_photoOver; bool _photoOver; - ChildWidget _title; - ChildWidget _description; + ChildWidget _title; + ChildWidget _description; QImage _photoBig; QPixmap _photoSmall; @@ -187,7 +191,7 @@ private: int32 _aboutPublicWidth, _aboutPublicHeight; Text _aboutPublic, _aboutPrivate; - ChildWidget _link; + ChildWidget _link; QRect _invitationLink; bool _linkOver; @@ -236,8 +240,8 @@ private: PeerData *_peer; QString _boxTitle; - ChildWidget _first; - ChildWidget _last; + ChildWidget _first; + ChildWidget _last; ChildWidget _save; ChildWidget _cancel; @@ -283,8 +287,8 @@ private: ChannelData *_channel; - ChildWidget _title; - ChildWidget _description; + ChildWidget _title; + ChildWidget _description; ChildWidget _sign; ChildWidget _publicLink; diff --git a/Telegram/SourceFiles/boxes/autolockbox.cpp b/Telegram/SourceFiles/boxes/autolockbox.cpp index f2b025c07..feaf0e0fe 100644 --- a/Telegram/SourceFiles/boxes/autolockbox.cpp +++ b/Telegram/SourceFiles/boxes/autolockbox.cpp @@ -28,6 +28,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" +#include "styles/style_boxes.h" AutoLockBox::AutoLockBox() : _close(this, lang(lng_box_ok), st::defaultBoxButton) { diff --git a/Telegram/SourceFiles/boxes/backgroundbox.cpp b/Telegram/SourceFiles/boxes/backgroundbox.cpp index 30a0851b2..6a20c8ecb 100644 --- a/Telegram/SourceFiles/boxes/backgroundbox.cpp +++ b/Telegram/SourceFiles/boxes/backgroundbox.cpp @@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "window/window_theme.h" #include "styles/style_overview.h" +#include "styles/style_boxes.h" BackgroundBox::BackgroundBox() : ItemListBox(st::backgroundScroll) , _inner(this) { diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 3db3c34c3..c8af5320a 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -23,6 +23,48 @@ using "basic.style"; using "ui/widgets/widgets.style"; using "intro/intro.style"; +defaultBoxButton: RoundButton { + textFg: #2f9fea; + textFgOver: #2f9fea; + secondaryTextFg: #2f9fea; + secondaryTextFgOver: #2f9fea; + textBg: boxBg; + textBgOver: lightButtonBgOver; + + width: -24px; + height: 36px; + padding: margins(0px, 0px, 0px, 0px); + + textTop: 8px; + downTextTop: 8px; + + font: boxButtonFont; + + ripple: RippleAnimation(defaultRippleAnimation) { + color: lightButtonBgRipple; + } +} + +cancelBoxButton: RoundButton(defaultBoxButton) { + textFg: #aeaeae; +} + +attentionBoxButton: RoundButton(defaultBoxButton) { + textFg: attentionBoxButtonFg; + textFgOver: attentionBoxButtonFgOver; + textBgOver: attentionBoxButtonBgOver; + + ripple: RippleAnimation(defaultRippleAnimation) { + color: attentionBoxButtonBgRipple; + } +} + +defaultBoxCheckbox: Checkbox(defaultCheckbox) { + width: -46px; + textPosition: point(34px, 1px); + font: boxTextFont; +} + boxBlockTitleHeight: 48px; boxBlockTitlePosition: point(18px, 14px); boxBlockTitleFont: font(boxFontSize semibold); @@ -46,6 +88,8 @@ boxLinkButton: LinkButton { overFont: font(boxFontSize underline); } +boxOptionListPadding: margins(2px, 20px, 2px, 2px); + confirmInviteTitle: flatLabel(labelDefFlat) { font: font(16px semibold); align: align(center); @@ -281,7 +325,7 @@ sessionTerminateAllButton: LinkButton(boxLinkButton) { passcodeHeaderFont: font(19px); passcodeHeaderHeight: 80px; -passcodeInput: flatInput(inpIntroPhone) { +passcodeInput: FlatInput(introPhone) { } passcodeSubmit: RoundButton(introNextButton) { width: 225px; @@ -340,3 +384,45 @@ aboutLabel: flatLabel(labelDefFlat) { aboutTextStyle: textStyle(defaultTextStyle) { lineHeight: 22px; } + +editTextArea: InputArea(defaultInputArea) { + textMargins: margins(1px, 6px, 1px, 4px); + heightMax: 256px; +} + +confirmCaptionArea: InputArea(defaultInputArea) { + textMargins: margins(1px, 6px, 1px, 4px); + heightMax: 56px; +} +confirmBg: #f2f2f2; +confirmMaxHeight: 245px; +confirmCompressedSkip: 10px; + +connectionHostInputField: InputField(defaultInputField) { + width: 160px; +} +connectionPortInputField: InputField(defaultInputField) { + width: 55px; +} +connectionUserInputField: InputField(defaultInputField) { + width: 95px; +} +connectionPasswordInputField: InputField(defaultInputField) { + width: 120px; +} +connectionIPv6Skip: 11px; + +langsWidth: 256px; +langsButton: Radiobutton(defaultRadiobutton) { + width: 200px; +} + +backgroundPadding: 10px; +backgroundSize: size(108px, 193px); +backgroundScroll: flatScroll(boxScroll) { + round: 2px; + width: 10px; + deltax: 3px; + deltat: 10px; + deltab: 0px; +} diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index e713528af..ab5234175 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -29,6 +29,11 @@ class Checkbox; class RoundButton; } // namespace Ui +namespace st { +extern const style::RoundButton &defaultBoxButton; +extern const style::RoundButton &cancelBoxButton; +} // namespace style + class InformBox; class ConfirmBox : public AbstractBox, public ClickHandlerHost { Q_OBJECT diff --git a/Telegram/SourceFiles/boxes/confirmphonebox.cpp b/Telegram/SourceFiles/boxes/confirmphonebox.cpp index 36d0871a0..1f0b6a16e 100644 --- a/Telegram/SourceFiles/boxes/confirmphonebox.cpp +++ b/Telegram/SourceFiles/boxes/confirmphonebox.cpp @@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_boxes.h" #include "boxes/confirmbox.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "mainwidget.h" #include "lang.h" @@ -106,7 +107,7 @@ void ConfirmPhoneBox::launch() { } _about->setMarkedText(aboutText); - _code = new InputField(this, st::confirmPhoneCodeField, lang(lng_code_ph)); + _code.create(this, st::confirmPhoneCodeField, lang(lng_code_ph)); _send.create(this, lang(lng_confirm_phone_send), st::defaultBoxButton); _cancel.create(this, lang(lng_cancel), st::cancelBoxButton); @@ -300,6 +301,10 @@ void ConfirmPhoneBox::resizeEvent(QResizeEvent *e) { AbstractBox::resizeEvent(e); } +void ConfirmPhoneBox::doSetInnerFocus() { + _code->setFocus(); +} + ConfirmPhoneBox::~ConfirmPhoneBox() { if (_sendCodeRequestId) { MTP::cancel(_sendCodeRequestId); diff --git a/Telegram/SourceFiles/boxes/confirmphonebox.h b/Telegram/SourceFiles/boxes/confirmphonebox.h index 6c3788262..a48baf6f6 100644 --- a/Telegram/SourceFiles/boxes/confirmphonebox.h +++ b/Telegram/SourceFiles/boxes/confirmphonebox.h @@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org class FlatLabel; namespace Ui { +class InputField; class RoundButton; } // namespace Ui @@ -47,9 +48,7 @@ protected: void showAll() override { showChildren(); } - void doSetInnerFocus() override { - _code->setFocus(); - } + void doSetInnerFocus() override; private: ConfirmPhoneBox(QWidget *parent, const QString &phone, const QString &hash); @@ -96,7 +95,7 @@ private: ChildWidget _about = { nullptr }; ChildWidget _send = { nullptr }; ChildWidget _cancel = { nullptr }; - ChildWidget _code = { nullptr }; + ChildWidget _code = { nullptr }; // Flag for not calling onTextChanged() recursively. bool _fixing = false; diff --git a/Telegram/SourceFiles/boxes/connectionbox.cpp b/Telegram/SourceFiles/boxes/connectionbox.cpp index cd481d302..5e22dc7b5 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.cpp +++ b/Telegram/SourceFiles/boxes/connectionbox.cpp @@ -27,7 +27,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "history/history_location_manager.h" +#include "styles/style_boxes.h" ConnectionBox::ConnectionBox() : AbstractBox(st::boxWidth) , _hostInput(this, st::connectionHostInputField, lang(lng_connection_host_ph), Global::ConnectionProxy().host) diff --git a/Telegram/SourceFiles/boxes/connectionbox.h b/Telegram/SourceFiles/boxes/connectionbox.h index 9e0e3de88..65ae47359 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.h +++ b/Telegram/SourceFiles/boxes/connectionbox.h @@ -23,6 +23,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "abstractbox.h" namespace Ui { +class InputField; +class PortInput; +class PasswordInput; class Checkbox; class Radiobutton; class RoundButton; @@ -47,10 +50,10 @@ protected: void doSetInnerFocus() override; private: - ChildWidget _hostInput; - ChildWidget _portInput; - ChildWidget _userInput; - ChildWidget _passwordInput; + ChildWidget _hostInput; + ChildWidget _portInput; + ChildWidget _userInput; + ChildWidget _passwordInput; ChildWidget _autoRadio; ChildWidget _httpProxyRadio; ChildWidget _tcpProxyRadio; diff --git a/Telegram/SourceFiles/boxes/languagebox.cpp b/Telegram/SourceFiles/boxes/languagebox.cpp index 3ac63ad6c..e07104148 100644 --- a/Telegram/SourceFiles/boxes/languagebox.cpp +++ b/Telegram/SourceFiles/boxes/languagebox.cpp @@ -29,6 +29,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "mainwindow.h" #include "langloaderplain.h" +#include "styles/style_boxes.h" LanguageBox::LanguageBox() : _close(this, lang(lng_box_ok), st::defaultBoxButton) { diff --git a/Telegram/SourceFiles/boxes/passcodebox.cpp b/Telegram/SourceFiles/boxes/passcodebox.cpp index 92a8a2ebf..54e305519 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.cpp +++ b/Telegram/SourceFiles/boxes/passcodebox.cpp @@ -27,6 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localstorage.h" #include "styles/style_boxes.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" PasscodeBox::PasscodeBox(bool turningOff) : AbstractBox(st::boxWidth) , _replacedBy(0) diff --git a/Telegram/SourceFiles/boxes/passcodebox.h b/Telegram/SourceFiles/boxes/passcodebox.h index 53ce16438..9c15dbb94 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.h +++ b/Telegram/SourceFiles/boxes/passcodebox.h @@ -23,6 +23,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "abstractbox.h" namespace Ui { +class InputField; +class PasswordInput; class LinkButton; class RoundButton; } // namespace Ui @@ -81,11 +83,11 @@ private: ChildWidget _saveButton; ChildWidget _cancelButton; - ChildWidget _oldPasscode; - ChildWidget _newPasscode; - ChildWidget _reenterPasscode; - ChildWidget _passwordHint; - ChildWidget _recoverEmail; + ChildWidget _oldPasscode; + ChildWidget _newPasscode; + ChildWidget _reenterPasscode; + ChildWidget _passwordHint; + ChildWidget _recoverEmail; ChildWidget _recover; QString _oldError, _newError, _emailError; @@ -123,7 +125,7 @@ private: ChildWidget _saveButton; ChildWidget _cancelButton; - ChildWidget _recoverCode; + ChildWidget _recoverCode; QString _error; diff --git a/Telegram/SourceFiles/boxes/photocropbox.cpp b/Telegram/SourceFiles/boxes/photocropbox.cpp index 755c36dc0..60e3d031d 100644 --- a/Telegram/SourceFiles/boxes/photocropbox.cpp +++ b/Telegram/SourceFiles/boxes/photocropbox.cpp @@ -27,6 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "photocropbox.h" #include "fileuploader.h" #include "ui/widgets/buttons.h" +#include "styles/style_boxes.h" PhotoCropBox::PhotoCropBox(const QImage &img, const PeerId &peer) : AbstractBox() , _downState(0) diff --git a/Telegram/SourceFiles/boxes/photosendbox.cpp b/Telegram/SourceFiles/boxes/photosendbox.cpp index 3ebb1446a..24acd18f0 100644 --- a/Telegram/SourceFiles/boxes/photosendbox.cpp +++ b/Telegram/SourceFiles/boxes/photosendbox.cpp @@ -28,7 +28,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "history/history_media_types.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "styles/style_history.h" +#include "styles/style_boxes.h" PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxWideWidth) , _file(file) @@ -141,7 +143,7 @@ PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxW updateBoxSize(); _caption->setMaxLength(MaxPhotoCaption); - _caption->setCtrlEnterSubmit(CtrlEnterSubmitBoth); + _caption->setCtrlEnterSubmit(Ui::CtrlEnterSubmitBoth); connect(_compressed, SIGNAL(changed()), this, SLOT(onCompressedChange())); connect(_caption, SIGNAL(resized()), this, SLOT(onCaptionResized())); connect(_caption, SIGNAL(submitted(bool)), this, SLOT(onSend(bool))); @@ -493,13 +495,13 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth) if (_animated || _photo || _doc) { _field.create(this, st::confirmCaptionArea, lang(lng_photo_caption), caption); _field->setMaxLength(MaxPhotoCaption); - _field->setCtrlEnterSubmit(CtrlEnterSubmitBoth); + _field->setCtrlEnterSubmit(Ui::CtrlEnterSubmitBoth); } else { auto original = msg->originalText(); QString text = textApplyEntities(original.text, original.entities); _field.create(this, st::editTextArea, lang(lng_photo_caption), text); // _field->setMaxLength(MaxMessageSize); // entities can make text in input field larger but still valid - _field->setCtrlEnterSubmit(cCtrlEnter() ? CtrlEnterSubmitCtrlEnter : CtrlEnterSubmitEnter); + _field->setCtrlEnterSubmit(cCtrlEnter() ? Ui::CtrlEnterSubmitCtrlEnter : Ui::CtrlEnterSubmitEnter); } updateBoxSize(); connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSave(bool))); diff --git a/Telegram/SourceFiles/boxes/photosendbox.h b/Telegram/SourceFiles/boxes/photosendbox.h index 04b424e4a..64dd01048 100644 --- a/Telegram/SourceFiles/boxes/photosendbox.h +++ b/Telegram/SourceFiles/boxes/photosendbox.h @@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Ui { class Checkbox; class RoundButton; +class InputArea; } // namespace Ui class PhotoSendBox : public AbstractBox { @@ -57,7 +58,7 @@ private: QPixmap _thumb; - ChildWidget _caption; + ChildWidget _caption; bool _compressedFromSettings; ChildWidget _compressed; @@ -108,7 +109,7 @@ private: QPixmap _thumb; - ChildWidget _field = { nullptr }; + ChildWidget _field = { nullptr }; ChildWidget _save; ChildWidget _cancel; diff --git a/Telegram/SourceFiles/boxes/report_box.cpp b/Telegram/SourceFiles/boxes/report_box.cpp index 4b56c3f16..7f4e34cc5 100644 --- a/Telegram/SourceFiles/boxes/report_box.cpp +++ b/Telegram/SourceFiles/boxes/report_box.cpp @@ -27,6 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/confirmbox.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "mainwindow.h" ReportBox::ReportBox(ChannelData *channel) : AbstractBox(st::boxWidth) @@ -75,9 +76,9 @@ void ReportBox::resizeEvent(QResizeEvent *e) { void ReportBox::onChange() { if (_reasonOther->checked()) { if (!_reasonOtherText) { - _reasonOtherText = new InputArea(this, st::profileReportReasonOther, lang(lng_report_reason_description)); + _reasonOtherText.create(this, st::profileReportReasonOther, lang(lng_report_reason_description)); _reasonOtherText->show(); - _reasonOtherText->setCtrlEnterSubmit(CtrlEnterSubmitBoth); + _reasonOtherText->setCtrlEnterSubmit(Ui::CtrlEnterSubmitBoth); _reasonOtherText->setMaxLength(MaxPhotoCaption); _reasonOtherText->resize(width() - (st::boxPadding.left() + st::boxOptionListPadding.left() + st::boxPadding.right()), _reasonOtherText->height()); diff --git a/Telegram/SourceFiles/boxes/report_box.h b/Telegram/SourceFiles/boxes/report_box.h index 8e631eb38..b02bee021 100644 --- a/Telegram/SourceFiles/boxes/report_box.h +++ b/Telegram/SourceFiles/boxes/report_box.h @@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Ui { class Radiobutton; class RoundButton; +class InputArea; } // namespace Ui class ReportBox : public AbstractBox, public RPCSender { @@ -59,7 +60,7 @@ private: ChildWidget _reasonViolence; ChildWidget _reasonPornography; ChildWidget _reasonOther; - ChildWidget _reasonOtherText = { nullptr }; + ChildWidget _reasonOtherText = { nullptr }; ChildWidget _report, _cancel; diff --git a/Telegram/SourceFiles/boxes/usernamebox.cpp b/Telegram/SourceFiles/boxes/usernamebox.cpp index d4c1a964c..b0772ea66 100644 --- a/Telegram/SourceFiles/boxes/usernamebox.cpp +++ b/Telegram/SourceFiles/boxes/usernamebox.cpp @@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "mainwindow.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "styles/style_boxes.h" UsernameBox::UsernameBox() : AbstractBox(st::boxWidth), diff --git a/Telegram/SourceFiles/boxes/usernamebox.h b/Telegram/SourceFiles/boxes/usernamebox.h index 05beb5067..d26a62fc2 100644 --- a/Telegram/SourceFiles/boxes/usernamebox.h +++ b/Telegram/SourceFiles/boxes/usernamebox.h @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "abstractbox.h" namespace Ui { +class UsernameInput; class RoundButton; class LinkButton; } // namespace Ui @@ -60,7 +61,7 @@ private: ChildWidget _save; ChildWidget _cancel; - ChildWidget _username; + ChildWidget _username; ChildWidget _link; mtpRequestId _saveRequestId, _checkRequestId; diff --git a/Telegram/SourceFiles/codegen/style/generator.cpp b/Telegram/SourceFiles/codegen/style/generator.cpp index 379022939..ed05284d9 100644 --- a/Telegram/SourceFiles/codegen/style/generator.cpp +++ b/Telegram/SourceFiles/codegen/style/generator.cpp @@ -651,10 +651,19 @@ bool Generator::writeIncludesInSource() { return true; } - bool result = module_.enumIncludes([this](const Module &module) -> bool { - source_->include(moduleBaseName(module) + ".h"); + auto includes = QStringList(); + std::function collector = [this, &collector, &includes](const Module &module) { + module.enumIncludes(collector); + auto base = moduleBaseName(module); + if (!includes.contains(base)) { + includes.push_back(base); + } return true; - }); + }; + auto result = module_.enumIncludes(collector); + for (auto base : includes) { + source_->include(base + ".h"); + } source_->newline(); return result; } diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index e9ddd6088..fb6796711 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/utils.h" -#define BETA_VERSION_MACRO (10019006ULL) +#define BETA_VERSION_MACRO (10019007ULL) constexpr int AppVersion = 10020; constexpr str_const AppVersionStr = "0.10.20"; diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index 6179e45b2..9cd3c617d 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "data/data_drafts.h" +#include "ui/widgets/input_fields.h" #include "historywidget.h" #include "mainwidget.h" #include "localstorage.h" @@ -30,6 +31,13 @@ namespace { } // namespace +Draft::Draft(const Ui::FlatTextarea *field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequestId) + : textWithTags(field->getTextWithTags()) + , msgId(msgId) + , cursor(field) + , previewCancelled(previewCancelled) { +} + void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft) { auto history = App::history(peerId); auto text = qs(draft.vmessage); diff --git a/Telegram/SourceFiles/data/data_drafts.h b/Telegram/SourceFiles/data/data_drafts.h index 858207248..d5b369bc7 100644 --- a/Telegram/SourceFiles/data/data_drafts.h +++ b/Telegram/SourceFiles/data/data_drafts.h @@ -20,6 +20,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +namespace Ui { +class FlatTextarea; +} // namespace Ui + namespace Data { void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft); @@ -35,12 +39,8 @@ struct Draft { , previewCancelled(previewCancelled) , saveRequestId(saveRequestId) { } - Draft(const FlatTextarea *field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequestId = 0) - : textWithTags(field->getTextWithTags()) - , msgId(msgId) - , cursor(field) - , previewCancelled(previewCancelled) { - } + Draft(const Ui::FlatTextarea *field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequestId = 0); + QDateTime date; TextWithTags textWithTags; MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 4a68d2998..d4399ceed 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -84,6 +84,10 @@ dialogsMenuToggle: IconButton { iconOver: icon {{ "dialogs_menu", dialogsMenuIconFgOver }}; iconPosition: point(6px, 6px); iconPositionDown: point(6px, 6px); + + rippleAreaPosition: point(0px, 0px); + rippleAreaSize: 32px; + ripple: defaultRippleAnimation; } dialogsLock: IconButton(dialogsMenuToggle) { icon: icon {{ "dialogs_lock", dialogsMenuIconFg }}; @@ -92,7 +96,7 @@ dialogsLock: IconButton(dialogsMenuToggle) { dialogsUnlockIcon: icon {{ "dialogs_unlock", dialogsMenuIconFg }}; dialogsUnlockIconOver: icon {{ "dialogs_unlock", dialogsMenuIconFgOver }}; -dialogsFilter: flatInput(inpDefGray) { +dialogsFilter: FlatInput(defaultFlatInput) { font: font(fsize); bgColor: #f2f2f2; phColor: #949494; @@ -102,9 +106,15 @@ dialogsFilter: flatInput(inpDefGray) { height: 32px; textMrg: margins(12px, 3px, 30px, 3px); } -dialogsCancelSearch: IconButton(dialogsMenuToggle) { - icon: icon {{ "dialogs_cancel_search", dialogsMenuIconFg, point(0px, 1px) }}; - iconOver: icon {{ "dialogs_cancel_search", dialogsMenuIconFgOver, point(0px, 1px) }}; +dialogsCancelSearchInPeer: IconButton(dialogsMenuToggle) { + icon: icon {{ "dialogs_cancel_search", dialogsMenuIconFg }}; + iconOver: icon {{ "dialogs_cancel_search", dialogsMenuIconFgOver }}; + + iconPosition: point(7px, 7px); + iconPositionDown: point(7px, 7px); +} +dialogsCancelSearch: IconButton(dialogsCancelSearchInPeer) { + rippleAreaSize: 0px; } dialogsMenu: Menu(defaultMenu) { @@ -183,6 +193,10 @@ dialogsUpdateButton: FlatButton { font: semiboldFont; overFont: semiboldFont; + + ripple: RippleAnimation(defaultRippleAnimation) { + color: activeButtonBgRipple; + } } dialogsForwardHeight: 32px; diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 32fd61b7c..182930f38 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -40,6 +40,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localstorage.h" #include "apiwrap.h" #include "ui/widgets/dropdown_menu.h" +#include "ui/widgets/input_fields.h" #include "autoupdater.h" DialogsInner::DialogsInner(QWidget *parent, QWidget *main) : SplittedWidget(parent) @@ -47,7 +48,7 @@ DialogsInner::DialogsInner(QWidget *parent, QWidget *main) : SplittedWidget(pare , contactsNoDialogs(std_::make_unique(Dialogs::SortMode::Name)) , contacts(std_::make_unique(Dialogs::SortMode::Name)) , _addContactLnk(this, lang(lng_add_contact_button)) -, _cancelSearchInPeer(this, st::dialogsCancelSearch) { +, _cancelSearchInPeer(this, st::dialogsCancelSearchInPeer) { if (Global::DialogsModeEnabled()) { importantDialogs = std_::make_unique(Dialogs::SortMode::Date); } diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index a708116ca..897416fc1 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -34,6 +34,7 @@ class PopupMenu; class DropdownMenu; class FlatButton; class LinkButton; +class FlatInput; } // namespace Ui enum DialogsSearchRequestType { @@ -332,7 +333,7 @@ private: ChildWidget _forwardCancel = { nullptr }; ChildWidget _mainMenuToggle; ChildWidget _mainMenu = { nullptr }; - ChildWidget _filter; + ChildWidget _filter; ChildWidget _cancelSearch; ChildWidget _lockUnlock; ChildWidget _scroll; diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 6dd45ecd8..5cc99a617 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -152,7 +152,30 @@ struct SendAction { int32 progress; }; -using TextWithTags = FlatTextarea::TextWithTags; +struct TextWithTags { + struct Tag { + int offset, length; + QString id; + }; + using Tags = QVector; + + QString text; + Tags tags; +}; + +inline bool operator==(const TextWithTags::Tag &a, const TextWithTags::Tag &b) { + return (a.offset == b.offset) && (a.length == b.length) && (a.id == b.id); +} +inline bool operator!=(const TextWithTags::Tag &a, const TextWithTags::Tag &b) { + return !(a == b); +} + +inline bool operator==(const TextWithTags &a, const TextWithTags &b) { + return (a.text == b.text) && (a.tags == b.tags); +} +inline bool operator!=(const TextWithTags &a, const TextWithTags &b) { + return !(a == b); +} namespace Data { struct Draft; diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index 70b84ef5b..32eb6c452 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -136,7 +136,7 @@ historyPeer8UserpicBg: #f7b37c; historyPeer8UserpicFg: #de8d62; historyPeer8UserpicPerson: icon {{ size(120px, 120px), historyPeer8UserpicBg }, { "userpic_person", historyPeer8UserpicFg }}; -historyComposeField: flatTextarea { +historyComposeField: FlatTextarea { textColor: #000000; bgColor: historyComposeAreaBg; align: align(left); @@ -170,12 +170,16 @@ historyComposeButton: FlatButton { width: -32px; height: 46px; - textTop: 12px; - overTextTop: 12px; - downTextTop: 13px; + textTop: 14px; + overTextTop: 14px; + downTextTop: 14px; font: semiboldFont; overFont: semiboldFont; + + ripple: RippleAnimation(defaultRippleAnimation) { + color: historyComposeButtonBgRipple; + } } historyUnblock: FlatButton(historyComposeButton) { color: #d15948; @@ -290,23 +294,6 @@ historyInlineBotCancel: IconButton(historyReplyCancel) { height: 46px; } -topBarSearch: IconButton { - width: 44px; - height: topBarHeight; - - icon: icon {{ "title_search", menuIconFg }}; - iconOver: icon {{ "title_search", menuIconFgOver }}; - - iconPosition: point(13px, 18px); - iconPositionDown: point(13px, 18px); -} -topBarMenuToggle: IconButton(topBarSearch) { - icon: icon {{ "title_menu_dots", menuIconFg }}; - iconOver: icon {{ "title_menu_dots", menuIconFgOver }}; - - iconPosition: point(18px, 17px); - iconPositionDown: point(18px, 17px); -} reportSpamHide: FlatButton { duration: 200; cursor: cursor(pointer); @@ -327,17 +314,6 @@ reportSpamHide: FlatButton { font: font(fsize); overFont: font(fsize underline); } -reportSpamButton: FlatButton(reportSpamHide) { - textTop: 6px; - overTextTop: 6px; - downTextTop: 7px; - - width: -50px; - height: 30px; - - bgColor: #888888; - overBgColor: #7b7b7b; -} reportSpamSeparator: 30px; reportSpamBg: #fffffff0; reportSpamFg: #000000; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index e250eb2bb..ba8a3b016 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -23,6 +23,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_history.h" #include "styles/style_dialogs.h" +#include "styles/style_window.h" +#include "styles/style_boxes.h" #include "boxes/confirmbox.h" #include "boxes/photosendbox.h" #include "boxes/sharebox.h" @@ -77,13 +79,13 @@ QMimeData *mimeDataFromTextWithEntities(const TextWithEntities &forClipboard) { for (auto &tag : tags) { tag.id = mimeTagFromTag(tag.id); } - result->setData(FlatTextarea::tagsMimeType(), FlatTextarea::serializeTagsList(tags)); + result->setData(Ui::FlatTextarea::tagsMimeType(), Ui::FlatTextarea::serializeTagsList(tags)); } return result; } // For mention tags save and validate userId, ignore tags for different userId. -class FieldTagMimeProcessor : public FlatTextarea::TagMimeProcessor { +class FieldTagMimeProcessor : public Ui::FlatTextarea::TagMimeProcessor { public: QString mimeTagFromTag(const QString &tagId) override { return ::mimeTagFromTag(tagId); @@ -2302,7 +2304,7 @@ void HistoryInner::onParentGeometryChanged() { } } -MessageField::MessageField(HistoryWidget *history, const style::flatTextarea &st, const QString &ph, const QString &val) : FlatTextarea(history, st, ph, val), history(history) { +MessageField::MessageField(HistoryWidget *history, const style::FlatTextarea &st, const QString &ph, const QString &val) : Ui::FlatTextarea(history, st, ph, val), history(history) { setMinHeight(st::historySend.height - 2 * st::historySendPadding); setMaxHeight(st::historyComposeFieldMaxHeight); } @@ -2976,7 +2978,7 @@ QPoint SilentToggle::tooltipPos() const { return QCursor::pos(); } -EntitiesInText entitiesFromTextTags(const FlatTextarea::TagList &tags) { +EntitiesInText entitiesFromTextTags(const TextWithTags::Tags &tags) { EntitiesInText result; if (tags.isEmpty()) { return result; @@ -3229,14 +3231,14 @@ void HistoryWidget::updateInlineBotQuery() { MTP::cancel(_inlineBotResolveRequestId); _inlineBotResolveRequestId = 0; } - if (bot == LookingUpInlineBot) { - _inlineBot = LookingUpInlineBot; + if (bot == Ui::LookingUpInlineBot) { + _inlineBot = Ui::LookingUpInlineBot; // Notify::inlineBotRequesting(true); _inlineBotResolveRequestId = MTP::send(MTPcontacts_ResolveUsername(MTP_string(_inlineBotUsername)), rpcDone(&HistoryWidget::inlineBotResolveDone), rpcFail(&HistoryWidget::inlineBotResolveFail, _inlineBotUsername)); return; } - } else if (bot == LookingUpInlineBot) { - if (_inlineBot == LookingUpInlineBot) { + } else if (bot == Ui::LookingUpInlineBot) { + if (_inlineBot == Ui::LookingUpInlineBot) { return; } bot = _inlineBot; @@ -4389,11 +4391,11 @@ void HistoryWidget::updateAfterDrag() { } void HistoryWidget::updateFieldSubmitSettings() { - FlatTextarea::SubmitSettings settings = FlatTextarea::SubmitSettings::Enter; + auto settings = Ui::FlatTextarea::SubmitSettings::Enter; if (_inlineBotCancel) { - settings = FlatTextarea::SubmitSettings::None; + settings = Ui::FlatTextarea::SubmitSettings::None; } else if (cCtrlEnter()) { - settings = FlatTextarea::SubmitSettings::CtrlEnter; + settings = Ui::FlatTextarea::SubmitSettings::CtrlEnter; } _field->setSubmitSettings(settings); } @@ -5156,9 +5158,9 @@ void HistoryWidget::preloadHistoryIfNeeded() { void HistoryWidget::onInlineBotCancel() { auto &textWithTags = _field->getTextWithTags(); if (textWithTags.text.size() > _inlineBotUsername.size() + 2) { - setFieldText({ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, FlatTextarea::AddToUndoHistory); + setFieldText({ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory); } else { - clearFieldText(TextUpdateEvent::SaveDraft, FlatTextarea::AddToUndoHistory); + clearFieldText(TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory); } } @@ -5883,7 +5885,7 @@ bool HistoryWidget::insertBotCommand(const QString &cmd, bool specialGif) { auto &textWithTags = _field->getTextWithTags(); if (specialGif) { if (textWithTags.text.trimmed() == '@' + cInlineGifBotUsername() && textWithTags.text.at(0) == '@') { - clearFieldText(TextUpdateEvent::SaveDraft, FlatTextarea::AddToUndoHistory); + clearFieldText(TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory); } } else { TextWithTags textWithTagsToSet; @@ -5905,7 +5907,7 @@ bool HistoryWidget::insertBotCommand(const QString &cmd, bool specialGif) { } } else { if (!specialGif || _field->isEmpty()) { - setFieldText({ toInsert, TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, FlatTextarea::AddToUndoHistory); + setFieldText({ toInsert, TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory); _field->setFocus(); return true; } @@ -6027,7 +6029,7 @@ void HistoryWidget::inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result) QString inlineBotUsername; auto query = _field->getInlineBotQuery(&bot, &inlineBotUsername); if (inlineBotUsername == _inlineBotUsername) { - if (bot == LookingUpInlineBot) { + if (bot == Ui::LookingUpInlineBot) { bot = resolvedBot; } } else { @@ -6223,7 +6225,7 @@ void HistoryWidget::onKbToggle(bool manual) { } void HistoryWidget::onCmdStart() { - setFieldText({ qsl("/"), TextWithTags::Tags() }, 0, FlatTextarea::AddToUndoHistory); + setFieldText({ qsl("/"), TextWithTags::Tags() }, 0, Ui::FlatTextarea::AddToUndoHistory); } void HistoryWidget::contextMenuEvent(QContextMenuEvent *e) { @@ -6502,7 +6504,7 @@ void HistoryWidget::clearInlineBot() { } void HistoryWidget::inlineBotChanged() { - bool isInlineBot = _inlineBot && (_inlineBot != LookingUpInlineBot); + bool isInlineBot = _inlineBot && (_inlineBot != Ui::LookingUpInlineBot); if (isInlineBot && !_inlineBotCancel) { _inlineBotCancel = std_::make_unique(this, st::historyInlineBotCancel); connect(_inlineBotCancel.get(), SIGNAL(clicked()), this, SLOT(onInlineBotCancel())); @@ -6532,7 +6534,7 @@ void HistoryWidget::onCheckFieldAutocomplete() { if (!_history || _a_show.animating()) return; bool start = false; - bool isInlineBot = _inlineBot && (_inlineBot != LookingUpInlineBot); + bool isInlineBot = _inlineBot && (_inlineBot != Ui::LookingUpInlineBot); QString query = isInlineBot ? QString() : _field->getMentionHashtagBotCommandPart(start); if (!query.isEmpty()) { if (query.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtagsAndBots(); @@ -6547,7 +6549,7 @@ void HistoryWidget::updateFieldPlaceholder() { _field->setPlaceholder(lang(lng_edit_message_text)); _send->setIcon(&st::historyEditSaveIcon, &st::historyEditSaveIconOver); } else { - if (_inlineBot && _inlineBot != LookingUpInlineBot) { + if (_inlineBot && _inlineBot != Ui::LookingUpInlineBot) { _field->setPlaceholder(_inlineBot->botInfo->inlinePlaceholder.mid(1), _inlineBot->username.size() + 2); } else { _field->setPlaceholder(lang((_history && _history->isChannel() && !_history->isMegagroup()) ? (_silent->checked() ? lng_broadcast_silent_ph : lng_broadcast_ph) : lng_message_ph)); @@ -7770,7 +7772,7 @@ void HistoryWidget::sendExistingPhoto(PhotoData *photo, const QString &caption) _field->setFocus(); } -void HistoryWidget::setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events, FlatTextarea::UndoHistoryAction undoHistoryAction) { +void HistoryWidget::setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction) { _textUpdateEvents = events; _field->setTextWithTags(textWithTags, undoHistoryAction); _field->moveCursor(QTextCursor::End); diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 0224b08ed..8d689301b 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localimageloader.h" #include "ui/effects/rect_shadow.h" #include "ui/widgets/tooltip.h" +#include "ui/widgets/input_fields.h" #include "history/history_common.h" #include "history/field_autocomplete.h" #include "window/section_widget.h" @@ -310,11 +311,11 @@ private: }; -class MessageField : public FlatTextarea { +class MessageField : public Ui::FlatTextarea { Q_OBJECT public: - MessageField(HistoryWidget *history, const style::flatTextarea &st, const QString &ph = QString(), const QString &val = QString()); + MessageField(HistoryWidget *history, const style::FlatTextarea &st, const QString &ph = QString(), const QString &val = QString()); void dropEvent(QDropEvent *e); bool canInsertFromMimeData(const QMimeData *source) const; void insertFromMimeData(const QMimeData *source); @@ -1014,8 +1015,8 @@ private: void writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft); void writeDrafts(History *history); - void setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events = 0, FlatTextarea::UndoHistoryAction undoHistoryAction = FlatTextarea::ClearUndoHistory); - void clearFieldText(TextUpdateEvents events = 0, FlatTextarea::UndoHistoryAction undoHistoryAction = FlatTextarea::ClearUndoHistory) { + void setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events = 0, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction = Ui::FlatTextarea::ClearUndoHistory); + void clearFieldText(TextUpdateEvents events = 0, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction = Ui::FlatTextarea::ClearUndoHistory) { setFieldText(TextWithTags(), events, undoHistoryAction); } diff --git a/Telegram/SourceFiles/intro/intro.style b/Telegram/SourceFiles/intro/intro.style index 70881640b..a39394f9c 100644 --- a/Telegram/SourceFiles/intro/intro.style +++ b/Telegram/SourceFiles/intro/intro.style @@ -39,7 +39,7 @@ introCountry: countryInput { bgColor: #f2f2f2; ptrSize: size(15px, 8px); textMrg: margins(16px, 5px, 16px, 15px); - font: inpDefFont; + font: defaultInputFont; align: align(left); } @@ -89,17 +89,17 @@ introNextButton: RoundButton(defaultActiveButton) { } introPhoneTop: 8px; -inpIntroCountryCode: flatInput(inpDefGray) { +introCountryCode: FlatInput(defaultFlatInput) { width: 70px; height: 41px; align: align(center); } -inpIntroPhone: flatInput(inpDefGray) { +introPhone: FlatInput(defaultFlatInput) { textMrg: margins(12px, 5px, 12px, 6px); width: 225px; height: 41px; } -inpIntroCode: flatInput(inpDefGray) { +introCode: FlatInput(defaultFlatInput) { textMrg: margins(12px, 5px, 12px, 6px); width: 106px; height: 41px; @@ -109,10 +109,10 @@ inpIntroCode: flatInput(inpDefGray) { phAlign: align(center); phShift: 0px; } -inpIntroName: flatInput(inpIntroPhone) { +introName: FlatInput(introPhone) { width: 192px; } -inpIntroPassword: flatInput(inpIntroPhone) { +introPassword: FlatInput(introPhone) { width: 300px; } diff --git a/Telegram/SourceFiles/intro/introcode.cpp b/Telegram/SourceFiles/intro/introcode.cpp index 982fe678d..88b75a2c0 100644 --- a/Telegram/SourceFiles/intro/introcode.cpp +++ b/Telegram/SourceFiles/intro/introcode.cpp @@ -28,7 +28,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/widgets/buttons.h" #include "styles/style_intro.h" -CodeInput::CodeInput(QWidget *parent, const style::flatInput &st, const QString &ph) : FlatInput(parent, st, ph) { +CodeInput::CodeInput(QWidget *parent, const style::FlatInput &st, const QString &ph) : Ui::FlatInput(parent, st, ph) { } void CodeInput::correctValue(const QString &was, QString &now) { @@ -80,7 +80,7 @@ IntroCode::IntroCode(IntroWidget *parent) : IntroStep(parent) , _desc(st::introTextSize.width()) , _noTelegramCode(this, lang(lng_code_no_telegram), st::introLink) , _noTelegramCodeRequestId(0) -, _code(this, st::inpIntroCode, lang(lng_code_ph)) +, _code(this, st::introCode, lang(lng_code_ph)) , _callTimer(this) , _callStatus(intro()->getCallStatus()) , _checkRequest(this) { diff --git a/Telegram/SourceFiles/intro/introcode.h b/Telegram/SourceFiles/intro/introcode.h index d3f894ccb..eb8a76a9d 100644 --- a/Telegram/SourceFiles/intro/introcode.h +++ b/Telegram/SourceFiles/intro/introcode.h @@ -21,19 +21,18 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "intro/introwidget.h" - -class FlatInput; +#include "ui/widgets/input_fields.h" namespace Ui { class RoundButton; class LinkButton; } // namespace Ui -class CodeInput final : public FlatInput { +class CodeInput final : public Ui::FlatInput { Q_OBJECT public: - CodeInput(QWidget *parent, const style::flatInput &st, const QString &ph); + CodeInput(QWidget *parent, const style::FlatInput &st, const QString &ph); signals: void codeEntered(); diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp index 11c3311b6..1eedf740a 100644 --- a/Telegram/SourceFiles/intro/introphone.cpp +++ b/Telegram/SourceFiles/intro/introphone.cpp @@ -49,8 +49,8 @@ IntroPhone::IntroPhone(IntroWidget *parent) : IntroStep(parent) , _a_error(animation(this, &IntroPhone::step_error)) , _next(this, lang(lng_intro_next), st::introNextButton) , _country(this, st::introCountry) -, _phone(this, st::inpIntroPhone) -, _code(this, st::inpIntroCountryCode) +, _phone(this, st::introPhone) +, _code(this, st::introCountryCode) , _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), FlatLabel::InitType::Rich, st::introErrorLabel, st::introErrorLabelTextStyle) , _checkRequest(this) { setVisible(false); @@ -110,7 +110,7 @@ void IntroPhone::resizeEvent(QResizeEvent *e) { _next->move((width() - _next->width()) / 2, st::introBtnTop); _country->move((width() - _country->width()) / 2, st::introTextTop + st::introTextSize.height() + st::introCountry.top); int phoneTop = _country->y() + _country->height() + st::introPhoneTop; - _phone->move((width() - _country->width()) / 2 + _country->width() - st::inpIntroPhone.width, phoneTop); + _phone->move((width() - _country->width()) / 2 + _country->width() - st::introPhone.width, phoneTop); _code->move((width() - _country->width()) / 2, phoneTop); } _signup->move((width() - _signup->width()) / 2, _next->y() + _next->height() + st::introErrorTop - ((st::introErrorLabelTextStyle.lineHeight - st::introErrorFont->height) / 2)); diff --git a/Telegram/SourceFiles/intro/introphone.h b/Telegram/SourceFiles/intro/introphone.h index 019251705..f4ebedf4c 100644 --- a/Telegram/SourceFiles/intro/introphone.h +++ b/Telegram/SourceFiles/intro/introphone.h @@ -25,6 +25,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "intro/introwidget.h" namespace Ui { +class PhonePartInput; +class CountryCodeInput; class RoundButton; } // namespace Ui @@ -76,8 +78,8 @@ private: QRect _textRect; ChildWidget _country; - ChildWidget _phone; - ChildWidget _code; + ChildWidget _phone; + ChildWidget _code; ChildWidget _signup; QPixmap _signupCache; diff --git a/Telegram/SourceFiles/intro/intropwdcheck.cpp b/Telegram/SourceFiles/intro/intropwdcheck.cpp index 0dc6ee140..f1e974cda 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.cpp +++ b/Telegram/SourceFiles/intro/intropwdcheck.cpp @@ -22,12 +22,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "intro/intropwdcheck.h" #include "styles/style_intro.h" +#include "styles/style_boxes.h" #include "ui/filedialog.h" #include "boxes/confirmbox.h" #include "lang.h" #include "application.h" #include "intro/introsignup.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" IntroPwdCheck::IntroPwdCheck(IntroWidget *parent) : IntroStep(parent) , a_errorAlpha(0) @@ -36,8 +38,8 @@ IntroPwdCheck::IntroPwdCheck(IntroWidget *parent) : IntroStep(parent) , _salt(parent->getPwdSalt()) , _hasRecovery(parent->getHasRecovery()) , _hint(parent->getPwdHint()) -, _pwdField(this, st::inpIntroPassword, lang(lng_signin_password)) -, _codeField(this, st::inpIntroPassword, lang(lng_signin_code)) +, _pwdField(this, st::introPassword, lang(lng_signin_password)) +, _codeField(this, st::introPassword, lang(lng_signin_code)) , _toRecover(this, lang(lng_signin_recover)) , _toPassword(this, lang(lng_signin_try_password)) , _reset(this, lang(lng_signin_reset_account), st::introResetLink) diff --git a/Telegram/SourceFiles/intro/intropwdcheck.h b/Telegram/SourceFiles/intro/intropwdcheck.h index 8d4d1d1c0..b25fca221 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.h +++ b/Telegram/SourceFiles/intro/intropwdcheck.h @@ -22,9 +22,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "intro/introwidget.h" -class FlatInput; - namespace Ui { +class FlatInput; class RoundButton; class LinkButton; } // namespace Ui @@ -80,8 +79,8 @@ private: bool _hasRecovery; QString _hint, _emailPattern; - ChildWidget _pwdField; - ChildWidget _codeField; + ChildWidget _pwdField; + ChildWidget _codeField; ChildWidget _toRecover; ChildWidget _toPassword; ChildWidget _reset; diff --git a/Telegram/SourceFiles/intro/introsignup.cpp b/Telegram/SourceFiles/intro/introsignup.cpp index 450f28675..df3e6ed58 100644 --- a/Telegram/SourceFiles/intro/introsignup.cpp +++ b/Telegram/SourceFiles/intro/introsignup.cpp @@ -28,6 +28,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "application.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" IntroSignup::IntroSignup(IntroWidget *parent) : IntroStep(parent) , a_errorAlpha(0) @@ -35,8 +36,8 @@ IntroSignup::IntroSignup(IntroWidget *parent) : IntroStep(parent) , _a_error(animation(this, &IntroSignup::step_error)) , _a_photo(animation(this, &IntroSignup::step_photo)) , _next(this, lang(lng_intro_finish), st::introNextButton) -, _first(this, st::inpIntroName, lang(lng_signup_firstname)) -, _last(this, st::inpIntroName, lang(lng_signup_lastname)) +, _first(this, st::introName, lang(lng_signup_firstname)) +, _last(this, st::introName, lang(lng_signup_lastname)) , _invertOrder(langFirstNameGoesSecond()) , _checkRequest(this) { setVisible(false); diff --git a/Telegram/SourceFiles/intro/introsignup.h b/Telegram/SourceFiles/intro/introsignup.h index 8bfe59bfa..bbb60453a 100644 --- a/Telegram/SourceFiles/intro/introsignup.h +++ b/Telegram/SourceFiles/intro/introsignup.h @@ -20,11 +20,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "ui/flatinput.h" #include "intro/introwidget.h" namespace Ui { class RoundButton; +class FlatInput; } // namespace Ui class IntroSignup final : public IntroStep { @@ -72,8 +72,8 @@ private: QPixmap _photoSmall; int32 _phLeft, _phTop; - ChildWidget _first; - ChildWidget _last; + ChildWidget _first; + ChildWidget _last; QString _firstName, _lastName; mtpRequestId _sentRequest = 0; diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index c41b8ac55..f75a11e42 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -37,6 +37,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_intro.h" #include "autoupdater.h" #include "window/slide_animation.h" +#include "styles/style_boxes.h" IntroWidget::IntroWidget(QWidget *parent) : TWidget(parent) , _a_stage(animation(this, &IntroWidget::step_stage)) diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index 4dcf409f0..0cb6d7232 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -353,22 +353,22 @@ void FileLoadTask::process() { } if (!fullimage.isNull() && fullimage.width() > 0 && !song && !gif && !voice) { - int32 w = fullimage.width(), h = fullimage.height(); + auto w = fullimage.width(), h = fullimage.height(); attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h))); if (w < 20 * h && h < 20 * w) { if (animated) { attributes.push_back(MTP_documentAttributeAnimated()); } else if (_type != PrepareDocument) { - QPixmap thumb = (w > 100 || h > 100) ? App::pixmapFromImageInPlace(fullimage.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage); + auto thumb = (w > 100 || h > 100) ? App::pixmapFromImageInPlace(fullimage.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage); photoThumbs.insert('s', thumb); photoSizes.push_back(MTP_photoSize(MTP_string("s"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0))); - QPixmap medium = (w > 320 || h > 320) ? App::pixmapFromImageInPlace(fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage); + auto medium = (w > 320 || h > 320) ? App::pixmapFromImageInPlace(fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage); photoThumbs.insert('m', medium); photoSizes.push_back(MTP_photoSize(MTP_string("m"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0))); - QPixmap full = (w > 1280 || h > 1280) ? App::pixmapFromImageInPlace(fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage); + auto full = (w > 1280 || h > 1280) ? App::pixmapFromImageInPlace(fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage); photoThumbs.insert('y', full); photoSizes.push_back(MTP_photoSize(MTP_string("y"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0))); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index d56a65240..e9f776bc6 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -32,6 +32,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "lang.h" #include "media/media_audio.h" +#include "ui/widgets/input_fields.h" #include "application.h" #include "apiwrap.h" @@ -2369,8 +2370,8 @@ void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const Messa _writeMap(WriteMapFast); } - auto msgTags = FlatTextarea::serializeTagsList(localDraft.textWithTags.tags); - auto editTags = FlatTextarea::serializeTagsList(editDraft.textWithTags.tags); + auto msgTags = Ui::FlatTextarea::serializeTagsList(localDraft.textWithTags.tags); + auto editTags = Ui::FlatTextarea::serializeTagsList(editDraft.textWithTags.tags); int size = sizeof(quint64); size += Serialize::stringSize(localDraft.textWithTags.text) + Serialize::bytearraySize(msgTags) + 2 * sizeof(qint32); @@ -2476,8 +2477,8 @@ void readDraftsWithCursors(History *h) { return; } - msgData.tags = FlatTextarea::deserializeTagsList(msgTagsSerialized, msgData.text.size()); - editData.tags = FlatTextarea::deserializeTagsList(editTagsSerialized, editData.text.size()); + msgData.tags = Ui::FlatTextarea::deserializeTagsList(msgTagsSerialized, msgData.text.size()); + editData.tags = Ui::FlatTextarea::deserializeTagsList(editTagsSerialized, editData.text.size()); MessageCursor msgCursor, editCursor; _readDraftCursors(peer, msgCursor, editCursor); diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index ddcddf5a3..d871cc80a 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -84,7 +84,6 @@ int32 oldMapVersion(); int32 oldSettingsVersion(); -using TextWithTags = FlatTextarea::TextWithTags; struct MessageDraft { MessageDraft(MsgId msgId = 0, TextWithTags textWithTags = TextWithTags(), bool previewCancelled = false) : msgId(msgId) diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 32f8fba2a..17eddd37b 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "styles/style_dialogs.h" +#include "styles/style_history.h" #include "ui/buttons/peer_avatar_button.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" @@ -58,6 +59,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/qthelp_url.h" #include "window/window_theme.h" #include "window/player_wrap_widget.h" +#include "styles/style_boxes.h" StackItemSection::StackItemSection(std_::unique_ptr &&memento) : StackItem(nullptr) , _memento(std_::move(memento)) { diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index f6a68ca78..13b474c53 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_layout.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" +#include "styles/style_boxes.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/buttons.h" #include "core/zlib_help.h" diff --git a/Telegram/SourceFiles/media/media_clip_reader.h b/Telegram/SourceFiles/media/media_clip_reader.h index 0b3a8a395..925d0d3f3 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.h +++ b/Telegram/SourceFiles/media/media_clip_reader.h @@ -78,11 +78,12 @@ public: void start(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius); QPixmap current(int framew, int frameh, int outerw, int outerh, uint64 ms); QPixmap frameOriginal() const { - Frame *frame = frameToShow(); - if (!frame) return QPixmap(); - QPixmap result(frame ? QPixmap::fromImage(frame->original) : QPixmap()); - result.detach(); - return result; + if (auto frame = frameToShow()) { + auto result = QPixmap::fromImage(frame->original); + result.detach(); + return result; + } + return QPixmap(); } bool currentDisplayed() const { Frame *frame = frameToShow(); diff --git a/Telegram/SourceFiles/overview/overview.style b/Telegram/SourceFiles/overview/overview.style index a9fb2ac68..40750a84b 100644 --- a/Telegram/SourceFiles/overview/overview.style +++ b/Telegram/SourceFiles/overview/overview.style @@ -118,7 +118,7 @@ overviewLinksChecked: icon { { "overview_links_check", #ffffff, point(4px, 5px) }, }; -overviewFilter: flatInput(inpDefGray) { +overviewFilter: FlatInput(defaultFlatInput) { font: font(fsize); bgColor: #f2f2f2; phColor: #949494; diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 85f060e5d..0626583bd 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -30,6 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/widgets/popup_menu.h" #include "ui/widgets/tooltip.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "window/top_bar_widget.h" #include "window/window_theme.h" #include "lang.h" @@ -77,17 +78,17 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, PeerD setMouseTracking(true); connect(_cancelSearch, SIGNAL(clicked()), this, SLOT(onCancelSearch())); - connect(&_search, SIGNAL(cancelled()), this, SLOT(onCancel())); - connect(&_search, SIGNAL(changed()), this, SLOT(onSearchUpdate())); + connect(_search, SIGNAL(cancelled()), this, SLOT(onCancel())); + connect(_search, SIGNAL(changed()), this, SLOT(onSearchUpdate())); _searchTimer.setSingleShot(true); connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchMessages())); _cancelSearch->hide(); if (_type == OverviewLinks || _type == OverviewFiles) { - _search.show(); + _search->show(); } else { - _search.hide(); + _search->hide(); } } @@ -176,7 +177,7 @@ void OverviewInner::fixItemIndex(int32 ¤t, MsgId msgId) const { } void OverviewInner::searchReceived(SearchRequestType type, const MTPmessages_Messages &result, mtpRequestId req) { - if (!_search.text().isEmpty()) { + if (!_search->text().isEmpty()) { if (type == SearchFromStart) { SearchQueries::iterator i = _searchQueries.find(req); if (i != _searchQueries.cend()) { @@ -683,7 +684,7 @@ QPoint OverviewInner::mapMouseToItem(QPoint p, MsgId itemId, int32 itemIndex) { void OverviewInner::activate() { if (_type == OverviewLinks || _type == OverviewFiles) { - _search.setFocus(); + _search->setFocus(); } else { setFocus(); } @@ -1111,7 +1112,7 @@ void OverviewInner::mouseReleaseEvent(QMouseEvent *e) { } void OverviewInner::keyPressEvent(QKeyEvent *e) { - if ((_search.isHidden() || !_search.hasFocus()) && !_overview->isHidden() && e->key() == Qt::Key_Escape) { + if ((_search->isHidden() || !_search->hasFocus()) && !_overview->isHidden() && e->key() == Qt::Key_Escape) { onCancel(); } else if (e->key() == Qt::Key_Back) { App::main()->showBackFromStack(); @@ -1282,8 +1283,8 @@ int32 OverviewInner::resizeToWidth(int32 nwidth, int32 scrollTop, int32 minHeigh } _rowsLeft = (_width - _rowWidth) / 2; - _search.setGeometry(_rowsLeft, st::linksSearchMargin.top(), _rowWidth, _search.height()); - _cancelSearch->moveToLeft(_rowsLeft + _rowWidth - _cancelSearch->width(), _search.y()); + _search->setGeometry(_rowsLeft, st::linksSearchMargin.top(), _rowWidth, _search->height()); + _cancelSearch->moveToLeft(_rowsLeft + _rowWidth - _cancelSearch->width(), _search->y()); if (_type == OverviewPhotos || _type == OverviewVideos) { for (int32 i = 0, l = _items.size(); i < l; ++i) { @@ -1334,14 +1335,14 @@ void OverviewInner::switchType(MediaOverviewType type) { _type = type; _reversed = (_type != OverviewLinks && _type != OverviewFiles); if (_type == OverviewLinks || _type == OverviewFiles) { - _search.show(); + _search->show(); } else { - _search.hide(); + _search->hide(); } - if (!_search.getLastText().isEmpty()) { - _search.setText(QString()); - _search.updatePlaceholder(); + if (!_search->getLastText().isEmpty()) { + _search->setText(QString()); + _search->updatePlaceholder(); onSearchUpdate(); } _cancelSearch->hide(); @@ -1425,7 +1426,7 @@ void OverviewInner::saveContextFile() { bool OverviewInner::onSearchMessages(bool searchCache) { _searchTimer.stop(); - QString q = _search.text().trimmed(); + QString q = _search->text().trimmed(); if (q.isEmpty()) { if (_searchRequest) { _searchRequest = 0; @@ -1462,7 +1463,7 @@ void OverviewInner::onNeedSearchMessages() { } void OverviewInner::onSearchUpdate() { - QString filterText = (_type == OverviewLinks || _type == OverviewFiles) ? _search.text().trimmed() : QString(); + QString filterText = (_type == OverviewLinks || _type == OverviewFiles) ? _search->text().trimmed() : QString(); bool inSearch = !filterText.isEmpty(), changed = (inSearch != _inSearch); _inSearch = inSearch; @@ -1495,11 +1496,11 @@ void OverviewInner::onCancel() { } bool OverviewInner::onCancelSearch() { - if (_search.isHidden()) return false; - bool clearing = !_search.text().isEmpty(); + if (_search->isHidden()) return false; + bool clearing = !_search->text().isEmpty(); _cancelSearch->hide(); - _search.clear(); - _search.updatePlaceholder(); + _search->clear(); + _search->updatePlaceholder(); onSearchUpdate(); return clearing; } @@ -1796,7 +1797,7 @@ void OverviewInner::recountMargins() { _marginTop = st::playlistPadding; _marginBottom = qMax(_minHeight - _height - _marginTop, int32(st::playlistPadding)); } else if (_type == OverviewLinks || _type == OverviewFiles) { - _marginTop = st::linksSearchMargin.top() + _search.height() + st::linksSearchMargin.bottom(); + _marginTop = st::linksSearchMargin.top() + _search->height() + st::linksSearchMargin.bottom(); _marginBottom = qMax(_minHeight - _height - _marginTop, int32(st::playlistPadding)); } else { _marginBottom = st::playlistPadding; diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index aebbe33cb..b5480bf13 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -35,6 +35,7 @@ namespace Ui { class PlainShadow; class PopupMenu; class IconButton; +class FlatInput; } // namespace Ui class OverviewWidget; @@ -179,7 +180,7 @@ private: Overview::Layout::AbstractItem *layoutPrepare(const QDate &date, bool month); int32 setLayoutItem(int32 index, Overview::Layout::AbstractItem *item, int32 top); - FlatInput _search; + ChildWidget _search; ChildWidget _cancelSearch; QVector _results; int32 _itemsToBeLoaded; diff --git a/Telegram/SourceFiles/passcodewidget.cpp b/Telegram/SourceFiles/passcodewidget.cpp index 0c1d3de2e..62e89c118 100644 --- a/Telegram/SourceFiles/passcodewidget.cpp +++ b/Telegram/SourceFiles/passcodewidget.cpp @@ -27,6 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "application.h" #include "ui/text/text.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "styles/style_boxes.h" #include "window/slide_animation.h" diff --git a/Telegram/SourceFiles/passcodewidget.h b/Telegram/SourceFiles/passcodewidget.h index 3ccd192b7..ab41cde0a 100644 --- a/Telegram/SourceFiles/passcodewidget.h +++ b/Telegram/SourceFiles/passcodewidget.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once namespace Ui { +class FlatInput; class LinkButton; class RoundButton; } // namespace Ui @@ -55,7 +56,7 @@ private: anim::ivalue a_coordUnder, a_coordOver; anim::fvalue a_shadow; - ChildWidget _passcode; + ChildWidget _passcode; ChildWidget _submit; ChildWidget _logout; QString _error; diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index 1a8a56a23..110d5326a 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -436,7 +436,7 @@ void MainWindow::createGlobalMenu() { namespace { void _sendKeySequence(Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier) { QWidget *focused = QApplication::focusWidget(); - if (qobject_cast(focused) || qobject_cast(focused) || qobject_cast(focused)) { + if (qobject_cast(focused) || qobject_cast(focused) || qobject_cast(focused)) { QApplication::postEvent(focused, new QKeyEvent(QEvent::KeyPress, key, modifiers)); QApplication::postEvent(focused, new QKeyEvent(QEvent::KeyRelease, key, modifiers)); } @@ -498,11 +498,11 @@ void MainWindow::psMacUpdateMenu() { canUndo = edit->isUndoAvailable(); canRedo = edit->isRedoAvailable(); canPaste = !Application::clipboard()->text().isEmpty(); - } else if (auto edit = qobject_cast(focused)) { + } else if (auto edit = qobject_cast(focused)) { canCut = canCopy = canDelete = edit->textCursor().hasSelection(); - canSelectAll = !edit->isEmpty(); - canUndo = edit->isUndoAvailable(); - canRedo = edit->isRedoAvailable(); + canSelectAll = !edit->document()->isEmpty(); + canUndo = edit->document()->isUndoAvailable(); + canRedo = edit->document()->isRedoAvailable(); canPaste = !Application::clipboard()->text().isEmpty(); } else if (auto list = qobject_cast(focused)) { canCopy = list->canCopySelected(); @@ -534,7 +534,7 @@ bool MainWindow::psFilterNativeEvent(void *event) { bool MainWindow::eventFilter(QObject *obj, QEvent *evt) { QEvent::Type t = evt->type(); if (t == QEvent::FocusIn || t == QEvent::FocusOut) { - if (qobject_cast(obj) || qobject_cast(obj) || qobject_cast(obj)) { + if (qobject_cast(obj) || qobject_cast(obj) || qobject_cast(obj)) { psMacUpdateMenu(); } } diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index efc6ff1ed..521453932 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -21,6 +21,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org using "basic.style"; using "basic_types.style"; +using "window/window.style"; + profileBg: windowBg; profileTopBarHeight: topBarHeight; diff --git a/Telegram/SourceFiles/profile/profile_actions_widget.cpp b/Telegram/SourceFiles/profile/profile_actions_widget.cpp index ef4ee8465..a62c4967f 100644 --- a/Telegram/SourceFiles/profile/profile_actions_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_actions_widget.cpp @@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "profile/profile_actions_widget.h" #include "styles/style_profile.h" +#include "styles/style_boxes.h" #include "ui/buttons/left_outline_button.h" #include "boxes/confirmbox.h" #include "boxes/report_box.h" diff --git a/Telegram/SourceFiles/profile/profile_actions_widget.h b/Telegram/SourceFiles/profile/profile_actions_widget.h index 0043ccfb7..014312c42 100644 --- a/Telegram/SourceFiles/profile/profile_actions_widget.h +++ b/Telegram/SourceFiles/profile/profile_actions_widget.h @@ -22,6 +22,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "profile/profile_block_widget.h" +namespace style { +struct OutlineButton; +} // namespace style + +namespace st { +extern const style::OutlineButton &defaultLeftOutlineButton; +} // namespace st + namespace Ui { class LeftOutlineButton; } // namespace Ui diff --git a/Telegram/SourceFiles/profile/profile_block_widget.cpp b/Telegram/SourceFiles/profile/profile_block_widget.cpp index 1b287cc6e..95e0eabb9 100644 --- a/Telegram/SourceFiles/profile/profile_block_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_block_widget.cpp @@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "profile/profile_block_widget.h" #include "styles/style_profile.h" +#include "styles/style_widgets.h" namespace Profile { diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index 6c5e925f7..7d96fdd32 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -25,6 +25,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org class FlatLabel; +namespace style { +struct RoundButton; +} // namespace style + namespace Ui { class RoundButton; class LinkButton; diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp index 03927b938..eba4f65fc 100644 --- a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp @@ -30,6 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/confirmbox.h" #include "observer_peer.h" #include "window/top_bar_widget.h" +#include "styles/style_boxes.h" namespace Profile { diff --git a/Telegram/SourceFiles/profile/profile_settings_widget.cpp b/Telegram/SourceFiles/profile/profile_settings_widget.cpp index 886cf9c8b..ed9c84cc4 100644 --- a/Telegram/SourceFiles/profile/profile_settings_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_settings_widget.cpp @@ -129,7 +129,7 @@ void SettingsWidget::refreshManageAdminsButton() { }; _manageAdmins.destroy(); if (hasManageAdmins()) { - _manageAdmins = new Ui::LeftOutlineButton(this, lang(lng_profile_manage_admins), st::defaultLeftOutlineButton); + _manageAdmins.create(this, lang(lng_profile_manage_admins), st::defaultLeftOutlineButton); _manageAdmins->show(); connect(_manageAdmins, SIGNAL(clicked()), this, SLOT(onManageAdmins())); } @@ -152,7 +152,7 @@ void SettingsWidget::refreshInviteLinkButton() { if (inviteLinkText.isEmpty()) { _inviteLink.destroy(); } else { - _inviteLink = new Ui::LeftOutlineButton(this, inviteLinkText, st::defaultLeftOutlineButton); + _inviteLink.create(this, inviteLinkText, st::defaultLeftOutlineButton); _inviteLink->show(); connect(_inviteLink, SIGNAL(clicked()), this, SLOT(onInviteLink())); } diff --git a/Telegram/SourceFiles/stdafx.h b/Telegram/SourceFiles/stdafx.h index fde5cb65d..83ad17611 100644 --- a/Telegram/SourceFiles/stdafx.h +++ b/Telegram/SourceFiles/stdafx.h @@ -70,8 +70,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/animation.h" #include "ui/twidget.h" -#include "ui/flatinput.h" -#include "ui/flattextarea.h" #include "ui/scrollarea.h" #include "ui/images.h" #include "ui/text/text.h" diff --git a/Telegram/SourceFiles/stickers/stickers.style b/Telegram/SourceFiles/stickers/stickers.style index c751dcdc7..d0a6534b5 100644 --- a/Telegram/SourceFiles/stickers/stickers.style +++ b/Telegram/SourceFiles/stickers/stickers.style @@ -23,6 +23,13 @@ using "basic.style"; using "boxes/boxes.style"; using "ui/widgets/widgets.style"; +switchPmButton: RoundButton(defaultBoxButton) { + width: 320px; + height: 34px; + textTop: 7px; + downTextTop: 8px; +} + stickersTrendingHeader: 45px; stickersTrendingSkip: 15px; diff --git a/Telegram/SourceFiles/ui/buttons/left_outline_button.h b/Telegram/SourceFiles/ui/buttons/left_outline_button.h index 590535d32..33db20cb2 100644 --- a/Telegram/SourceFiles/ui/buttons/left_outline_button.h +++ b/Telegram/SourceFiles/ui/buttons/left_outline_button.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "ui/abstract_button.h" +#include "styles/style_widgets.h" namespace Ui { diff --git a/Telegram/SourceFiles/ui/buttons/peer_avatar_button.h b/Telegram/SourceFiles/ui/buttons/peer_avatar_button.h index 7a31ba9db..ccad1341c 100644 --- a/Telegram/SourceFiles/ui/buttons/peer_avatar_button.h +++ b/Telegram/SourceFiles/ui/buttons/peer_avatar_button.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "ui/abstract_button.h" +#include "styles/style_window.h" class PeerData; diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index e43b4eade..1c833447c 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -111,7 +111,7 @@ CountryInput::CountryInput(QWidget *parent, const style::countryInput &st) : QWi } _arrow = App::pixmapFromImageInPlace(std_::move(trImage)); _inner = QRect(0, 0, _st.width, _st.height); - _arrowRect = QRect((st::inpIntroCountryCode.width - _arrow.width() - 1) / 2, _st.height, _arrow.width(), _arrow.height()); + _arrowRect = QRect((st::introCountryCode.width - _arrow.width() - 1) / 2, _st.height, _arrow.width(), _arrow.height()); } void CountryInput::paintEvent(QPaintEvent *e) { diff --git a/Telegram/SourceFiles/ui/countryinput.h b/Telegram/SourceFiles/ui/countryinput.h index d3416c068..a5a8d376e 100644 --- a/Telegram/SourceFiles/ui/countryinput.h +++ b/Telegram/SourceFiles/ui/countryinput.h @@ -20,7 +20,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "ui/flatinput.h" #include "ui/scrollarea.h" #include "ui/effects/rect_shadow.h" #include "boxes/abstractbox.h" diff --git a/Telegram/SourceFiles/ui/effects/panel_animation.cpp b/Telegram/SourceFiles/ui/effects/panel_animation.cpp index 28b3efa84..91d373f00 100644 --- a/Telegram/SourceFiles/ui/effects/panel_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/panel_animation.cpp @@ -229,7 +229,7 @@ void RoundShadowAnimation::paintShadowHorizontal(int left, int right, int top, c void PanelAnimation::setFinalImage(QImage &&finalImage, QRect inner) { t_assert(!started()); - _finalImage = QPixmap::fromImage(std_::move(finalImage).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly); + _finalImage = App::pixmapFromImageInPlace(std_::move(finalImage).convertToFormat(QImage::Format_ARGB32_Premultiplied)); t_assert(!_finalImage.isNull()); _finalWidth = _finalImage.width(); @@ -317,7 +317,7 @@ void PanelAnimation::createFadeMask() { } ints += intsPerLineAdded; } - _fadeMask = QPixmap::fromImage(style::colorizeImage(result, _st.fadeBg), Qt::ColorOnly); + _fadeMask = App::pixmapFromImageInPlace(style::colorizeImage(result, _st.fadeBg)); _fadeHeight = _fadeMask.height(); } diff --git a/Telegram/SourceFiles/ui/effects/ripple_animation.cpp b/Telegram/SourceFiles/ui/effects/ripple_animation.cpp new file mode 100644 index 000000000..71823d0d9 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/ripple_animation.cpp @@ -0,0 +1,157 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "ui/effects/ripple_animation.h" + +namespace Ui { + +class RippleAnimation::Ripple { +public: + Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, UpdateCallback update); + + void paint(QPainter &p, const QPixmap &mask, uint64 ms); + + void stop(); + bool finished() const { + return _hiding && !_hide.animating(); + } + +private: + const style::RippleAnimation &_st; + UpdateCallback _update; + + QPoint _origin; + int _radiusFrom = 0; + int _radiusTo = 0; + + bool _hiding = false; + FloatAnimation _show; + FloatAnimation _hide; + QPixmap _cache; + QImage _frame; + +}; + +RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, UpdateCallback update) +: _st(st) +, _update(update) +, _origin(origin) +, _radiusFrom(startRadius) +, _frame(mask.size(), QImage::Format_ARGB32_Premultiplied) { + _frame.setDevicePixelRatio(mask.devicePixelRatio()); + + QPoint points[] = { + { 0, 0 }, + { _frame.width() / cIntRetinaFactor(), 0 }, + { _frame.width() / cIntRetinaFactor(), _frame.height() / cIntRetinaFactor() }, + { 0, _frame.height() / cIntRetinaFactor() }, + }; + for (auto point : points) { + accumulate_max(_radiusTo, style::point::dotProduct(_origin - point, _origin - point)); + } + _radiusTo = qRound(sqrt(_radiusTo)); + + _show.start(UpdateCallback(_update), 0., 1., _st.showDuration); +} + +void RippleAnimation::Ripple::paint(QPainter &p, const QPixmap &mask, uint64 ms) { + auto opacity = _hide.current(ms, _hiding ? 0. : 1.); + if (opacity == 0.) { + return; + } + + if (_cache.isNull()) { + auto radius = anim::interpolate(_radiusFrom, _radiusTo, _show.current(ms, 1.)); + _frame.fill(Qt::transparent); + { + Painter p(&_frame); + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.setPen(Qt::NoPen); + p.setBrush(_st.color); + p.drawEllipse(_origin, radius, radius); + + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + p.drawPixmap(0, 0, mask); + } + if (radius == _radiusTo) { + _cache = App::pixmapFromImageInPlace(std_::move(_frame)); + } + } + auto saved = p.opacity(); + if (opacity != 1.) p.setOpacity(saved * opacity); + if (_cache.isNull()) { + p.drawImage(0, 0, _frame); + } else { + p.drawPixmap(0, 0, _cache); + } + if (opacity != 1.) p.setOpacity(saved); +} + +void RippleAnimation::Ripple::stop() { + _hiding = true; + _hide.start(UpdateCallback(_update), 1., 0., _st.hideDuration); +} + +RippleAnimation::RippleAnimation(const style::RippleAnimation &st, QImage mask, UpdateCallback callback) +: _st(st) +, _mask(App::pixmapFromImageInPlace(std_::move(mask))) +, _update(std_::move(callback)) { +} + + +void RippleAnimation::add(QPoint origin, int startRadius) { + _ripples.push_back(new Ripple(_st, origin, startRadius, _mask, _update)); +} + +void RippleAnimation::stopLast() { + if (!_ripples.isEmpty()) { + _ripples.back()->stop(); + } +} + +void RippleAnimation::paint(QPainter &p, int x, int y, int outerWidth, uint64 ms) { + if (_ripples.isEmpty()) { + return; + } + + if (rtl()) x = outerWidth - x - (_mask.width() / cIntRetinaFactor()); + p.translate(x, y); + for (auto ripple : _ripples) { + ripple->paint(p, _mask, ms); + } + p.translate(-x, -y); + clearFinished(); +} + +void RippleAnimation::clearFinished() { + while (!_ripples.isEmpty() && _ripples.front()->finished()) { + delete base::take(_ripples.front()); + _ripples.pop_front(); + } +} + +void RippleAnimation::clear() { + for (auto ripple : base::take(_ripples)) { + delete ripple; + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/ripple_animation.h b/Telegram/SourceFiles/ui/effects/ripple_animation.h new file mode 100644 index 000000000..ed531a3ff --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/ripple_animation.h @@ -0,0 +1,62 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "styles/style_widgets.h" + +namespace Ui { + +class RippleAnimation { +public: + using UpdateCallback = base::lambda_wrap; + + // White upon transparent mask, like colorizeImage(black-white-mask, white). + RippleAnimation(const style::RippleAnimation &st, QImage mask, UpdateCallback update); + + void setMask(QImage &&mask); + + void add(QPoint origin, int startRadius = 0); + void stopLast(); + + void paint(QPainter &p, int x, int y, int outerWidth, uint64 ms); + + bool empty() const { + return _ripples.isEmpty(); + } + + ~RippleAnimation() { + clear(); + } + +private: + void clear(); + void clearFinished(); + + const style::RippleAnimation &_st; + QPixmap _mask; + UpdateCallback _update; + + class Ripple; + QList _ripples; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/flattextarea.cpp b/Telegram/SourceFiles/ui/flattextarea.cpp deleted file mode 100644 index f8495a4a7..000000000 --- a/Telegram/SourceFiles/ui/flattextarea.cpp +++ /dev/null @@ -1,1426 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#include "stdafx.h" -#include "flattextarea.h" - -#include "ui/widgets/popup_menu.h" -#include "mainwindow.h" - -QByteArray FlatTextarea::serializeTagsList(const TagList &tags) { - if (tags.isEmpty()) { - return QByteArray(); - } - - QByteArray tagsSerialized; - { - QBuffer buffer(&tagsSerialized); - buffer.open(QIODevice::WriteOnly); - QDataStream stream(&buffer); - stream.setVersion(QDataStream::Qt_5_1); - stream << qint32(tags.size()); - for_const (auto &tag, tags) { - stream << qint32(tag.offset) << qint32(tag.length) << tag.id; - } - } - return tagsSerialized; -} - -FlatTextarea::TagList FlatTextarea::deserializeTagsList(QByteArray data, int textLength) { - TagList result; - if (data.isEmpty()) { - return result; - } - - QBuffer buffer(&data); - buffer.open(QIODevice::ReadOnly); - - QDataStream stream(&buffer); - stream.setVersion(QDataStream::Qt_5_1); - - qint32 tagCount = 0; - stream >> tagCount; - if (stream.status() != QDataStream::Ok) { - return result; - } - if (tagCount <= 0 || tagCount > textLength) { - return result; - } - - for (int i = 0; i < tagCount; ++i) { - qint32 offset = 0, length = 0; - QString id; - stream >> offset >> length >> id; - if (stream.status() != QDataStream::Ok) { - return result; - } - if (offset < 0 || length <= 0 || offset + length > textLength) { - return result; - } - result.push_back({ offset, length, id }); - } - return result; -} - -QString FlatTextarea::tagsMimeType() { - return qsl("application/x-td-field-tags"); -} - -FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &pholder, const QString &v, const TagList &tags) : QTextEdit(parent) -, _phVisible(!v.length()) -, a_phLeft(_phVisible ? 0 : st.phShift) -, a_phAlpha(_phVisible ? 1 : 0) -, a_phColorFocused(0) -, _a_appearance(animation(this, &FlatTextarea::step_appearance)) -, _lastTextWithTags { v, tags } -, _st(st) { - setAcceptRichText(false); - resize(_st.width, _st.font->height); - - setFont(_st.font->f); - setAlignment(_st.align); - - setPlaceholder(pholder); - - QPalette p(palette()); - p.setColor(QPalette::Text, _st.textColor->c); - setPalette(p); - - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - setFrameStyle(QFrame::NoFrame | QFrame::Plain); - viewport()->setAutoFillBackground(false); - - setContentsMargins(0, 0, 0, 0); - - switch (cScale()) { - case dbisOneAndQuarter: _fakeMargin = 1; break; - case dbisOneAndHalf: _fakeMargin = 2; break; - case dbisTwo: _fakeMargin = 4; break; - } - setStyleSheet(qsl("QTextEdit { margin: %1px; }").arg(_fakeMargin)); - - viewport()->setAttribute(Qt::WA_AcceptTouchEvents); - _touchTimer.setSingleShot(true); - connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); - - connect(document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(onDocumentContentsChange(int,int,int))); - connect(document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged())); - connect(this, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool))); - connect(this, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool))); - if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); - - if (!_lastTextWithTags.text.isEmpty()) { - setTextWithTags(_lastTextWithTags, ClearUndoHistory); - } -} - -FlatTextarea::TextWithTags FlatTextarea::getTextWithTagsPart(int start, int end) { - TextWithTags result; - result.text = getTextPart(start, end, &result.tags); - return result; -} - -void FlatTextarea::setTextWithTags(const TextWithTags &textWithTags, UndoHistoryAction undoHistoryAction) { - _insertedTags = textWithTags.tags; - _insertedTagsAreFromMime = false; - _realInsertPosition = 0; - _realCharsAdded = textWithTags.text.size(); - auto doc = document(); - auto cursor = QTextCursor(doc->docHandle(), 0); - if (undoHistoryAction == ClearUndoHistory) { - doc->setUndoRedoEnabled(false); - cursor.beginEditBlock(); - } else if (undoHistoryAction == MergeWithUndoHistory) { - cursor.joinPreviousEditBlock(); - } else { - cursor.beginEditBlock(); - } - cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); - cursor.insertText(textWithTags.text); - cursor.movePosition(QTextCursor::End); - cursor.endEditBlock(); - if (undoHistoryAction == ClearUndoHistory) { - doc->setUndoRedoEnabled(true); - } - _insertedTags.clear(); - _realInsertPosition = -1; - finishPlaceholder(); -} - -void FlatTextarea::finishPlaceholder() { - if (_a_appearance.animating()) { - a_phLeft.finish(); - a_phAlpha.finish(); - _a_appearance.stop(); - update(); - } -} - -void FlatTextarea::setMaxLength(int32 maxLength) { - _maxLength = maxLength; -} - -void FlatTextarea::setMinHeight(int32 minHeight) { - _minHeight = minHeight; - heightAutoupdated(); -} - -void FlatTextarea::setMaxHeight(int32 maxHeight) { - _maxHeight = maxHeight; - heightAutoupdated(); -} - -bool FlatTextarea::heightAutoupdated() { - if (_minHeight < 0 || _maxHeight < 0 || _inHeightCheck) return false; - _inHeightCheck = true; - - myEnsureResized(this); - - int newh = ceil(document()->size().height()) + 2 * fakeMargin(); - if (newh > _maxHeight) { - newh = _maxHeight; - } else if (newh < _minHeight) { - newh = _minHeight; - } - if (height() != newh) { - resize(width(), newh); - _inHeightCheck = false; - return true; - } - _inHeightCheck = false; - return false; -} - -void FlatTextarea::onTouchTimer() { - _touchRightButton = true; -} - -bool FlatTextarea::viewportEvent(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 QTextEdit::viewportEvent(e); - } - } - return QTextEdit::viewportEvent(e); -} - -void FlatTextarea::touchEvent(QTouchEvent *e) { - switch (e->type()) { - case QEvent::TouchBegin: - if (_touchPress || e->touchPoints().isEmpty()) return; - _touchTimer.start(QApplication::startDragTime()); - _touchPress = true; - _touchMove = _touchRightButton = false; - _touchStart = e->touchPoints().cbegin()->screenPos().toPoint(); - break; - - case QEvent::TouchUpdate: - if (!_touchPress || e->touchPoints().isEmpty()) return; - if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { - _touchMove = true; - } - break; - - case QEvent::TouchEnd: - if (!_touchPress) return; - if (!_touchMove && window()) { - Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton); - QPoint mapped(mapFromGlobal(_touchStart)), winMapped(window()->mapFromGlobal(_touchStart)); - - if (_touchRightButton) { - QContextMenuEvent contextEvent(QContextMenuEvent::Mouse, mapped, _touchStart); - contextMenuEvent(&contextEvent); - } - } - _touchTimer.stop(); - _touchPress = _touchMove = _touchRightButton = false; - break; - - case QEvent::TouchCancel: - _touchPress = false; - _touchTimer.stop(); - break; - } -} - -QRect FlatTextarea::getTextRect() const { - return rect().marginsRemoved(_st.textMrg + st::textRectMargins); -} - -int32 FlatTextarea::fakeMargin() const { - return _fakeMargin; -} - -void FlatTextarea::paintEvent(QPaintEvent *e) { - QPainter p(viewport()); - QRect r(rect().intersected(e->rect())); - p.fillRect(r, _st.bgColor->b); - bool phDraw = _phVisible; - if (_a_appearance.animating()) { - p.setOpacity(a_phAlpha.current()); - phDraw = true; - } - if (phDraw) { - p.save(); - p.setClipRect(r); - p.setFont(_st.font); - p.setPen(anim::pen(_st.phColor, _st.phFocusColor, a_phColorFocused.current())); - if (_st.phAlign == style::al_topleft && _phAfter > 0) { - int skipWidth = placeholderSkipWidth(); - p.drawText(_st.textMrg.left() - _fakeMargin + a_phLeft.current() + skipWidth, _st.textMrg.top() - _fakeMargin - st::lineWidth + _st.font->ascent, _ph); - } else { - QRect phRect(_st.textMrg.left() - _fakeMargin + _st.phPos.x() + a_phLeft.current(), _st.textMrg.top() - _fakeMargin + _st.phPos.y(), width() - _st.textMrg.left() - _st.textMrg.right(), height() - _st.textMrg.top() - _st.textMrg.bottom()); - p.drawText(phRect, _ph, QTextOption(_st.phAlign)); - } - p.restore(); - p.setOpacity(1); - } - QTextEdit::paintEvent(e); -} - -int FlatTextarea::placeholderSkipWidth() const { - if (!_phAfter) { - return 0; - } - auto text = getTextWithTags().text; - auto result = _st.font->width(text.mid(0, _phAfter)); - if (_phAfter > text.size()) { - result += _st.font->spacew; - } - return result; -} - -void FlatTextarea::focusInEvent(QFocusEvent *e) { - a_phColorFocused.start(1.); - _a_appearance.start(); - QTextEdit::focusInEvent(e); -} - -void FlatTextarea::focusOutEvent(QFocusEvent *e) { - a_phColorFocused.start(0.); - _a_appearance.start(); - QTextEdit::focusOutEvent(e); -} - -QSize FlatTextarea::sizeHint() const { - return geometry().size(); -} - -QSize FlatTextarea::minimumSizeHint() const { - return geometry().size(); -} - -EmojiPtr FlatTextarea::getSingleEmoji() const { - QString text; - QTextFragment fragment; - - getSingleEmojiFragment(text, fragment); - - if (!text.isEmpty()) { - QTextCharFormat format = fragment.charFormat(); - QString imageName = static_cast(&format)->name(); - if (imageName.startsWith(qstr("emoji://e."))) { - return emojiFromUrl(imageName); - } - } - return nullptr; -} - -QString FlatTextarea::getInlineBotQuery(UserData **outInlineBot, QString *outInlineBotUsername) const { - t_assert(outInlineBot != nullptr); - t_assert(outInlineBotUsername != nullptr); - - auto &text = getTextWithTags().text; - auto textLength = text.size(); - - int inlineUsernameStart = 1, inlineUsernameLength = 0; - if (textLength > 2 && text.at(0) == '@' && text.at(1).isLetter()) { - inlineUsernameLength = 1; - for (int i = inlineUsernameStart + 1; i != textLength; ++i) { - if (text.at(i).isLetterOrNumber() || text.at(i).unicode() == '_') { - ++inlineUsernameLength; - continue; - } - if (!text.at(i).isSpace()) { - inlineUsernameLength = 0; - } - break; - } - auto inlineUsernameEnd = inlineUsernameStart + inlineUsernameLength; - auto inlineUsernameEqualsText = (inlineUsernameEnd == textLength); - auto validInlineUsername = false; - if (inlineUsernameEqualsText) { - validInlineUsername = text.endsWith(qstr("bot")); - } else if (inlineUsernameEnd < textLength && inlineUsernameLength) { - validInlineUsername = text.at(inlineUsernameEnd).isSpace(); - } - if (validInlineUsername) { - auto username = text.midRef(inlineUsernameStart, inlineUsernameLength); - if (username != *outInlineBotUsername) { - *outInlineBotUsername = username.toString(); - auto peer = App::peerByName(*outInlineBotUsername); - if (peer) { - if (peer->isUser()) { - *outInlineBot = peer->asUser(); - } else { - *outInlineBot = nullptr; - } - } else { - *outInlineBot = LookingUpInlineBot; - } - } - if (*outInlineBot == LookingUpInlineBot) return QString(); - - if (*outInlineBot && (!(*outInlineBot)->botInfo || (*outInlineBot)->botInfo->inlinePlaceholder.isEmpty())) { - *outInlineBot = nullptr; - } else { - return inlineUsernameEqualsText ? QString() : text.mid(inlineUsernameEnd + 1); - } - } else { - inlineUsernameLength = 0; - } - } - if (inlineUsernameLength < 3) { - *outInlineBot = nullptr; - *outInlineBotUsername = QString(); - } - return QString(); -} - -QString FlatTextarea::getMentionHashtagBotCommandPart(bool &start) const { - start = false; - - int32 pos = textCursor().position(); - if (textCursor().anchor() != pos) return QString(); - - // check mention / hashtag / bot command - QTextDocument *doc(document()); - QTextBlock block = doc->findBlock(pos); - for (QTextBlock::Iterator iter = block.begin(); !iter.atEnd(); ++iter) { - QTextFragment fr(iter.fragment()); - if (!fr.isValid()) continue; - - int32 p = fr.position(), e = (p + fr.length()); - if (p >= pos || e < pos) continue; - - QTextCharFormat f = fr.charFormat(); - if (f.isImageFormat()) continue; - - bool mentionInCommand = false; - QString t(fr.text()); - for (int i = pos - p; i > 0; --i) { - if (t.at(i - 1) == '@') { - if ((pos - p - i < 1 || t.at(i).isLetter()) && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) { - start = (i == 1) && (p == 0); - return t.mid(i - 1, pos - p - i + 1); - } else if ((pos - p - i < 1 || t.at(i).isLetter()) && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) { - mentionInCommand = true; - --i; - continue; - } - return QString(); - } else if (t.at(i - 1) == '#') { - if (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_')) { - start = (i == 1) && (p == 0); - return t.mid(i - 1, pos - p - i + 1); - } - return QString(); - } else if (t.at(i - 1) == '/') { - if (i < 2) { - start = (i == 1) && (p == 0); - return t.mid(i - 1, pos - p - i + 1); - } - return QString(); - } - if (pos - p - i > 127 || (!mentionInCommand && (pos - p - i > 63))) break; - if (!t.at(i - 1).isLetterOrNumber() && t.at(i - 1) != '_') break; - } - break; - } - return QString(); -} - -void FlatTextarea::insertTag(const QString &text, QString tagId) { - auto cursor = textCursor(); - int32 pos = cursor.position(); - - auto doc = document(); - auto block = doc->findBlock(pos); - for (auto iter = block.begin(); !iter.atEnd(); ++iter) { - auto fragment = iter.fragment(); - t_assert(fragment.isValid()); - - int fragmentPosition = fragment.position(); - int fragmentEnd = (fragmentPosition + fragment.length()); - if (fragmentPosition >= pos || fragmentEnd < pos) continue; - - auto format = fragment.charFormat(); - if (format.isImageFormat()) continue; - - bool mentionInCommand = false; - auto fragmentText = fragment.text(); - for (int i = pos - fragmentPosition; i > 0; --i) { - auto previousChar = fragmentText.at(i - 1); - if (previousChar == '@' || previousChar == '#' || previousChar == '/') { - if ((i == pos - fragmentPosition || (previousChar == '/' ? fragmentText.at(i).isLetterOrNumber() : fragmentText.at(i).isLetter()) || previousChar == '#') && - (i < 2 || !(fragmentText.at(i - 2).isLetterOrNumber() || fragmentText.at(i - 2) == '_'))) { - cursor.setPosition(fragmentPosition + i - 1); - int till = fragmentPosition + i; - for (; (till < fragmentEnd && till < pos); ++till) { - auto ch = fragmentText.at(till - fragmentPosition); - if (!ch.isLetterOrNumber() && ch != '_' && ch != '@') { - break; - } - } - if (till < fragmentEnd && fragmentText.at(till - fragmentPosition) == ' ') { - ++till; - } - cursor.setPosition(till, QTextCursor::KeepAnchor); - break; - } else if ((i == pos - fragmentPosition || fragmentText.at(i).isLetter()) && fragmentText.at(i - 1) == '@' && i > 2 && (fragmentText.at(i - 2).isLetterOrNumber() || fragmentText.at(i - 2) == '_') && !mentionInCommand) { - mentionInCommand = true; - --i; - continue; - } - break; - } - if (pos - fragmentPosition - i > 127 || (!mentionInCommand && (pos - fragmentPosition - i > 63))) break; - if (!fragmentText.at(i - 1).isLetterOrNumber() && fragmentText.at(i - 1) != '_') break; - } - break; - } - if (tagId.isEmpty()) { - QTextCharFormat format = cursor.charFormat(); - format.setAnchor(false); - format.setAnchorName(QString()); - format.clearForeground(); - cursor.insertText(text + ' ', format); - } else { - _insertedTags.clear(); - _insertedTags.push_back({ 0, text.size(), tagId }); - _insertedTagsAreFromMime = false; - cursor.insertText(text + ' '); - _insertedTags.clear(); - } -} - -void FlatTextarea::setTagMimeProcessor(std_::unique_ptr &&processor) { - _tagMimeProcessor = std_::move(processor); -} - -void FlatTextarea::getSingleEmojiFragment(QString &text, QTextFragment &fragment) const { - int32 end = textCursor().position(), start = end - 1; - if (textCursor().anchor() != end) return; - - if (start < 0) start = 0; - - QTextDocument *doc(document()); - QTextBlock from = doc->findBlock(start), till = doc->findBlock(end); - if (till.isValid()) till = till.next(); - - for (QTextBlock b = from; b != till; b = b.next()) { - for (QTextBlock::Iterator iter = b.begin(); !iter.atEnd(); ++iter) { - QTextFragment fr(iter.fragment()); - if (!fr.isValid()) continue; - - int32 p = fr.position(), e = (p + fr.length()); - if (p >= end || e <= start) { - continue; - } - - QTextCharFormat f = fr.charFormat(); - QString t(fr.text()); - if (p < start) { - t = t.mid(start - p, end - start); - } else if (e > end) { - t = t.mid(0, end - p); - } - if (f.isImageFormat() && !t.isEmpty() && t.at(0).unicode() == QChar::ObjectReplacementCharacter) { - QString imageName = static_cast(&f)->name(); - if (imageName.startsWith(qstr("emoji://e."))) { - fragment = fr; - text = t; - return; - } - } - return; - } - } - return; -} - -void FlatTextarea::removeSingleEmoji() { - QString text; - QTextFragment fragment; - - getSingleEmojiFragment(text, fragment); - - if (!text.isEmpty()) { - QTextCursor t(textCursor()); - t.setPosition(fragment.position()); - t.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor); - t.removeSelectedText(); - setTextCursor(t); - } -} - -namespace { - -class TagAccumulator { -public: - TagAccumulator(FlatTextarea::TagList *tags) : _tags(tags) { - } - - bool changed() const { - return _changed; - } - - void feed(const QString &randomTagId, int currentPosition) { - if (randomTagId == _currentTagId) return; - - if (!_currentTagId.isEmpty()) { - int randomPartPosition = _currentTagId.lastIndexOf('/'); - t_assert(randomPartPosition > 0); - - bool tagChanged = true; - if (_currentTag < _tags->size()) { - auto &alreadyTag = _tags->at(_currentTag); - if (alreadyTag.offset == _currentStart && - alreadyTag.length == currentPosition - _currentStart && - alreadyTag.id == _currentTagId.midRef(0, randomPartPosition)) { - tagChanged = false; - } - } - if (tagChanged) { - _changed = true; - FlatTextarea::Tag tag = { - _currentStart, - currentPosition - _currentStart, - _currentTagId.mid(0, randomPartPosition), - }; - if (_currentTag < _tags->size()) { - (*_tags)[_currentTag] = tag; - } else { - _tags->push_back(tag); - } - } - ++_currentTag; - } - _currentTagId = randomTagId; - _currentStart = currentPosition; - }; - - void finish() { - if (_currentTag < _tags->size()) { - _tags->resize(_currentTag); - _changed = true; - } - } - -private: - FlatTextarea::TagList *_tags; - bool _changed = false; - - int _currentTag = 0; - int _currentStart = 0; - QString _currentTagId; - -}; - -} // namespace - -QString FlatTextarea::getTextPart(int start, int end, TagList *outTagsList, bool *outTagsChanged) const { - if (end >= 0 && end <= start) return QString(); - - if (start < 0) start = 0; - bool full = (start == 0) && (end < 0); - - TagAccumulator tagAccumulator(outTagsList); - - QTextDocument *doc(document()); - QTextBlock from = full ? doc->begin() : doc->findBlock(start), till = (end < 0) ? doc->end() : doc->findBlock(end); - if (till.isValid()) till = till.next(); - - int32 possibleLen = 0; - for (QTextBlock b = from; b != till; b = b.next()) { - possibleLen += b.length(); - } - QString result; - result.reserve(possibleLen + 1); - if (!full && end < 0) { - end = possibleLen; - } - - bool tillFragmentEnd = full; - for (auto b = from; b != till; b = b.next()) { - for (auto iter = b.begin(); !iter.atEnd(); ++iter) { - QTextFragment fragment(iter.fragment()); - if (!fragment.isValid()) continue; - - int32 p = full ? 0 : fragment.position(), e = full ? 0 : (p + fragment.length()); - if (!full) { - tillFragmentEnd = (e <= end); - if (p == end) { - tagAccumulator.feed(fragment.charFormat().anchorName(), result.size()); - } - if (p >= end) { - break; - } - if (e <= start) { - continue; - } - } - if (full || p >= start) { - tagAccumulator.feed(fragment.charFormat().anchorName(), result.size()); - } - - QTextCharFormat f = fragment.charFormat(); - QString emojiText; - QString t(fragment.text()); - if (!full) { - if (p < start) { - t = t.mid(start - p, end - start); - } else if (e > end) { - t = t.mid(0, end - p); - } - } - QChar *ub = t.data(), *uc = ub, *ue = uc + t.size(); - for (; uc != ue; ++uc) { - switch (uc->unicode()) { - case 0xfdd0: // QTextBeginningOfFrame - case 0xfdd1: // QTextEndOfFrame - case QChar::ParagraphSeparator: - case QChar::LineSeparator: - *uc = QLatin1Char('\n'); - break; - case QChar::Nbsp: - *uc = QLatin1Char(' '); - break; - case QChar::ObjectReplacementCharacter: - if (emojiText.isEmpty() && f.isImageFormat()) { - QString imageName = static_cast(&f)->name(); - if (imageName.startsWith(qstr("emoji://e."))) { - if (EmojiPtr emoji = emojiFromUrl(imageName)) { - emojiText = emojiString(emoji); - } - } - } - if (uc > ub) result.append(ub, uc - ub); - if (!emojiText.isEmpty()) result.append(emojiText); - ub = uc + 1; - break; - } - } - if (uc > ub) result.append(ub, uc - ub); - } - result.append('\n'); - } - result.chop(1); - - if (tillFragmentEnd) tagAccumulator.feed(QString(), result.size()); - tagAccumulator.finish(); - - if (outTagsChanged) { - *outTagsChanged = tagAccumulator.changed(); - } - return result; -} - -bool FlatTextarea::hasText() const { - QTextDocument *doc(document()); - QTextBlock from = doc->begin(), till = doc->end(); - - if (from == till) return false; - - for (QTextBlock::Iterator iter = from.begin(); !iter.atEnd(); ++iter) { - QTextFragment fragment(iter.fragment()); - if (!fragment.isValid()) continue; - if (!fragment.text().isEmpty()) return true; - } - return (from.next() != till); -} - -bool FlatTextarea::isUndoAvailable() const { - return _undoAvailable; -} - -bool FlatTextarea::isRedoAvailable() const { - return _redoAvailable; -} - -void FlatTextarea::parseLinks() { // some code is duplicated in text.cpp! - LinkRanges newLinks; - - QString text(toPlainText()); - if (text.isEmpty()) { - if (!_links.isEmpty()) { - _links.clear(); - emit linksChanged(); - } - return; - } - - initLinkSets(); - - int32 len = text.size(); - const QChar *start = text.unicode(), *end = start + text.size(); - for (int32 offset = 0, matchOffset = offset; offset < len;) { - QRegularExpressionMatch m = reDomain().match(text, matchOffset); - if (!m.hasMatch()) break; - - int32 domainOffset = m.capturedStart(); - - QString protocol = m.captured(1).toLower(); - QString topDomain = m.captured(3).toLower(); - - bool isProtocolValid = protocol.isEmpty() || validProtocols().contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); - bool isTopDomainValid = !protocol.isEmpty() || validTopDomains().contains(hashCrc32(topDomain.constData(), topDomain.size() * sizeof(QChar))); - - if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) { - QString forMailName = text.mid(offset, domainOffset - offset - 1); - QRegularExpressionMatch mMailName = reMailName().match(forMailName); - if (mMailName.hasMatch()) { - offset = matchOffset = m.capturedEnd(); - continue; - } - } - if (!isProtocolValid || !isTopDomainValid) { - offset = matchOffset = m.capturedEnd(); - continue; - } - - QStack parenth; - const QChar *domainEnd = start + m.capturedEnd(), *p = domainEnd; - for (; p < end; ++p) { - QChar ch(*p); - if (chIsLinkEnd(ch)) break; // link finished - if (chIsAlmostLinkEnd(ch)) { - const QChar *endTest = p + 1; - while (endTest < end && chIsAlmostLinkEnd(*endTest)) { - ++endTest; - } - if (endTest >= end || chIsLinkEnd(*endTest)) { - break; // link finished at p - } - p = endTest; - ch = *p; - } - if (ch == '(' || ch == '[' || ch == '{' || ch == '<') { - parenth.push(p); - } else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') { - if (parenth.isEmpty()) break; - const QChar *q = parenth.pop(), open(*q); - if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) { - p = q; - break; - } - } - } - if (p > domainEnd) { // check, that domain ended - if (domainEnd->unicode() != '/' && domainEnd->unicode() != '?') { - matchOffset = domainEnd - start; - continue; - } - } - newLinks.push_back({ domainOffset - 1, static_cast(p - start - domainOffset + 2) }); - offset = matchOffset = p - start; - } - - if (newLinks != _links) { - _links = newLinks; - emit linksChanged(); - } -} - -QStringList FlatTextarea::linksList() const { - QStringList result; - if (!_links.isEmpty()) { - QString text(toPlainText()); - for_const (auto &link, _links) { - result.push_back(text.mid(link.start + 1, link.length - 2)); - } - } - return result; -} - -void FlatTextarea::insertFromMimeData(const QMimeData *source) { - auto mime = tagsMimeType(); - auto text = source->text(); - if (source->hasFormat(mime)) { - auto tagsData = source->data(mime); - _insertedTags = deserializeTagsList(tagsData, text.size()); - _insertedTagsAreFromMime = true; - } else { - _insertedTags.clear(); - } - auto cursor = textCursor(); - _realInsertPosition = qMin(cursor.position(), cursor.anchor()); - _realCharsAdded = text.size(); - QTextEdit::insertFromMimeData(source); - if (!_inDrop) { - emit spacedReturnedPasted(); - _insertedTags.clear(); - _realInsertPosition = -1; - } -} - -void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) { - QTextImageFormat imageFormat; - int32 ew = ESize + st::emojiPadding * cIntRetinaFactor() * 2, eh = _st.font->height * cIntRetinaFactor(); - imageFormat.setWidth(ew / cIntRetinaFactor()); - imageFormat.setHeight(eh / cIntRetinaFactor()); - imageFormat.setName(qsl("emoji://e.") + QString::number(emojiKey(emoji), 16)); - imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline); - if (c.charFormat().isAnchor()) { - imageFormat.setAnchor(true); - imageFormat.setAnchorName(c.charFormat().anchorName()); - imageFormat.setForeground(st::defaultTextStyle.linkFg); - } - static QString objectReplacement(QChar::ObjectReplacementCharacter); - c.insertText(objectReplacement, imageFormat); -} - -QVariant FlatTextarea::loadResource(int type, const QUrl &name) { - QString imageName = name.toDisplayString(); - if (imageName.startsWith(qstr("emoji://e."))) { - if (EmojiPtr emoji = emojiFromUrl(imageName)) { - return QVariant(App::emojiSingle(emoji, _st.font->height)); - } - } - return QVariant(); -} - -void FlatTextarea::checkContentHeight() { - if (heightAutoupdated()) { - emit resized(); - } -} - -namespace { - -// Optimization: with null page size document does not re-layout -// on each insertText / mergeCharFormat. -void prepareFormattingOptimization(QTextDocument *document) { - if (!document->pageSize().isNull()) { - document->setPageSize(QSizeF(0, 0)); - } -} - -void removeTags(const style::color &textFg, QTextDocument *document, int from, int end) { - QTextCursor c(document->docHandle(), 0); - c.setPosition(from); - c.setPosition(end, QTextCursor::KeepAnchor); - - QTextCharFormat format; - format.setAnchor(false); - format.setAnchorName(QString()); - format.setForeground(textFg); - c.mergeCharFormat(format); -} - -// Returns the position of the first inserted tag or "changedEnd" value if none found. -int processInsertedTags(const style::color &textFg, QTextDocument *document, int changedPosition, int changedEnd, const FlatTextarea::TagList &tags, FlatTextarea::TagMimeProcessor *processor) { - int firstTagStart = changedEnd; - int applyNoTagFrom = changedEnd; - for_const (auto &tag, tags) { - int tagFrom = changedPosition + tag.offset; - int tagTo = tagFrom + tag.length; - accumulate_max(tagFrom, changedPosition); - accumulate_min(tagTo, changedEnd); - auto tagId = processor ? processor->tagFromMimeTag(tag.id) : tag.id; - if (tagTo > tagFrom && !tagId.isEmpty()) { - accumulate_min(firstTagStart, tagFrom); - - prepareFormattingOptimization(document); - - if (applyNoTagFrom < tagFrom) { - removeTags(textFg, document, applyNoTagFrom, tagFrom); - } - QTextCursor c(document->docHandle(), 0); - c.setPosition(tagFrom); - c.setPosition(tagTo, QTextCursor::KeepAnchor); - - QTextCharFormat format; - format.setAnchor(true); - format.setAnchorName(tagId + '/' + QString::number(rand_value())); - format.setForeground(st::defaultTextStyle.linkFg); - c.mergeCharFormat(format); - - applyNoTagFrom = tagTo; - } - } - if (applyNoTagFrom < changedEnd) { - removeTags(textFg, document, applyNoTagFrom, changedEnd); - } - - return firstTagStart; -} - -// When inserting a part of text inside a tag we need to have -// a way to know if the insertion replaced the end of the tag -// or it was strictly inside (in the middle) of the tag. -bool wasInsertTillTheEndOfTag(QTextBlock block, QTextBlock::iterator fragmentIt, int insertionEnd) { - auto insertTagName = fragmentIt.fragment().charFormat().anchorName(); - while (true) { - for (; !fragmentIt.atEnd(); ++fragmentIt) { - auto fragment = fragmentIt.fragment(); - bool fragmentOutsideInsertion = (fragment.position() >= insertionEnd); - if (fragmentOutsideInsertion) { - return (fragment.charFormat().anchorName() != insertTagName); - } - int fragmentEnd = fragment.position() + fragment.length(); - bool notFullFragmentInserted = (fragmentEnd > insertionEnd); - if (notFullFragmentInserted) { - return false; - } - } - if (block.isValid()) { - fragmentIt = block.begin(); - block = block.next(); - } else { - break; - } - } - // Insertion goes till the end of the text => not strictly inside a tag. - return true; -} - -struct FormattingAction { - enum class Type { - Invalid, - InsertEmoji, - TildeFont, - RemoveTag, - }; - Type type = Type::Invalid; - EmojiPtr emoji = nullptr; - bool isTilde = false; - int intervalStart = 0; - int intervalEnd = 0; -}; - -} // namespace - -void FlatTextarea::processFormatting(int insertPosition, int insertEnd) { - // Tilde formatting. - auto regularFont = qsl("Open Sans"), semiboldFont = qsl("Open Sans Semibold"); - bool tildeFormatting = !cRetina() && (font().pixelSize() == 13) && (font().family() == regularFont); - bool isTildeFragment = false; - - // First tag handling (the one we inserted text to). - bool startTagFound = false; - bool breakTagOnNotLetter = false; - - auto doc = document(); - - // Apply inserted tags. - auto insertedTagsProcessor = _insertedTagsAreFromMime ? _tagMimeProcessor.get() : nullptr; - int breakTagOnNotLetterTill = processInsertedTags(_st.textColor, doc, insertPosition, insertEnd, - _insertedTags, insertedTagsProcessor); - using ActionType = FormattingAction::Type; - while (true) { - FormattingAction action; - - auto fromBlock = doc->findBlock(insertPosition); - auto tillBlock = doc->findBlock(insertEnd); - if (tillBlock.isValid()) tillBlock = tillBlock.next(); - - for (auto block = fromBlock; block != tillBlock; block = block.next()) { - for (auto fragmentIt = block.begin(); !fragmentIt.atEnd(); ++fragmentIt) { - auto fragment = fragmentIt.fragment(); - t_assert(fragment.isValid()); - - int fragmentPosition = fragment.position(); - if (insertPosition >= fragmentPosition + fragment.length()) { - continue; - } - int changedPositionInFragment = insertPosition - fragmentPosition; // Can be negative. - int changedEndInFragment = insertEnd - fragmentPosition; - if (changedEndInFragment <= 0) { - break; - } - - auto charFormat = fragment.charFormat(); - if (tildeFormatting) { - isTildeFragment = (charFormat.fontFamily() == semiboldFont); - } - - auto fragmentText = fragment.text(); - auto *textStart = fragmentText.constData(); - auto *textEnd = textStart + fragmentText.size(); - - if (!startTagFound) { - startTagFound = true; - auto tagName = charFormat.anchorName(); - if (!tagName.isEmpty()) { - breakTagOnNotLetter = wasInsertTillTheEndOfTag(block, fragmentIt, insertEnd); - } - } - - auto *ch = textStart + qMax(changedPositionInFragment, 0); - for (; ch < textEnd; ++ch) { - int emojiLength = 0; - if (auto emoji = emojiFromText(ch, textEnd, &emojiLength)) { - // Replace emoji if no current action is prepared. - if (action.type == ActionType::Invalid) { - action.type = ActionType::InsertEmoji; - action.emoji = emoji; - action.intervalStart = fragmentPosition + (ch - textStart); - action.intervalEnd = action.intervalStart + emojiLength; - } - break; - } - - if (breakTagOnNotLetter && !ch->isLetter()) { - // Remove tag name till the end if no current action is prepared. - if (action.type != ActionType::Invalid) { - break; - } - breakTagOnNotLetter = false; - if (fragmentPosition + (ch - textStart) < breakTagOnNotLetterTill) { - action.type = ActionType::RemoveTag; - action.intervalStart = fragmentPosition + (ch - textStart); - action.intervalEnd = breakTagOnNotLetterTill; - break; - } - } - if (tildeFormatting) { // Tilde symbol fix in OpenSans. - bool tilde = (ch->unicode() == '~'); - if ((tilde && !isTildeFragment) || (!tilde && isTildeFragment)) { - if (action.type == ActionType::Invalid) { - action.type = ActionType::TildeFont; - action.intervalStart = fragmentPosition + (ch - textStart); - action.intervalEnd = action.intervalStart + 1; - action.isTilde = tilde; - } else { - ++action.intervalEnd; - } - } else if (action.type == ActionType::TildeFont) { - break; - } - } - - if (ch + 1 < textEnd && ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) { - ++ch; - ++fragmentPosition; - } - } - if (action.type != ActionType::Invalid) break; - } - if (action.type != ActionType::Invalid) break; - } - if (action.type != ActionType::Invalid) { - prepareFormattingOptimization(doc); - - QTextCursor c(doc->docHandle(), 0); - c.setPosition(action.intervalStart); - c.setPosition(action.intervalEnd, QTextCursor::KeepAnchor); - if (action.type == ActionType::InsertEmoji) { - insertEmoji(action.emoji, c); - insertPosition = action.intervalStart + 1; - } else if (action.type == ActionType::RemoveTag) { - QTextCharFormat format; - format.setAnchor(false); - format.setAnchorName(QString()); - format.setForeground(_st.textColor); - c.mergeCharFormat(format); - } else if (action.type == ActionType::TildeFont) { - QTextCharFormat format; - format.setFontFamily(action.isTilde ? semiboldFont : regularFont); - c.mergeCharFormat(format); - insertPosition = action.intervalEnd; - } - } else { - break; - } - } -} - -void FlatTextarea::onDocumentContentsChange(int position, int charsRemoved, int charsAdded) { - if (_correcting) return; - - int insertPosition = (_realInsertPosition >= 0) ? _realInsertPosition : position; - int insertLength = (_realInsertPosition >= 0) ? _realCharsAdded : charsAdded; - - int removePosition = position; - int removeLength = charsRemoved; - - QTextCursor(document()->docHandle(), 0).joinPreviousEditBlock(); - - _correcting = true; - if (_maxLength >= 0) { - QTextCursor c(document()->docHandle(), 0); - c.movePosition(QTextCursor::End); - int32 fullSize = c.position(), toRemove = fullSize - _maxLength; - if (toRemove > 0) { - if (toRemove > insertLength) { - if (insertLength) { - c.setPosition(insertPosition); - c.setPosition((insertPosition + insertLength), QTextCursor::KeepAnchor); - c.removeSelectedText(); - } - c.setPosition(fullSize - (toRemove - insertLength)); - c.setPosition(fullSize, QTextCursor::KeepAnchor); - c.removeSelectedText(); - } else { - c.setPosition(insertPosition + (insertLength - toRemove)); - c.setPosition(insertPosition + insertLength, QTextCursor::KeepAnchor); - c.removeSelectedText(); - } - } - } - _correcting = false; - - if (insertPosition == removePosition) { - if (!_links.isEmpty()) { - bool changed = false; - for (auto i = _links.begin(); i != _links.end();) { - if (i->start + i->length <= insertPosition) { - ++i; - } else if (i->start >= removePosition + removeLength) { - i->start += insertLength - removeLength; - ++i; - } else { - i = _links.erase(i); - changed = true; - } - } - if (changed) emit linksChanged(); - } - } else { - parseLinks(); - } - - if (document()->availableRedoSteps() > 0) { - QTextCursor(document()->docHandle(), 0).endEditBlock(); - return; - } - - if (insertLength <= 0) { - QTextCursor(document()->docHandle(), 0).endEditBlock(); - return; - } - - _correcting = true; - auto pageSize = document()->pageSize(); - processFormatting(insertPosition, insertPosition + insertLength); - if (document()->pageSize() != pageSize) { - document()->setPageSize(pageSize); - } - _correcting = false; - - QTextCursor(document()->docHandle(), 0).endEditBlock(); -} - -void FlatTextarea::onDocumentContentsChanged() { - if (_correcting) return; - - auto tagsChanged = false; - auto curText = getTextPart(0, -1, &_lastTextWithTags.tags, &tagsChanged); - - _correcting = true; - correctValue(_lastTextWithTags.text, curText, _lastTextWithTags.tags); - _correcting = false; - - bool textOrTagsChanged = tagsChanged || (_lastTextWithTags.text != curText); - if (textOrTagsChanged) { - _lastTextWithTags.text = curText; - emit changed(); - checkContentHeight(); - } - updatePlaceholder(); - if (App::wnd()) App::wnd()->updateGlobalMenu(); -} - -void FlatTextarea::onUndoAvailable(bool avail) { - _undoAvailable = avail; - if (App::wnd()) App::wnd()->updateGlobalMenu(); -} - -void FlatTextarea::onRedoAvailable(bool avail) { - _redoAvailable = avail; - if (App::wnd()) App::wnd()->updateGlobalMenu(); -} - -void FlatTextarea::step_appearance(float64 ms, bool timer) { - float dt = ms / _st.phDuration; - if (dt >= 1) { - _a_appearance.stop(); - a_phLeft.finish(); - a_phAlpha.finish(); - a_phColorFocused.finish(); - a_phLeft = anim::ivalue(a_phLeft.current()); - a_phAlpha = anim::fvalue(a_phAlpha.current()); - a_phColorFocused = anim::fvalue(a_phColorFocused.current()); - } else { - a_phLeft.update(dt, anim::linear); - a_phAlpha.update(dt, anim::linear); - a_phColorFocused.update(dt, anim::linear); - } - if (timer) update(); -} - -void FlatTextarea::setPlaceholder(const QString &ph, int32 afterSymbols) { - _ph = ph; - if (_phAfter != afterSymbols) { - _phAfter = afterSymbols; - updatePlaceholder(); - } - int skipWidth = placeholderSkipWidth(); - _phelided = _st.font->elided(_ph, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1 - skipWidth); - if (_phVisible) update(); -} - -void FlatTextarea::updatePlaceholder() { - bool vis = (getTextWithTags().text.size() <= _phAfter); - if (vis == _phVisible) return; - - a_phLeft.start(vis ? 0 : _st.phShift); - a_phAlpha.start(vis ? 1 : 0); - _a_appearance.start(); - - _phVisible = vis; -} - -QMimeData *FlatTextarea::createMimeDataFromSelection() const { - QMimeData *result = new QMimeData(); - QTextCursor c(textCursor()); - int32 start = c.selectionStart(), end = c.selectionEnd(); - if (end > start) { - TagList tags; - result->setText(getTextPart(start, end, &tags)); - if (!tags.isEmpty()) { - if (_tagMimeProcessor) { - for (auto &tag : tags) { - tag.id = _tagMimeProcessor->mimeTagFromTag(tag.id); - } - } - result->setData(tagsMimeType(), serializeTagsList(tags)); - } - } - return result; -} - -void FlatTextarea::setSubmitSettings(SubmitSettings settings) { - _submitSettings = settings; -} - -void FlatTextarea::keyPressEvent(QKeyEvent *e) { - bool shift = e->modifiers().testFlag(Qt::ShiftModifier); - bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier); - bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier); - bool enterSubmit = (ctrl && shift); - if (ctrl && _submitSettings != SubmitSettings::None && _submitSettings != SubmitSettings::Enter) { - enterSubmit = true; - } - if (!ctrl && !shift && _submitSettings != SubmitSettings::None && _submitSettings != SubmitSettings::CtrlEnter) { - enterSubmit = true; - } - bool enter = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return); - - if (macmeta && e->key() == Qt::Key_Backspace) { - QTextCursor tc(textCursor()), start(tc); - start.movePosition(QTextCursor::StartOfLine); - tc.setPosition(start.position(), QTextCursor::KeepAnchor); - tc.removeSelectedText(); - } else if (enter && enterSubmit) { - emit submitted(ctrl && shift); - } else if (e->key() == Qt::Key_Escape) { - emit cancelled(); - } else if (e->key() == Qt::Key_Tab || (ctrl && e->key() == Qt::Key_Backtab)) { - if (ctrl) { - e->ignore(); - } else { - emit tabbed(); - } - } else if (e->key() == Qt::Key_Search || e == QKeySequence::Find) { - e->ignore(); -#ifdef Q_OS_MAC - } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { - auto cursor = textCursor(); - int start = cursor.selectionStart(), end = cursor.selectionEnd(); - if (end > start) { - TagList tags; - QApplication::clipboard()->setText(getTextPart(start, end, &tags), QClipboard::FindBuffer); - } -#endif // Q_OS_MAC - } else { - QTextCursor tc(textCursor()); - if (enter && ctrl) { - e->setModifiers(e->modifiers() & ~Qt::ControlModifier); - } - bool spaceOrReturn = false; - QString t(e->text()); - if (!t.isEmpty() && t.size() < 3) { - if (t.at(0) == '\n' || t.at(0) == '\r' || t.at(0).isSpace() || t.at(0) == QChar::LineSeparator) { - spaceOrReturn = true; - } - } - QTextEdit::keyPressEvent(e); - if (tc == textCursor()) { - bool check = false; - if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_Up) { - tc.movePosition(QTextCursor::Start, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); - check = true; - } else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) { - tc.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); - check = true; - } - if (check) { - if (tc == textCursor()) { - e->ignore(); - } else { - setTextCursor(tc); - } - } - } - if (spaceOrReturn) emit spacedReturnedPasted(); - } -} - -void FlatTextarea::resizeEvent(QResizeEvent *e) { - _phelided = _st.font->elided(_ph, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1); - QTextEdit::resizeEvent(e); - checkContentHeight(); -} - -void FlatTextarea::mousePressEvent(QMouseEvent *e) { - QTextEdit::mousePressEvent(e); -} - -void FlatTextarea::dropEvent(QDropEvent *e) { - _inDrop = true; - QTextEdit::dropEvent(e); - _inDrop = false; - _insertedTags.clear(); - _realInsertPosition = -1; - - emit spacedReturnedPasted(); -} - -void FlatTextarea::contextMenuEvent(QContextMenuEvent *e) { - if (auto menu = createStandardContextMenu()) { - (new Ui::PopupMenu(menu))->popup(e->globalPos()); - } -} diff --git a/Telegram/SourceFiles/ui/flattextarea.h b/Telegram/SourceFiles/ui/flattextarea.h deleted file mode 100644 index d78b6f904..000000000 --- a/Telegram/SourceFiles/ui/flattextarea.h +++ /dev/null @@ -1,261 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#pragma once - -#include "animation.h" - -class UserData; -static UserData * const LookingUpInlineBot = SharedMemoryLocation(); - -class FlatTextarea : public QTextEdit { - Q_OBJECT - T_WIDGET - -public: - struct Tag { - int offset, length; - QString id; - }; - using TagList = QVector; - struct TextWithTags { - using Tags = FlatTextarea::TagList; - QString text; - Tags tags; - }; - - static QByteArray serializeTagsList(const TagList &tags); - static TagList deserializeTagsList(QByteArray data, int textLength); - static QString tagsMimeType(); - - FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &ph = QString(), const QString &val = QString(), const TagList &tags = TagList()); - - void setMaxLength(int32 maxLength); - void setMinHeight(int32 minHeight); - void setMaxHeight(int32 maxHeight); - - void setPlaceholder(const QString &ph, int32 afterSymbols = 0); - void updatePlaceholder(); - void finishPlaceholder(); - - QRect getTextRect() const; - int32 fakeMargin() const; - - void step_appearance(float64 ms, bool timer); - - QSize sizeHint() const override; - QSize minimumSizeHint() const override; - - EmojiPtr getSingleEmoji() const; - QString getMentionHashtagBotCommandPart(bool &start) const; - - // Get the current inline bot and request string for it. - // The *outInlineBot can be filled by LookingUpInlineBot shared ptr. - // In that case the caller should lookup the bot by *outInlineBotUsername. - QString getInlineBotQuery(UserData **outInlineBot, QString *outInlineBotUsername) const; - - void removeSingleEmoji(); - bool hasText() const; - - bool isUndoAvailable() const; - bool isRedoAvailable() const; - - void parseLinks(); - QStringList linksList() const; - - void insertFromMimeData(const QMimeData *source) override; - - QMimeData *createMimeDataFromSelection() const override; - - enum class SubmitSettings { - None, - Enter, - CtrlEnter, - Both, - }; - void setSubmitSettings(SubmitSettings settings); - - const TextWithTags &getTextWithTags() const { - return _lastTextWithTags; - } - TextWithTags getTextWithTagsPart(int start, int end = -1); - void insertTag(const QString &text, QString tagId = QString()); - - bool isEmpty() const { - return _lastTextWithTags.text.isEmpty(); - } - - enum UndoHistoryAction { - AddToUndoHistory, - MergeWithUndoHistory, - ClearUndoHistory - }; - void setTextWithTags(const TextWithTags &textWithTags, UndoHistoryAction undoHistoryAction = AddToUndoHistory); - - // If you need to make some preparations of tags before putting them to QMimeData - // (and then to clipboard or to drag-n-drop object), here is a strategy for that. - class TagMimeProcessor { - public: - virtual QString mimeTagFromTag(const QString &tagId) = 0; - virtual QString tagFromMimeTag(const QString &mimeTag) = 0; - virtual ~TagMimeProcessor() { - } - }; - void setTagMimeProcessor(std_::unique_ptr &&processor); - -public slots: - void onTouchTimer(); - - void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); - void onDocumentContentsChanged(); - - void onUndoAvailable(bool avail); - void onRedoAvailable(bool avail); - -signals: - void resized(); - void changed(); - void submitted(bool ctrlShiftEnter); - void cancelled(); - void tabbed(); - void spacedReturnedPasted(); - void linksChanged(); - -protected: - void enterEventHook(QEvent *e) { - return QTextEdit::enterEvent(e); - } - void leaveEventHook(QEvent *e) { - return QTextEdit::leaveEvent(e); - } - - bool viewportEvent(QEvent *e) override; - void touchEvent(QTouchEvent *e); - void paintEvent(QPaintEvent *e) override; - void focusInEvent(QFocusEvent *e) override; - void focusOutEvent(QFocusEvent *e) override; - void keyPressEvent(QKeyEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void dropEvent(QDropEvent *e) override; - void contextMenuEvent(QContextMenuEvent *e) override; - - virtual void correctValue(const QString &was, QString &now, TagList &nowTags) { - } - - void insertEmoji(EmojiPtr emoji, QTextCursor c); - - QVariant loadResource(int type, const QUrl &name) override; - - void checkContentHeight(); - -private: - // "start" and "end" are in coordinates of text where emoji are replaced - // by ObjectReplacementCharacter. If "end" = -1 means get text till the end. - QString getTextPart(int start, int end, TagList *outTagsList, bool *outTagsChanged = nullptr) const; - - void getSingleEmojiFragment(QString &text, QTextFragment &fragment) const; - - // After any characters added we must postprocess them. This includes: - // 1. Replacing font family to semibold for ~ characters, if we used Open Sans 13px. - // 2. Replacing font family from semibold for all non-~ characters, if we used ... - // 3. Replacing emoji code sequences by ObjectReplacementCharacters with emoji pics. - // 4. Interrupting tags in which the text was inserted by any char except a letter. - // 5. Applying tags from "_insertedTags" in case we pasted text with tags, not just text. - // Rule 4 applies only if we inserted chars not in the middle of a tag (but at the end). - void processFormatting(int changedPosition, int changedEnd); - - bool heightAutoupdated(); - - int placeholderSkipWidth() const; - - int _minHeight = -1; // < 0 - no autosize - int _maxHeight = -1; - int _maxLength = -1; - SubmitSettings _submitSettings = SubmitSettings::Enter; - - QString _ph, _phelided; - int _phAfter = 0; - bool _phVisible; - anim::ivalue a_phLeft; - anim::fvalue a_phAlpha; - anim::fvalue a_phColorFocused; - Animation _a_appearance; - - TextWithTags _lastTextWithTags; - - // Tags list which we should apply while setText() call or insert from mime data. - TagList _insertedTags; - bool _insertedTagsAreFromMime; - - // Override insert position and charsAdded from complex text editing - // (like drag-n-drop in the same text edit field). - int _realInsertPosition = -1; - int _realCharsAdded = 0; - - std_::unique_ptr _tagMimeProcessor; - - const style::flatTextarea &_st; - - bool _undoAvailable = false; - bool _redoAvailable = false; - bool _inDrop = false; - bool _inHeightCheck = false; - - int _fakeMargin = 0; - - QTimer _touchTimer; - bool _touchPress = false; - bool _touchRightButton = false; - bool _touchMove = false; - QPoint _touchStart; - - bool _correcting = false; - - struct LinkRange { - int start; - int length; - }; - friend bool operator==(const LinkRange &a, const LinkRange &b); - friend bool operator!=(const LinkRange &a, const LinkRange &b); - using LinkRanges = QVector; - LinkRanges _links; -}; - -inline bool operator==(const FlatTextarea::Tag &a, const FlatTextarea::Tag &b) { - return (a.offset == b.offset) && (a.length == b.length) && (a.id == b.id); -} -inline bool operator!=(const FlatTextarea::Tag &a, const FlatTextarea::Tag &b) { - return !(a == b); -} - -inline bool operator==(const FlatTextarea::TextWithTags &a, const FlatTextarea::TextWithTags &b) { - return (a.text == b.text) && (a.tags == b.tags); -} -inline bool operator!=(const FlatTextarea::TextWithTags &a, const FlatTextarea::TextWithTags &b) { - return !(a == b); -} - -inline bool operator==(const FlatTextarea::LinkRange &a, const FlatTextarea::LinkRange &b) { - return (a.start == b.start) && (a.length == b.length); -} -inline bool operator!=(const FlatTextarea::LinkRange &a, const FlatTextarea::LinkRange &b) { - return !(a == b); -} diff --git a/Telegram/SourceFiles/ui/widgets/buttons.cpp b/Telegram/SourceFiles/ui/widgets/buttons.cpp index c36909300..2e8457c15 100644 --- a/Telegram/SourceFiles/ui/widgets/buttons.cpp +++ b/Telegram/SourceFiles/ui/widgets/buttons.cpp @@ -21,6 +21,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "ui/widgets/buttons.h" +#include "ui/effects/ripple_animation.h" + namespace Ui { FlatButton::FlatButton(QWidget *parent, const QString &text, const style::FlatButton &st) : AbstractButton(parent) @@ -87,6 +89,27 @@ void FlatButton::onStateChanged(int oldState, StateChangeSource source) { } else { _a_appearance.start(); } + + handleRipples(oldState & StateDown, (source == StateChangeSource::ByPress)); +} + +void FlatButton::handleRipples(bool wasDown, bool wasPress) { + auto down = static_cast(_state & StateDown); + if (!_st.ripple.showDuration || down == wasDown) { + return; + } + + if (down && wasPress) { + // Start a ripple only from mouse press. + if (!_ripple) { + _ripple = std_::make_unique(_st.ripple, prepareRippleMask(), [this] { update(); }); + } + auto clickPosition = mapFromGlobal(QCursor::pos()); + _ripple->add(clickPosition); + } else if (!down && _ripple) { + // Finish ripple anyway. + _ripple->stopLast(); + } } void FlatButton::paintEvent(QPaintEvent *e) { @@ -97,6 +120,14 @@ void FlatButton::paintEvent(QPaintEvent *e) { p.setOpacity(_opacity); p.fillRect(r, anim::brush(_st.bgColor, _st.overBgColor, a_over.current())); + auto ms = getms(); + if (_ripple) { + _ripple->paint(p, 0, 0, width(), ms); + if (_ripple->empty()) { + _ripple.reset(); + } + } + p.setFont((_state & StateOver) ? _st.overFont : _st.font); p.setRenderHint(QPainter::TextAntialiasing); p.setPen(anim::pen(_st.color, _st.overColor, a_over.current())); @@ -107,6 +138,15 @@ void FlatButton::paintEvent(QPaintEvent *e) { p.drawText(r, _text, style::al_top); } +QImage FlatButton::prepareRippleMask() const { + auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + result.fill(QColor(255, 255, 255)); + return std_::move(result); +} + +FlatButton::~FlatButton() = default; + LinkButton::LinkButton(QWidget *parent, const QString &text, const style::LinkButton &st) : AbstractButton(parent) , _text(text) , _textWidth(st.font->width(_text)) @@ -210,16 +250,25 @@ int RoundButton::contentWidth() const { void RoundButton::paintEvent(QPaintEvent *e) { Painter p(this); - int innerWidth = contentWidth(); - auto rounded = rtlrect(rect().marginsRemoved(_st.padding), width()); + auto innerWidth = contentWidth(); + auto rounded = rect().marginsRemoved(_st.padding); if (_fullWidthOverride < 0) { rounded = QRect(0, rounded.top(), innerWidth - _fullWidthOverride, rounded.height()); } - App::roundRect(p, rounded, _st.textBg, ImageRoundRadius::Small); + App::roundRect(p, myrtlrect(rounded), _st.textBg, ImageRoundRadius::Small); auto over = (_state & StateOver); + auto down = (_state & StateDown); if (over) { - App::roundRect(p, rounded, _st.textBgOver, ImageRoundRadius::Small); + App::roundRect(p, myrtlrect(rounded), _st.textBgOver, ImageRoundRadius::Small); + } + + auto ms = getms(); + if (_ripple) { + _ripple->paint(p, rounded.x(), rounded.y(), width(), ms); + if (_ripple->empty()) { + _ripple.reset(); + } } p.setFont(_st.font); @@ -230,12 +279,12 @@ void RoundButton::paintEvent(QPaintEvent *e) { int textTopDelta = (_state & StateDown) ? (_st.downTextTop - _st.textTop) : 0; int textTop = _st.padding.top() + _st.textTop + textTopDelta; if (!_text.isEmpty()) { - p.setPen(over ? _st.textFgOver : _st.textFg); + p.setPen((over || down) ? _st.textFgOver : _st.textFg); p.drawTextLeft(textLeft, textTop, width(), _text); } if (!_secondaryText.isEmpty()) { textLeft += _textWidth + (_textWidth ? _st.secondarySkip : 0); - p.setPen(over ? _st.secondaryTextFgOver : _st.secondaryTextFg); + p.setPen((over || down) ? _st.secondaryTextFgOver : _st.secondaryTextFg); p.drawTextLeft(textLeft, textTop, width(), _secondaryText); } _st.icon.paint(p, QPoint(_st.padding.left(), _st.padding.right() + textTopDelta), width()); @@ -243,8 +292,50 @@ void RoundButton::paintEvent(QPaintEvent *e) { void RoundButton::onStateChanged(int oldState, StateChangeSource source) { update(); + + handleRipples(oldState & StateDown, (source == StateChangeSource::ByPress)); } +void RoundButton::handleRipples(bool wasDown, bool wasPress) { + auto down = static_cast(_state & StateDown); + if (!_st.ripple.showDuration || down == wasDown) { + return; + } + + if (down && wasPress) { + // Start a ripple only from mouse press. + if (!_ripple) { + _ripple = std_::make_unique(_st.ripple, prepareRippleMask(), [this] { update(); }); + } + auto clickPosition = mapFromGlobal(QCursor::pos()) - QPoint(_st.padding.left(), _st.padding.top()); + _ripple->add(clickPosition); + } else if (!down && _ripple) { + // Finish ripple anyway. + _ripple->stopLast(); + } +} + +QImage RoundButton::prepareRippleMask() const { + auto innerWidth = contentWidth(); + auto rounded = rtlrect(rect().marginsRemoved(_st.padding), width()); + if (_fullWidthOverride < 0) { + rounded = QRect(0, rounded.top(), innerWidth - _fullWidthOverride, rounded.height()); + } + + auto result = QImage(rounded.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + result.fill(Qt::transparent); + { + Painter p(&result); + p.setPen(Qt::NoPen); + p.setBrush(QColor(255, 255, 255)); + p.drawRoundedRect(rounded.translated(-rounded.topLeft()), st::buttonRadius, st::buttonRadius); + } + return std_::move(result); +} + +RoundButton::~RoundButton() = default; + IconButton::IconButton(QWidget *parent, const style::IconButton &st) : AbstractButton(parent) , _st(st) { resize(_st.width, _st.height); @@ -260,6 +351,15 @@ void IconButton::setIcon(const style::icon *icon, const style::icon *iconOver) { void IconButton::paintEvent(QPaintEvent *e) { Painter p(this); + auto ms = getms(); + + if (_ripple) { + _ripple->paint(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), width(), ms); + if (_ripple->empty()) { + _ripple.reset(); + } + } + auto over = _a_over.current(getms(), (_state & StateOver) ? 1. : 0.); auto overIcon = [this] { if (_iconOverrideOver) { @@ -306,6 +406,50 @@ void IconButton::onStateChanged(int oldState, StateChangeSource source) { update(); } } + + handleRipples(oldState & StateDown, (source == StateChangeSource::ByPress)); } +void IconButton::handleRipples(bool wasDown, bool wasPress) { + auto down = static_cast(_state & StateDown); + if (!_st.ripple.showDuration || _st.rippleAreaSize <= 0 || down == wasDown) { + return; + } + + if (down && wasPress) { + // Start a ripple only from mouse press. + if (!_ripple) { + _ripple = std_::make_unique(_st.ripple, prepareRippleMask(), [this] { update(); }); + } + auto clickPosition = mapFromGlobal(QCursor::pos()); + auto rippleCenter = QRect(_st.rippleAreaPosition, QSize(_st.rippleAreaSize, _st.rippleAreaSize)).center(); + auto clickRadiusSquare = style::point::dotProduct(clickPosition - rippleCenter, clickPosition - rippleCenter); + auto startRadius = 0; + if (clickRadiusSquare * 4 > _st.rippleAreaSize * _st.rippleAreaSize) { + startRadius = sqrt(clickRadiusSquare) - (_st.rippleAreaSize / 2); + } + _ripple->add(clickPosition - _st.rippleAreaPosition, startRadius); + } else if (!down && _ripple) { + // Finish ripple anyway. + _ripple->stopLast(); + } +} + +QImage IconButton::prepareRippleMask() const { + auto size = _st.rippleAreaSize * cIntRetinaFactor(); + auto result = QImage(size, size, QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + result.fill(Qt::transparent); + { + Painter p(&result); + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.setPen(Qt::NoPen); + p.setBrush(QColor(255, 255, 255)); + p.drawEllipse(0, 0, _st.rippleAreaSize, _st.rippleAreaSize); + } + return std_::move(result); +} + +IconButton::~IconButton() = default; + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/buttons.h b/Telegram/SourceFiles/ui/widgets/buttons.h index f7c1011a5..c9f5ca38c 100644 --- a/Telegram/SourceFiles/ui/widgets/buttons.h +++ b/Telegram/SourceFiles/ui/widgets/buttons.h @@ -25,6 +25,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Ui { +class RippleAnimation; + class FlatButton : public AbstractButton { public: FlatButton(QWidget *parent, const QString &text, const style::FlatButton &st); @@ -38,12 +40,17 @@ public: int32 textWidth() const; + ~FlatButton(); + protected: void paintEvent(QPaintEvent *e) override; void onStateChanged(int oldState, StateChangeSource source) override; private: + QImage prepareRippleMask() const; + void handleRipples(bool wasDown, bool wasPress); + QString _text, _textForAutoSize; int _width; @@ -54,6 +61,8 @@ private: float64 _opacity = 1.; + std_::unique_ptr _ripple; + }; class LinkButton : public AbstractButton { @@ -93,12 +102,17 @@ public: }; void setTextTransform(TextTransform transform); + ~RoundButton(); + protected: void paintEvent(QPaintEvent *e) override; void onStateChanged(int oldState, StateChangeSource source) override; private: + QImage prepareRippleMask() const; + void handleRipples(bool wasDown, bool wasPress); + void updateText(); void resizeToText(); @@ -114,6 +128,8 @@ private: TextTransform _transform = TextTransform::ToUpper; + std_::unique_ptr _ripple; + }; class IconButton : public AbstractButton { @@ -123,18 +139,25 @@ public: // Pass nullptr to restore the default icon. void setIcon(const style::icon *icon, const style::icon *iconOver = nullptr); + ~IconButton(); + protected: void paintEvent(QPaintEvent *e) override; void onStateChanged(int oldState, StateChangeSource source) override; private: + QImage prepareRippleMask() const; + void handleRipples(bool wasDown, bool wasPress); + const style::IconButton &_st; const style::icon *_iconOverride = nullptr; const style::icon *_iconOverrideOver = nullptr; FloatAnimation _a_over; + std_::unique_ptr _ripple; + }; } // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/checkbox.h b/Telegram/SourceFiles/ui/widgets/checkbox.h index ad5b8c7cc..2b208c192 100644 --- a/Telegram/SourceFiles/ui/widgets/checkbox.h +++ b/Telegram/SourceFiles/ui/widgets/checkbox.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "ui/abstract_button.h" +#include "styles/style_widgets.h" namespace Ui { diff --git a/Telegram/SourceFiles/ui/flatinput.cpp b/Telegram/SourceFiles/ui/widgets/input_fields.cpp similarity index 63% rename from Telegram/SourceFiles/ui/flatinput.cpp rename to Telegram/SourceFiles/ui/widgets/input_fields.cpp index ec04a0da6..7086b53d9 100644 --- a/Telegram/SourceFiles/ui/flatinput.cpp +++ b/Telegram/SourceFiles/ui/widgets/input_fields.cpp @@ -19,59 +19,1462 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "ui/flatinput.h" +#include "ui/widgets/input_fields.h" #include "ui/widgets/popup_menu.h" #include "mainwindow.h" -#include "countryinput.h" +#include "ui/countryinput.h" #include "lang.h" #include "numbers.h" +namespace Ui { namespace { - template - class InputStyle : public QCommonStyle { - public: - InputStyle() { - setParent(QCoreApplication::instance()); - } - void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const { +template +class InputStyle : public QCommonStyle { +public: + InputStyle() { + setParent(QCoreApplication::instance()); + } + + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const { + } + QRect subElementRect(SubElement r, const QStyleOption *opt, const QWidget *widget = 0) const { + switch (r) { + case SE_LineEditContents: + const InputClass *w = widget ? qobject_cast(widget) : 0; + return w ? w->getTextRect() : QCommonStyle::subElementRect(r, opt, widget); + break; } - QRect subElementRect(SubElement r, const QStyleOption *opt, const QWidget *widget = 0) const { - switch (r) { - case SE_LineEditContents: - const InputClass *w = widget ? qobject_cast(widget) : 0; - return w ? w->getTextRect() : QCommonStyle::subElementRect(r, opt, widget); - break; + return QCommonStyle::subElementRect(r, opt, widget); + } + + static InputStyle *instance() { + if (!_instance) { + if (!QGuiApplication::instance()) { + return nullptr; } - return QCommonStyle::subElementRect(r, opt, widget); + _instance = new InputStyle(); } + return _instance; + } - static InputStyle *instance() { - if (!_instance) { - if (!QGuiApplication::instance()) { - return nullptr; - } - _instance = new InputStyle(); - } - return _instance; + ~InputStyle() { + _instance = nullptr; + } + +private: + static InputStyle *_instance; + +}; + +template +InputStyle *InputStyle::_instance = nullptr; + +} // namespace + +QByteArray FlatTextarea::serializeTagsList(const TagList &tags) { + if (tags.isEmpty()) { + return QByteArray(); + } + + QByteArray tagsSerialized; + { + QBuffer buffer(&tagsSerialized); + buffer.open(QIODevice::WriteOnly); + QDataStream stream(&buffer); + stream.setVersion(QDataStream::Qt_5_1); + stream << qint32(tags.size()); + for_const (auto &tag, tags) { + stream << qint32(tag.offset) << qint32(tag.length) << tag.id; } - - ~InputStyle() { - _instance = nullptr; - } - - private: - static InputStyle *_instance; - - }; - - template - InputStyle *InputStyle::_instance = nullptr; - + } + return tagsSerialized; } -FlatInput::FlatInput(QWidget *parent, const style::flatInput &st, const QString &pholder, const QString &v) : QLineEdit(v, parent) +FlatTextarea::TagList FlatTextarea::deserializeTagsList(QByteArray data, int textLength) { + TagList result; + if (data.isEmpty()) { + return result; + } + + QBuffer buffer(&data); + buffer.open(QIODevice::ReadOnly); + + QDataStream stream(&buffer); + stream.setVersion(QDataStream::Qt_5_1); + + qint32 tagCount = 0; + stream >> tagCount; + if (stream.status() != QDataStream::Ok) { + return result; + } + if (tagCount <= 0 || tagCount > textLength) { + return result; + } + + for (int i = 0; i < tagCount; ++i) { + qint32 offset = 0, length = 0; + QString id; + stream >> offset >> length >> id; + if (stream.status() != QDataStream::Ok) { + return result; + } + if (offset < 0 || length <= 0 || offset + length > textLength) { + return result; + } + result.push_back({ offset, length, id }); + } + return result; +} + +QString FlatTextarea::tagsMimeType() { + return qsl("application/x-td-field-tags"); +} + +FlatTextarea::FlatTextarea(QWidget *parent, const style::FlatTextarea &st, const QString &pholder, const QString &v, const TagList &tags) : QTextEdit(parent) +, _phVisible(!v.length()) +, a_phLeft(_phVisible ? 0 : st.phShift) +, a_phAlpha(_phVisible ? 1 : 0) +, a_phColorFocused(0) +, _a_appearance(animation(this, &FlatTextarea::step_appearance)) +, _lastTextWithTags { v, tags } +, _st(st) { + setAcceptRichText(false); + resize(_st.width, _st.font->height); + + setFont(_st.font->f); + setAlignment(_st.align); + + setPlaceholder(pholder); + + QPalette p(palette()); + p.setColor(QPalette::Text, _st.textColor->c); + setPalette(p); + + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + setFrameStyle(QFrame::NoFrame | QFrame::Plain); + viewport()->setAutoFillBackground(false); + + setContentsMargins(0, 0, 0, 0); + + switch (cScale()) { + case dbisOneAndQuarter: _fakeMargin = 1; break; + case dbisOneAndHalf: _fakeMargin = 2; break; + case dbisTwo: _fakeMargin = 4; break; + } + setStyleSheet(qsl("QTextEdit { margin: %1px; }").arg(_fakeMargin)); + + viewport()->setAttribute(Qt::WA_AcceptTouchEvents); + _touchTimer.setSingleShot(true); + connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); + + connect(document(), SIGNAL(contentsChange(int, int, int)), this, SLOT(onDocumentContentsChange(int, int, int))); + connect(document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged())); + connect(this, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool))); + connect(this, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool))); + if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); + + if (!_lastTextWithTags.text.isEmpty()) { + setTextWithTags(_lastTextWithTags, ClearUndoHistory); + } +} + +TextWithTags FlatTextarea::getTextWithTagsPart(int start, int end) { + TextWithTags result; + result.text = getTextPart(start, end, &result.tags); + return result; +} + +void FlatTextarea::setTextWithTags(const TextWithTags &textWithTags, UndoHistoryAction undoHistoryAction) { + _insertedTags = textWithTags.tags; + _insertedTagsAreFromMime = false; + _realInsertPosition = 0; + _realCharsAdded = textWithTags.text.size(); + auto doc = document(); + auto cursor = QTextCursor(doc->docHandle(), 0); + if (undoHistoryAction == ClearUndoHistory) { + doc->setUndoRedoEnabled(false); + cursor.beginEditBlock(); + } else if (undoHistoryAction == MergeWithUndoHistory) { + cursor.joinPreviousEditBlock(); + } else { + cursor.beginEditBlock(); + } + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + cursor.insertText(textWithTags.text); + cursor.movePosition(QTextCursor::End); + cursor.endEditBlock(); + if (undoHistoryAction == ClearUndoHistory) { + doc->setUndoRedoEnabled(true); + } + _insertedTags.clear(); + _realInsertPosition = -1; + finishPlaceholder(); +} + +void FlatTextarea::finishPlaceholder() { + if (_a_appearance.animating()) { + a_phLeft.finish(); + a_phAlpha.finish(); + _a_appearance.stop(); + update(); + } +} + +void FlatTextarea::setMaxLength(int32 maxLength) { + _maxLength = maxLength; +} + +void FlatTextarea::setMinHeight(int32 minHeight) { + _minHeight = minHeight; + heightAutoupdated(); +} + +void FlatTextarea::setMaxHeight(int32 maxHeight) { + _maxHeight = maxHeight; + heightAutoupdated(); +} + +bool FlatTextarea::heightAutoupdated() { + if (_minHeight < 0 || _maxHeight < 0 || _inHeightCheck) return false; + _inHeightCheck = true; + + myEnsureResized(this); + + int newh = ceil(document()->size().height()) + 2 * fakeMargin(); + if (newh > _maxHeight) { + newh = _maxHeight; + } else if (newh < _minHeight) { + newh = _minHeight; + } + if (height() != newh) { + resize(width(), newh); + _inHeightCheck = false; + return true; + } + _inHeightCheck = false; + return false; +} + +void FlatTextarea::onTouchTimer() { + _touchRightButton = true; +} + +bool FlatTextarea::viewportEvent(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 QTextEdit::viewportEvent(e); + } + } + return QTextEdit::viewportEvent(e); +} + +void FlatTextarea::touchEvent(QTouchEvent *e) { + switch (e->type()) { + case QEvent::TouchBegin: + if (_touchPress || e->touchPoints().isEmpty()) return; + _touchTimer.start(QApplication::startDragTime()); + _touchPress = true; + _touchMove = _touchRightButton = false; + _touchStart = e->touchPoints().cbegin()->screenPos().toPoint(); + break; + + case QEvent::TouchUpdate: + if (!_touchPress || e->touchPoints().isEmpty()) return; + if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { + _touchMove = true; + } + break; + + case QEvent::TouchEnd: + if (!_touchPress) return; + if (!_touchMove && window()) { + Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton); + QPoint mapped(mapFromGlobal(_touchStart)), winMapped(window()->mapFromGlobal(_touchStart)); + + if (_touchRightButton) { + QContextMenuEvent contextEvent(QContextMenuEvent::Mouse, mapped, _touchStart); + contextMenuEvent(&contextEvent); + } + } + _touchTimer.stop(); + _touchPress = _touchMove = _touchRightButton = false; + break; + + case QEvent::TouchCancel: + _touchPress = false; + _touchTimer.stop(); + break; + } +} + +QRect FlatTextarea::getTextRect() const { + return rect().marginsRemoved(_st.textMrg + st::textRectMargins); +} + +int32 FlatTextarea::fakeMargin() const { + return _fakeMargin; +} + +void FlatTextarea::paintEvent(QPaintEvent *e) { + QPainter p(viewport()); + QRect r(rect().intersected(e->rect())); + p.fillRect(r, _st.bgColor->b); + bool phDraw = _phVisible; + if (_a_appearance.animating()) { + p.setOpacity(a_phAlpha.current()); + phDraw = true; + } + if (phDraw) { + p.save(); + p.setClipRect(r); + p.setFont(_st.font); + p.setPen(anim::pen(_st.phColor, _st.phFocusColor, a_phColorFocused.current())); + if (_st.phAlign == style::al_topleft && _phAfter > 0) { + int skipWidth = placeholderSkipWidth(); + p.drawText(_st.textMrg.left() - _fakeMargin + a_phLeft.current() + skipWidth, _st.textMrg.top() - _fakeMargin - st::lineWidth + _st.font->ascent, _ph); + } else { + QRect phRect(_st.textMrg.left() - _fakeMargin + _st.phPos.x() + a_phLeft.current(), _st.textMrg.top() - _fakeMargin + _st.phPos.y(), width() - _st.textMrg.left() - _st.textMrg.right(), height() - _st.textMrg.top() - _st.textMrg.bottom()); + p.drawText(phRect, _ph, QTextOption(_st.phAlign)); + } + p.restore(); + p.setOpacity(1); + } + QTextEdit::paintEvent(e); +} + +int FlatTextarea::placeholderSkipWidth() const { + if (!_phAfter) { + return 0; + } + auto text = getTextWithTags().text; + auto result = _st.font->width(text.mid(0, _phAfter)); + if (_phAfter > text.size()) { + result += _st.font->spacew; + } + return result; +} + +void FlatTextarea::focusInEvent(QFocusEvent *e) { + a_phColorFocused.start(1.); + _a_appearance.start(); + QTextEdit::focusInEvent(e); +} + +void FlatTextarea::focusOutEvent(QFocusEvent *e) { + a_phColorFocused.start(0.); + _a_appearance.start(); + QTextEdit::focusOutEvent(e); +} + +QSize FlatTextarea::sizeHint() const { + return geometry().size(); +} + +QSize FlatTextarea::minimumSizeHint() const { + return geometry().size(); +} + +EmojiPtr FlatTextarea::getSingleEmoji() const { + QString text; + QTextFragment fragment; + + getSingleEmojiFragment(text, fragment); + + if (!text.isEmpty()) { + QTextCharFormat format = fragment.charFormat(); + QString imageName = static_cast(&format)->name(); + if (imageName.startsWith(qstr("emoji://e."))) { + return emojiFromUrl(imageName); + } + } + return nullptr; +} + +QString FlatTextarea::getInlineBotQuery(UserData **outInlineBot, QString *outInlineBotUsername) const { + t_assert(outInlineBot != nullptr); + t_assert(outInlineBotUsername != nullptr); + + auto &text = getTextWithTags().text; + auto textLength = text.size(); + + int inlineUsernameStart = 1, inlineUsernameLength = 0; + if (textLength > 2 && text.at(0) == '@' && text.at(1).isLetter()) { + inlineUsernameLength = 1; + for (int i = inlineUsernameStart + 1; i != textLength; ++i) { + if (text.at(i).isLetterOrNumber() || text.at(i).unicode() == '_') { + ++inlineUsernameLength; + continue; + } + if (!text.at(i).isSpace()) { + inlineUsernameLength = 0; + } + break; + } + auto inlineUsernameEnd = inlineUsernameStart + inlineUsernameLength; + auto inlineUsernameEqualsText = (inlineUsernameEnd == textLength); + auto validInlineUsername = false; + if (inlineUsernameEqualsText) { + validInlineUsername = text.endsWith(qstr("bot")); + } else if (inlineUsernameEnd < textLength && inlineUsernameLength) { + validInlineUsername = text.at(inlineUsernameEnd).isSpace(); + } + if (validInlineUsername) { + auto username = text.midRef(inlineUsernameStart, inlineUsernameLength); + if (username != *outInlineBotUsername) { + *outInlineBotUsername = username.toString(); + auto peer = App::peerByName(*outInlineBotUsername); + if (peer) { + if (peer->isUser()) { + *outInlineBot = peer->asUser(); + } else { + *outInlineBot = nullptr; + } + } else { + *outInlineBot = LookingUpInlineBot; + } + } + if (*outInlineBot == LookingUpInlineBot) return QString(); + + if (*outInlineBot && (!(*outInlineBot)->botInfo || (*outInlineBot)->botInfo->inlinePlaceholder.isEmpty())) { + *outInlineBot = nullptr; + } else { + return inlineUsernameEqualsText ? QString() : text.mid(inlineUsernameEnd + 1); + } + } else { + inlineUsernameLength = 0; + } + } + if (inlineUsernameLength < 3) { + *outInlineBot = nullptr; + *outInlineBotUsername = QString(); + } + return QString(); +} + +QString FlatTextarea::getMentionHashtagBotCommandPart(bool &start) const { + start = false; + + int32 pos = textCursor().position(); + if (textCursor().anchor() != pos) return QString(); + + // check mention / hashtag / bot command + QTextDocument *doc(document()); + QTextBlock block = doc->findBlock(pos); + for (QTextBlock::Iterator iter = block.begin(); !iter.atEnd(); ++iter) { + QTextFragment fr(iter.fragment()); + if (!fr.isValid()) continue; + + int32 p = fr.position(), e = (p + fr.length()); + if (p >= pos || e < pos) continue; + + QTextCharFormat f = fr.charFormat(); + if (f.isImageFormat()) continue; + + bool mentionInCommand = false; + QString t(fr.text()); + for (int i = pos - p; i > 0; --i) { + if (t.at(i - 1) == '@') { + if ((pos - p - i < 1 || t.at(i).isLetter()) && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) { + start = (i == 1) && (p == 0); + return t.mid(i - 1, pos - p - i + 1); + } else if ((pos - p - i < 1 || t.at(i).isLetter()) && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) { + mentionInCommand = true; + --i; + continue; + } + return QString(); + } else if (t.at(i - 1) == '#') { + if (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_')) { + start = (i == 1) && (p == 0); + return t.mid(i - 1, pos - p - i + 1); + } + return QString(); + } else if (t.at(i - 1) == '/') { + if (i < 2) { + start = (i == 1) && (p == 0); + return t.mid(i - 1, pos - p - i + 1); + } + return QString(); + } + if (pos - p - i > 127 || (!mentionInCommand && (pos - p - i > 63))) break; + if (!t.at(i - 1).isLetterOrNumber() && t.at(i - 1) != '_') break; + } + break; + } + return QString(); +} + +void FlatTextarea::insertTag(const QString &text, QString tagId) { + auto cursor = textCursor(); + int32 pos = cursor.position(); + + auto doc = document(); + auto block = doc->findBlock(pos); + for (auto iter = block.begin(); !iter.atEnd(); ++iter) { + auto fragment = iter.fragment(); + t_assert(fragment.isValid()); + + int fragmentPosition = fragment.position(); + int fragmentEnd = (fragmentPosition + fragment.length()); + if (fragmentPosition >= pos || fragmentEnd < pos) continue; + + auto format = fragment.charFormat(); + if (format.isImageFormat()) continue; + + bool mentionInCommand = false; + auto fragmentText = fragment.text(); + for (int i = pos - fragmentPosition; i > 0; --i) { + auto previousChar = fragmentText.at(i - 1); + if (previousChar == '@' || previousChar == '#' || previousChar == '/') { + if ((i == pos - fragmentPosition || (previousChar == '/' ? fragmentText.at(i).isLetterOrNumber() : fragmentText.at(i).isLetter()) || previousChar == '#') && + (i < 2 || !(fragmentText.at(i - 2).isLetterOrNumber() || fragmentText.at(i - 2) == '_'))) { + cursor.setPosition(fragmentPosition + i - 1); + int till = fragmentPosition + i; + for (; (till < fragmentEnd && till < pos); ++till) { + auto ch = fragmentText.at(till - fragmentPosition); + if (!ch.isLetterOrNumber() && ch != '_' && ch != '@') { + break; + } + } + if (till < fragmentEnd && fragmentText.at(till - fragmentPosition) == ' ') { + ++till; + } + cursor.setPosition(till, QTextCursor::KeepAnchor); + break; + } else if ((i == pos - fragmentPosition || fragmentText.at(i).isLetter()) && fragmentText.at(i - 1) == '@' && i > 2 && (fragmentText.at(i - 2).isLetterOrNumber() || fragmentText.at(i - 2) == '_') && !mentionInCommand) { + mentionInCommand = true; + --i; + continue; + } + break; + } + if (pos - fragmentPosition - i > 127 || (!mentionInCommand && (pos - fragmentPosition - i > 63))) break; + if (!fragmentText.at(i - 1).isLetterOrNumber() && fragmentText.at(i - 1) != '_') break; + } + break; + } + if (tagId.isEmpty()) { + QTextCharFormat format = cursor.charFormat(); + format.setAnchor(false); + format.setAnchorName(QString()); + format.clearForeground(); + cursor.insertText(text + ' ', format); + } else { + _insertedTags.clear(); + _insertedTags.push_back({ 0, text.size(), tagId }); + _insertedTagsAreFromMime = false; + cursor.insertText(text + ' '); + _insertedTags.clear(); + } +} + +void FlatTextarea::setTagMimeProcessor(std_::unique_ptr &&processor) { + _tagMimeProcessor = std_::move(processor); +} + +void FlatTextarea::getSingleEmojiFragment(QString &text, QTextFragment &fragment) const { + int32 end = textCursor().position(), start = end - 1; + if (textCursor().anchor() != end) return; + + if (start < 0) start = 0; + + QTextDocument *doc(document()); + QTextBlock from = doc->findBlock(start), till = doc->findBlock(end); + if (till.isValid()) till = till.next(); + + for (QTextBlock b = from; b != till; b = b.next()) { + for (QTextBlock::Iterator iter = b.begin(); !iter.atEnd(); ++iter) { + QTextFragment fr(iter.fragment()); + if (!fr.isValid()) continue; + + int32 p = fr.position(), e = (p + fr.length()); + if (p >= end || e <= start) { + continue; + } + + QTextCharFormat f = fr.charFormat(); + QString t(fr.text()); + if (p < start) { + t = t.mid(start - p, end - start); + } else if (e > end) { + t = t.mid(0, end - p); + } + if (f.isImageFormat() && !t.isEmpty() && t.at(0).unicode() == QChar::ObjectReplacementCharacter) { + QString imageName = static_cast(&f)->name(); + if (imageName.startsWith(qstr("emoji://e."))) { + fragment = fr; + text = t; + return; + } + } + return; + } + } + return; +} + +void FlatTextarea::removeSingleEmoji() { + QString text; + QTextFragment fragment; + + getSingleEmojiFragment(text, fragment); + + if (!text.isEmpty()) { + QTextCursor t(textCursor()); + t.setPosition(fragment.position()); + t.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor); + t.removeSelectedText(); + setTextCursor(t); + } +} + +namespace { + +class TagAccumulator { +public: + TagAccumulator(FlatTextarea::TagList *tags) : _tags(tags) { + } + + bool changed() const { + return _changed; + } + + void feed(const QString &randomTagId, int currentPosition) { + if (randomTagId == _currentTagId) return; + + if (!_currentTagId.isEmpty()) { + int randomPartPosition = _currentTagId.lastIndexOf('/'); + t_assert(randomPartPosition > 0); + + bool tagChanged = true; + if (_currentTag < _tags->size()) { + auto &alreadyTag = _tags->at(_currentTag); + if (alreadyTag.offset == _currentStart && + alreadyTag.length == currentPosition - _currentStart && + alreadyTag.id == _currentTagId.midRef(0, randomPartPosition)) { + tagChanged = false; + } + } + if (tagChanged) { + _changed = true; + TextWithTags::Tag tag = { + _currentStart, + currentPosition - _currentStart, + _currentTagId.mid(0, randomPartPosition), + }; + if (_currentTag < _tags->size()) { + (*_tags)[_currentTag] = tag; + } else { + _tags->push_back(tag); + } + } + ++_currentTag; + } + _currentTagId = randomTagId; + _currentStart = currentPosition; + }; + + void finish() { + if (_currentTag < _tags->size()) { + _tags->resize(_currentTag); + _changed = true; + } + } + +private: + FlatTextarea::TagList *_tags; + bool _changed = false; + + int _currentTag = 0; + int _currentStart = 0; + QString _currentTagId; + +}; + +} // namespace + +QString FlatTextarea::getTextPart(int start, int end, TagList *outTagsList, bool *outTagsChanged) const { + if (end >= 0 && end <= start) return QString(); + + if (start < 0) start = 0; + bool full = (start == 0) && (end < 0); + + TagAccumulator tagAccumulator(outTagsList); + + QTextDocument *doc(document()); + QTextBlock from = full ? doc->begin() : doc->findBlock(start), till = (end < 0) ? doc->end() : doc->findBlock(end); + if (till.isValid()) till = till.next(); + + int32 possibleLen = 0; + for (QTextBlock b = from; b != till; b = b.next()) { + possibleLen += b.length(); + } + QString result; + result.reserve(possibleLen + 1); + if (!full && end < 0) { + end = possibleLen; + } + + bool tillFragmentEnd = full; + for (auto b = from; b != till; b = b.next()) { + for (auto iter = b.begin(); !iter.atEnd(); ++iter) { + QTextFragment fragment(iter.fragment()); + if (!fragment.isValid()) continue; + + int32 p = full ? 0 : fragment.position(), e = full ? 0 : (p + fragment.length()); + if (!full) { + tillFragmentEnd = (e <= end); + if (p == end) { + tagAccumulator.feed(fragment.charFormat().anchorName(), result.size()); + } + if (p >= end) { + break; + } + if (e <= start) { + continue; + } + } + if (full || p >= start) { + tagAccumulator.feed(fragment.charFormat().anchorName(), result.size()); + } + + QTextCharFormat f = fragment.charFormat(); + QString emojiText; + QString t(fragment.text()); + if (!full) { + if (p < start) { + t = t.mid(start - p, end - start); + } else if (e > end) { + t = t.mid(0, end - p); + } + } + QChar *ub = t.data(), *uc = ub, *ue = uc + t.size(); + for (; uc != ue; ++uc) { + switch (uc->unicode()) { + case 0xfdd0: // QTextBeginningOfFrame + case 0xfdd1: // QTextEndOfFrame + case QChar::ParagraphSeparator: + case QChar::LineSeparator: + *uc = QLatin1Char('\n'); + break; + case QChar::Nbsp: + *uc = QLatin1Char(' '); + break; + case QChar::ObjectReplacementCharacter: + if (emojiText.isEmpty() && f.isImageFormat()) { + QString imageName = static_cast(&f)->name(); + if (imageName.startsWith(qstr("emoji://e."))) { + if (EmojiPtr emoji = emojiFromUrl(imageName)) { + emojiText = emojiString(emoji); + } + } + } + if (uc > ub) result.append(ub, uc - ub); + if (!emojiText.isEmpty()) result.append(emojiText); + ub = uc + 1; + break; + } + } + if (uc > ub) result.append(ub, uc - ub); + } + result.append('\n'); + } + result.chop(1); + + if (tillFragmentEnd) tagAccumulator.feed(QString(), result.size()); + tagAccumulator.finish(); + + if (outTagsChanged) { + *outTagsChanged = tagAccumulator.changed(); + } + return result; +} + +bool FlatTextarea::hasText() const { + QTextDocument *doc(document()); + QTextBlock from = doc->begin(), till = doc->end(); + + if (from == till) return false; + + for (QTextBlock::Iterator iter = from.begin(); !iter.atEnd(); ++iter) { + QTextFragment fragment(iter.fragment()); + if (!fragment.isValid()) continue; + if (!fragment.text().isEmpty()) return true; + } + return (from.next() != till); +} + +bool FlatTextarea::isUndoAvailable() const { + return _undoAvailable; +} + +bool FlatTextarea::isRedoAvailable() const { + return _redoAvailable; +} + +void FlatTextarea::parseLinks() { // some code is duplicated in text.cpp! + LinkRanges newLinks; + + QString text(toPlainText()); + if (text.isEmpty()) { + if (!_links.isEmpty()) { + _links.clear(); + emit linksChanged(); + } + return; + } + + initLinkSets(); + + int32 len = text.size(); + const QChar *start = text.unicode(), *end = start + text.size(); + for (int32 offset = 0, matchOffset = offset; offset < len;) { + QRegularExpressionMatch m = reDomain().match(text, matchOffset); + if (!m.hasMatch()) break; + + int32 domainOffset = m.capturedStart(); + + QString protocol = m.captured(1).toLower(); + QString topDomain = m.captured(3).toLower(); + + bool isProtocolValid = protocol.isEmpty() || validProtocols().contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); + bool isTopDomainValid = !protocol.isEmpty() || validTopDomains().contains(hashCrc32(topDomain.constData(), topDomain.size() * sizeof(QChar))); + + if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) { + QString forMailName = text.mid(offset, domainOffset - offset - 1); + QRegularExpressionMatch mMailName = reMailName().match(forMailName); + if (mMailName.hasMatch()) { + offset = matchOffset = m.capturedEnd(); + continue; + } + } + if (!isProtocolValid || !isTopDomainValid) { + offset = matchOffset = m.capturedEnd(); + continue; + } + + QStack parenth; + const QChar *domainEnd = start + m.capturedEnd(), *p = domainEnd; + for (; p < end; ++p) { + QChar ch(*p); + if (chIsLinkEnd(ch)) break; // link finished + if (chIsAlmostLinkEnd(ch)) { + const QChar *endTest = p + 1; + while (endTest < end && chIsAlmostLinkEnd(*endTest)) { + ++endTest; + } + if (endTest >= end || chIsLinkEnd(*endTest)) { + break; // link finished at p + } + p = endTest; + ch = *p; + } + if (ch == '(' || ch == '[' || ch == '{' || ch == '<') { + parenth.push(p); + } else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') { + if (parenth.isEmpty()) break; + const QChar *q = parenth.pop(), open(*q); + if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) { + p = q; + break; + } + } + } + if (p > domainEnd) { // check, that domain ended + if (domainEnd->unicode() != '/' && domainEnd->unicode() != '?') { + matchOffset = domainEnd - start; + continue; + } + } + newLinks.push_back({ domainOffset - 1, static_cast(p - start - domainOffset + 2) }); + offset = matchOffset = p - start; + } + + if (newLinks != _links) { + _links = newLinks; + emit linksChanged(); + } +} + +QStringList FlatTextarea::linksList() const { + QStringList result; + if (!_links.isEmpty()) { + QString text(toPlainText()); + for_const (auto &link, _links) { + result.push_back(text.mid(link.start + 1, link.length - 2)); + } + } + return result; +} + +void FlatTextarea::insertFromMimeData(const QMimeData *source) { + auto mime = tagsMimeType(); + auto text = source->text(); + if (source->hasFormat(mime)) { + auto tagsData = source->data(mime); + _insertedTags = deserializeTagsList(tagsData, text.size()); + _insertedTagsAreFromMime = true; + } else { + _insertedTags.clear(); + } + auto cursor = textCursor(); + _realInsertPosition = qMin(cursor.position(), cursor.anchor()); + _realCharsAdded = text.size(); + QTextEdit::insertFromMimeData(source); + if (!_inDrop) { + emit spacedReturnedPasted(); + _insertedTags.clear(); + _realInsertPosition = -1; + } +} + +void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) { + QTextImageFormat imageFormat; + int32 ew = ESize + st::emojiPadding * cIntRetinaFactor() * 2, eh = _st.font->height * cIntRetinaFactor(); + imageFormat.setWidth(ew / cIntRetinaFactor()); + imageFormat.setHeight(eh / cIntRetinaFactor()); + imageFormat.setName(qsl("emoji://e.") + QString::number(emojiKey(emoji), 16)); + imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline); + if (c.charFormat().isAnchor()) { + imageFormat.setAnchor(true); + imageFormat.setAnchorName(c.charFormat().anchorName()); + imageFormat.setForeground(st::defaultTextStyle.linkFg); + } + static QString objectReplacement(QChar::ObjectReplacementCharacter); + c.insertText(objectReplacement, imageFormat); +} + +QVariant FlatTextarea::loadResource(int type, const QUrl &name) { + QString imageName = name.toDisplayString(); + if (imageName.startsWith(qstr("emoji://e."))) { + if (EmojiPtr emoji = emojiFromUrl(imageName)) { + return QVariant(App::emojiSingle(emoji, _st.font->height)); + } + } + return QVariant(); +} + +void FlatTextarea::checkContentHeight() { + if (heightAutoupdated()) { + emit resized(); + } +} + +namespace { + +// Optimization: with null page size document does not re-layout +// on each insertText / mergeCharFormat. +void prepareFormattingOptimization(QTextDocument *document) { + if (!document->pageSize().isNull()) { + document->setPageSize(QSizeF(0, 0)); + } +} + +void removeTags(const style::color &textFg, QTextDocument *document, int from, int end) { + QTextCursor c(document->docHandle(), 0); + c.setPosition(from); + c.setPosition(end, QTextCursor::KeepAnchor); + + QTextCharFormat format; + format.setAnchor(false); + format.setAnchorName(QString()); + format.setForeground(textFg); + c.mergeCharFormat(format); +} + +// Returns the position of the first inserted tag or "changedEnd" value if none found. +int processInsertedTags(const style::color &textFg, QTextDocument *document, int changedPosition, int changedEnd, const FlatTextarea::TagList &tags, FlatTextarea::TagMimeProcessor *processor) { + int firstTagStart = changedEnd; + int applyNoTagFrom = changedEnd; + for_const (auto &tag, tags) { + int tagFrom = changedPosition + tag.offset; + int tagTo = tagFrom + tag.length; + accumulate_max(tagFrom, changedPosition); + accumulate_min(tagTo, changedEnd); + auto tagId = processor ? processor->tagFromMimeTag(tag.id) : tag.id; + if (tagTo > tagFrom && !tagId.isEmpty()) { + accumulate_min(firstTagStart, tagFrom); + + prepareFormattingOptimization(document); + + if (applyNoTagFrom < tagFrom) { + removeTags(textFg, document, applyNoTagFrom, tagFrom); + } + QTextCursor c(document->docHandle(), 0); + c.setPosition(tagFrom); + c.setPosition(tagTo, QTextCursor::KeepAnchor); + + QTextCharFormat format; + format.setAnchor(true); + format.setAnchorName(tagId + '/' + QString::number(rand_value())); + format.setForeground(st::defaultTextStyle.linkFg); + c.mergeCharFormat(format); + + applyNoTagFrom = tagTo; + } + } + if (applyNoTagFrom < changedEnd) { + removeTags(textFg, document, applyNoTagFrom, changedEnd); + } + + return firstTagStart; +} + +// When inserting a part of text inside a tag we need to have +// a way to know if the insertion replaced the end of the tag +// or it was strictly inside (in the middle) of the tag. +bool wasInsertTillTheEndOfTag(QTextBlock block, QTextBlock::iterator fragmentIt, int insertionEnd) { + auto insertTagName = fragmentIt.fragment().charFormat().anchorName(); + while (true) { + for (; !fragmentIt.atEnd(); ++fragmentIt) { + auto fragment = fragmentIt.fragment(); + bool fragmentOutsideInsertion = (fragment.position() >= insertionEnd); + if (fragmentOutsideInsertion) { + return (fragment.charFormat().anchorName() != insertTagName); + } + int fragmentEnd = fragment.position() + fragment.length(); + bool notFullFragmentInserted = (fragmentEnd > insertionEnd); + if (notFullFragmentInserted) { + return false; + } + } + if (block.isValid()) { + fragmentIt = block.begin(); + block = block.next(); + } else { + break; + } + } + // Insertion goes till the end of the text => not strictly inside a tag. + return true; +} + +struct FormattingAction { + enum class Type { + Invalid, + InsertEmoji, + TildeFont, + RemoveTag, + }; + Type type = Type::Invalid; + EmojiPtr emoji = nullptr; + bool isTilde = false; + int intervalStart = 0; + int intervalEnd = 0; +}; + +} // namespace + +void FlatTextarea::processFormatting(int insertPosition, int insertEnd) { + // Tilde formatting. + auto regularFont = qsl("Open Sans"), semiboldFont = qsl("Open Sans Semibold"); + bool tildeFormatting = !cRetina() && (font().pixelSize() == 13) && (font().family() == regularFont); + bool isTildeFragment = false; + + // First tag handling (the one we inserted text to). + bool startTagFound = false; + bool breakTagOnNotLetter = false; + + auto doc = document(); + + // Apply inserted tags. + auto insertedTagsProcessor = _insertedTagsAreFromMime ? _tagMimeProcessor.get() : nullptr; + int breakTagOnNotLetterTill = processInsertedTags(_st.textColor, doc, insertPosition, insertEnd, + _insertedTags, insertedTagsProcessor); + using ActionType = FormattingAction::Type; + while (true) { + FormattingAction action; + + auto fromBlock = doc->findBlock(insertPosition); + auto tillBlock = doc->findBlock(insertEnd); + if (tillBlock.isValid()) tillBlock = tillBlock.next(); + + for (auto block = fromBlock; block != tillBlock; block = block.next()) { + for (auto fragmentIt = block.begin(); !fragmentIt.atEnd(); ++fragmentIt) { + auto fragment = fragmentIt.fragment(); + t_assert(fragment.isValid()); + + int fragmentPosition = fragment.position(); + if (insertPosition >= fragmentPosition + fragment.length()) { + continue; + } + int changedPositionInFragment = insertPosition - fragmentPosition; // Can be negative. + int changedEndInFragment = insertEnd - fragmentPosition; + if (changedEndInFragment <= 0) { + break; + } + + auto charFormat = fragment.charFormat(); + if (tildeFormatting) { + isTildeFragment = (charFormat.fontFamily() == semiboldFont); + } + + auto fragmentText = fragment.text(); + auto *textStart = fragmentText.constData(); + auto *textEnd = textStart + fragmentText.size(); + + if (!startTagFound) { + startTagFound = true; + auto tagName = charFormat.anchorName(); + if (!tagName.isEmpty()) { + breakTagOnNotLetter = wasInsertTillTheEndOfTag(block, fragmentIt, insertEnd); + } + } + + auto *ch = textStart + qMax(changedPositionInFragment, 0); + for (; ch < textEnd; ++ch) { + int emojiLength = 0; + if (auto emoji = emojiFromText(ch, textEnd, &emojiLength)) { + // Replace emoji if no current action is prepared. + if (action.type == ActionType::Invalid) { + action.type = ActionType::InsertEmoji; + action.emoji = emoji; + action.intervalStart = fragmentPosition + (ch - textStart); + action.intervalEnd = action.intervalStart + emojiLength; + } + break; + } + + if (breakTagOnNotLetter && !ch->isLetter()) { + // Remove tag name till the end if no current action is prepared. + if (action.type != ActionType::Invalid) { + break; + } + breakTagOnNotLetter = false; + if (fragmentPosition + (ch - textStart) < breakTagOnNotLetterTill) { + action.type = ActionType::RemoveTag; + action.intervalStart = fragmentPosition + (ch - textStart); + action.intervalEnd = breakTagOnNotLetterTill; + break; + } + } + if (tildeFormatting) { // Tilde symbol fix in OpenSans. + bool tilde = (ch->unicode() == '~'); + if ((tilde && !isTildeFragment) || (!tilde && isTildeFragment)) { + if (action.type == ActionType::Invalid) { + action.type = ActionType::TildeFont; + action.intervalStart = fragmentPosition + (ch - textStart); + action.intervalEnd = action.intervalStart + 1; + action.isTilde = tilde; + } else { + ++action.intervalEnd; + } + } else if (action.type == ActionType::TildeFont) { + break; + } + } + + if (ch + 1 < textEnd && ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) { + ++ch; + ++fragmentPosition; + } + } + if (action.type != ActionType::Invalid) break; + } + if (action.type != ActionType::Invalid) break; + } + if (action.type != ActionType::Invalid) { + prepareFormattingOptimization(doc); + + QTextCursor c(doc->docHandle(), 0); + c.setPosition(action.intervalStart); + c.setPosition(action.intervalEnd, QTextCursor::KeepAnchor); + if (action.type == ActionType::InsertEmoji) { + insertEmoji(action.emoji, c); + insertPosition = action.intervalStart + 1; + } else if (action.type == ActionType::RemoveTag) { + QTextCharFormat format; + format.setAnchor(false); + format.setAnchorName(QString()); + format.setForeground(_st.textColor); + c.mergeCharFormat(format); + } else if (action.type == ActionType::TildeFont) { + QTextCharFormat format; + format.setFontFamily(action.isTilde ? semiboldFont : regularFont); + c.mergeCharFormat(format); + insertPosition = action.intervalEnd; + } + } else { + break; + } + } +} + +void FlatTextarea::onDocumentContentsChange(int position, int charsRemoved, int charsAdded) { + if (_correcting) return; + + int insertPosition = (_realInsertPosition >= 0) ? _realInsertPosition : position; + int insertLength = (_realInsertPosition >= 0) ? _realCharsAdded : charsAdded; + + int removePosition = position; + int removeLength = charsRemoved; + + QTextCursor(document()->docHandle(), 0).joinPreviousEditBlock(); + + _correcting = true; + if (_maxLength >= 0) { + QTextCursor c(document()->docHandle(), 0); + c.movePosition(QTextCursor::End); + int32 fullSize = c.position(), toRemove = fullSize - _maxLength; + if (toRemove > 0) { + if (toRemove > insertLength) { + if (insertLength) { + c.setPosition(insertPosition); + c.setPosition((insertPosition + insertLength), QTextCursor::KeepAnchor); + c.removeSelectedText(); + } + c.setPosition(fullSize - (toRemove - insertLength)); + c.setPosition(fullSize, QTextCursor::KeepAnchor); + c.removeSelectedText(); + } else { + c.setPosition(insertPosition + (insertLength - toRemove)); + c.setPosition(insertPosition + insertLength, QTextCursor::KeepAnchor); + c.removeSelectedText(); + } + } + } + _correcting = false; + + if (insertPosition == removePosition) { + if (!_links.isEmpty()) { + bool changed = false; + for (auto i = _links.begin(); i != _links.end();) { + if (i->start + i->length <= insertPosition) { + ++i; + } else if (i->start >= removePosition + removeLength) { + i->start += insertLength - removeLength; + ++i; + } else { + i = _links.erase(i); + changed = true; + } + } + if (changed) emit linksChanged(); + } + } else { + parseLinks(); + } + + if (document()->availableRedoSteps() > 0) { + QTextCursor(document()->docHandle(), 0).endEditBlock(); + return; + } + + if (insertLength <= 0) { + QTextCursor(document()->docHandle(), 0).endEditBlock(); + return; + } + + _correcting = true; + auto pageSize = document()->pageSize(); + processFormatting(insertPosition, insertPosition + insertLength); + if (document()->pageSize() != pageSize) { + document()->setPageSize(pageSize); + } + _correcting = false; + + QTextCursor(document()->docHandle(), 0).endEditBlock(); +} + +void FlatTextarea::onDocumentContentsChanged() { + if (_correcting) return; + + auto tagsChanged = false; + auto curText = getTextPart(0, -1, &_lastTextWithTags.tags, &tagsChanged); + + _correcting = true; + correctValue(_lastTextWithTags.text, curText, _lastTextWithTags.tags); + _correcting = false; + + bool textOrTagsChanged = tagsChanged || (_lastTextWithTags.text != curText); + if (textOrTagsChanged) { + _lastTextWithTags.text = curText; + emit changed(); + checkContentHeight(); + } + updatePlaceholder(); + if (App::wnd()) App::wnd()->updateGlobalMenu(); +} + +void FlatTextarea::onUndoAvailable(bool avail) { + _undoAvailable = avail; + if (App::wnd()) App::wnd()->updateGlobalMenu(); +} + +void FlatTextarea::onRedoAvailable(bool avail) { + _redoAvailable = avail; + if (App::wnd()) App::wnd()->updateGlobalMenu(); +} + +void FlatTextarea::step_appearance(float64 ms, bool timer) { + float dt = ms / _st.phDuration; + if (dt >= 1) { + _a_appearance.stop(); + a_phLeft.finish(); + a_phAlpha.finish(); + a_phColorFocused.finish(); + a_phLeft = anim::ivalue(a_phLeft.current()); + a_phAlpha = anim::fvalue(a_phAlpha.current()); + a_phColorFocused = anim::fvalue(a_phColorFocused.current()); + } else { + a_phLeft.update(dt, anim::linear); + a_phAlpha.update(dt, anim::linear); + a_phColorFocused.update(dt, anim::linear); + } + if (timer) update(); +} + +void FlatTextarea::setPlaceholder(const QString &ph, int32 afterSymbols) { + _ph = ph; + if (_phAfter != afterSymbols) { + _phAfter = afterSymbols; + updatePlaceholder(); + } + int skipWidth = placeholderSkipWidth(); + _phelided = _st.font->elided(_ph, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1 - skipWidth); + if (_phVisible) update(); +} + +void FlatTextarea::updatePlaceholder() { + bool vis = (getTextWithTags().text.size() <= _phAfter); + if (vis == _phVisible) return; + + a_phLeft.start(vis ? 0 : _st.phShift); + a_phAlpha.start(vis ? 1 : 0); + _a_appearance.start(); + + _phVisible = vis; +} + +QMimeData *FlatTextarea::createMimeDataFromSelection() const { + QMimeData *result = new QMimeData(); + QTextCursor c(textCursor()); + int32 start = c.selectionStart(), end = c.selectionEnd(); + if (end > start) { + TagList tags; + result->setText(getTextPart(start, end, &tags)); + if (!tags.isEmpty()) { + if (_tagMimeProcessor) { + for (auto &tag : tags) { + tag.id = _tagMimeProcessor->mimeTagFromTag(tag.id); + } + } + result->setData(tagsMimeType(), serializeTagsList(tags)); + } + } + return result; +} + +void FlatTextarea::setSubmitSettings(SubmitSettings settings) { + _submitSettings = settings; +} + +void FlatTextarea::keyPressEvent(QKeyEvent *e) { + bool shift = e->modifiers().testFlag(Qt::ShiftModifier); + bool macmeta = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier); + bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier); + bool enterSubmit = (ctrl && shift); + if (ctrl && _submitSettings != SubmitSettings::None && _submitSettings != SubmitSettings::Enter) { + enterSubmit = true; + } + if (!ctrl && !shift && _submitSettings != SubmitSettings::None && _submitSettings != SubmitSettings::CtrlEnter) { + enterSubmit = true; + } + bool enter = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return); + + if (macmeta && e->key() == Qt::Key_Backspace) { + QTextCursor tc(textCursor()), start(tc); + start.movePosition(QTextCursor::StartOfLine); + tc.setPosition(start.position(), QTextCursor::KeepAnchor); + tc.removeSelectedText(); + } else if (enter && enterSubmit) { + emit submitted(ctrl && shift); + } else if (e->key() == Qt::Key_Escape) { + emit cancelled(); + } else if (e->key() == Qt::Key_Tab || (ctrl && e->key() == Qt::Key_Backtab)) { + if (ctrl) { + e->ignore(); + } else { + emit tabbed(); + } + } else if (e->key() == Qt::Key_Search || e == QKeySequence::Find) { + e->ignore(); +#ifdef Q_OS_MAC + } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { + auto cursor = textCursor(); + int start = cursor.selectionStart(), end = cursor.selectionEnd(); + if (end > start) { + TagList tags; + QApplication::clipboard()->setText(getTextPart(start, end, &tags), QClipboard::FindBuffer); + } +#endif // Q_OS_MAC + } else { + QTextCursor tc(textCursor()); + if (enter && ctrl) { + e->setModifiers(e->modifiers() & ~Qt::ControlModifier); + } + bool spaceOrReturn = false; + QString t(e->text()); + if (!t.isEmpty() && t.size() < 3) { + if (t.at(0) == '\n' || t.at(0) == '\r' || t.at(0).isSpace() || t.at(0) == QChar::LineSeparator) { + spaceOrReturn = true; + } + } + QTextEdit::keyPressEvent(e); + if (tc == textCursor()) { + bool check = false; + if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_Up) { + tc.movePosition(QTextCursor::Start, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); + check = true; + } else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) { + tc.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); + check = true; + } + if (check) { + if (tc == textCursor()) { + e->ignore(); + } else { + setTextCursor(tc); + } + } + } + if (spaceOrReturn) emit spacedReturnedPasted(); + } +} + +void FlatTextarea::resizeEvent(QResizeEvent *e) { + _phelided = _st.font->elided(_ph, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1); + QTextEdit::resizeEvent(e); + checkContentHeight(); +} + +void FlatTextarea::mousePressEvent(QMouseEvent *e) { + QTextEdit::mousePressEvent(e); +} + +void FlatTextarea::dropEvent(QDropEvent *e) { + _inDrop = true; + QTextEdit::dropEvent(e); + _inDrop = false; + _insertedTags.clear(); + _realInsertPosition = -1; + + emit spacedReturnedPasted(); +} + +void FlatTextarea::contextMenuEvent(QContextMenuEvent *e) { + if (auto menu = createStandardContextMenu()) { + (new Ui::PopupMenu(menu))->popup(e->globalPos()); + } +} + +FlatInput::FlatInput(QWidget *parent, const style::FlatInput &st, const QString &pholder, const QString &v) : QLineEdit(v, parent) , _oldtext(v) , _fullph(pholder) , _fastph(false) @@ -393,7 +1796,7 @@ void FlatInput::notaBene() { _a_appearance.start(); } -CountryCodeInput::CountryCodeInput(QWidget *parent, const style::flatInput &st) : FlatInput(parent, st) +CountryCodeInput::CountryCodeInput(QWidget *parent, const style::FlatInput &st) : FlatInput(parent, st) , _nosignal(false) { } @@ -456,7 +1859,7 @@ void CountryCodeInput::correctValue(const QString &was, QString &now) { } } -PhonePartInput::PhonePartInput(QWidget *parent, const style::flatInput &st) : FlatInput(parent, st, lang(lng_phone_ph)) { +PhonePartInput::PhonePartInput(QWidget *parent, const style::FlatInput &st) : FlatInput(parent, st, lang(lng_phone_ph)) { } void PhonePartInput::paintEvent(QPaintEvent *e) { @@ -2455,7 +3858,7 @@ void MaskedInputField::onCursorPositionChanged(int oldPosition, int position) { _oldcursor = position; } -PasswordField::PasswordField(QWidget *parent, const style::InputField &st, const QString &ph, const QString &val) : MaskedInputField(parent, st, ph, val) { +PasswordInput::PasswordInput(QWidget *parent, const style::InputField &st, const QString &ph, const QString &val) : MaskedInputField(parent, st, ph, val) { setEchoMode(QLineEdit::Password); } @@ -2681,3 +4084,5 @@ void PhoneInput::correctValue(const QString &was, int32 wasCursor, QString &now, setCursorPosition(newPos); } } + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/flatinput.h b/Telegram/SourceFiles/ui/widgets/input_fields.h similarity index 68% rename from Telegram/SourceFiles/ui/flatinput.h rename to Telegram/SourceFiles/ui/widgets/input_fields.h index 60d8a2e4f..908e70dbc 100644 --- a/Telegram/SourceFiles/ui/flatinput.h +++ b/Telegram/SourceFiles/ui/widgets/input_fields.h @@ -20,14 +20,232 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "animation.h" +#include "styles/style_widgets.h" + +class UserData; + +namespace Ui { + +static UserData * const LookingUpInlineBot = SharedMemoryLocation(); + +class FlatTextarea : public QTextEdit { + Q_OBJECT + T_WIDGET + +public: + using TagList = TextWithTags::Tags; + + static QByteArray serializeTagsList(const TagList &tags); + static TagList deserializeTagsList(QByteArray data, int textLength); + static QString tagsMimeType(); + + FlatTextarea(QWidget *parent, const style::FlatTextarea &st, const QString &ph = QString(), const QString &val = QString(), const TagList &tags = TagList()); + + void setMaxLength(int32 maxLength); + void setMinHeight(int32 minHeight); + void setMaxHeight(int32 maxHeight); + + void setPlaceholder(const QString &ph, int32 afterSymbols = 0); + void updatePlaceholder(); + void finishPlaceholder(); + + QRect getTextRect() const; + int32 fakeMargin() const; + + void step_appearance(float64 ms, bool timer); + + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + + EmojiPtr getSingleEmoji() const; + QString getMentionHashtagBotCommandPart(bool &start) const; + + // Get the current inline bot and request string for it. + // The *outInlineBot can be filled by LookingUpInlineBot shared ptr. + // In that case the caller should lookup the bot by *outInlineBotUsername. + QString getInlineBotQuery(UserData **outInlineBot, QString *outInlineBotUsername) const; + + void removeSingleEmoji(); + bool hasText() const; + + bool isUndoAvailable() const; + bool isRedoAvailable() const; + + void parseLinks(); + QStringList linksList() const; + + void insertFromMimeData(const QMimeData *source) override; + + QMimeData *createMimeDataFromSelection() const override; + + enum class SubmitSettings { + None, + Enter, + CtrlEnter, + Both, + }; + void setSubmitSettings(SubmitSettings settings); + + const TextWithTags &getTextWithTags() const { + return _lastTextWithTags; + } + TextWithTags getTextWithTagsPart(int start, int end = -1); + void insertTag(const QString &text, QString tagId = QString()); + + bool isEmpty() const { + return _lastTextWithTags.text.isEmpty(); + } + + enum UndoHistoryAction { + AddToUndoHistory, + MergeWithUndoHistory, + ClearUndoHistory + }; + void setTextWithTags(const TextWithTags &textWithTags, UndoHistoryAction undoHistoryAction = AddToUndoHistory); + + // If you need to make some preparations of tags before putting them to QMimeData + // (and then to clipboard or to drag-n-drop object), here is a strategy for that. + class TagMimeProcessor { + public: + virtual QString mimeTagFromTag(const QString &tagId) = 0; + virtual QString tagFromMimeTag(const QString &mimeTag) = 0; + virtual ~TagMimeProcessor() { + } + }; + void setTagMimeProcessor(std_::unique_ptr &&processor); + + public slots: + void onTouchTimer(); + + void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); + void onDocumentContentsChanged(); + + void onUndoAvailable(bool avail); + void onRedoAvailable(bool avail); + +signals: + void resized(); + void changed(); + void submitted(bool ctrlShiftEnter); + void cancelled(); + void tabbed(); + void spacedReturnedPasted(); + void linksChanged(); + +protected: + void enterEventHook(QEvent *e) { + return QTextEdit::enterEvent(e); + } + void leaveEventHook(QEvent *e) { + return QTextEdit::leaveEvent(e); + } + + bool viewportEvent(QEvent *e) override; + void touchEvent(QTouchEvent *e); + void paintEvent(QPaintEvent *e) override; + void focusInEvent(QFocusEvent *e) override; + void focusOutEvent(QFocusEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void dropEvent(QDropEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; + + virtual void correctValue(const QString &was, QString &now, TagList &nowTags) { + } + + void insertEmoji(EmojiPtr emoji, QTextCursor c); + + QVariant loadResource(int type, const QUrl &name) override; + + void checkContentHeight(); + +private: + // "start" and "end" are in coordinates of text where emoji are replaced + // by ObjectReplacementCharacter. If "end" = -1 means get text till the end. + QString getTextPart(int start, int end, TagList *outTagsList, bool *outTagsChanged = nullptr) const; + + void getSingleEmojiFragment(QString &text, QTextFragment &fragment) const; + + // After any characters added we must postprocess them. This includes: + // 1. Replacing font family to semibold for ~ characters, if we used Open Sans 13px. + // 2. Replacing font family from semibold for all non-~ characters, if we used ... + // 3. Replacing emoji code sequences by ObjectReplacementCharacters with emoji pics. + // 4. Interrupting tags in which the text was inserted by any char except a letter. + // 5. Applying tags from "_insertedTags" in case we pasted text with tags, not just text. + // Rule 4 applies only if we inserted chars not in the middle of a tag (but at the end). + void processFormatting(int changedPosition, int changedEnd); + + bool heightAutoupdated(); + + int placeholderSkipWidth() const; + + int _minHeight = -1; // < 0 - no autosize + int _maxHeight = -1; + int _maxLength = -1; + SubmitSettings _submitSettings = SubmitSettings::Enter; + + QString _ph, _phelided; + int _phAfter = 0; + bool _phVisible; + anim::ivalue a_phLeft; + anim::fvalue a_phAlpha; + anim::fvalue a_phColorFocused; + Animation _a_appearance; + + TextWithTags _lastTextWithTags; + + // Tags list which we should apply while setText() call or insert from mime data. + TagList _insertedTags; + bool _insertedTagsAreFromMime; + + // Override insert position and charsAdded from complex text editing + // (like drag-n-drop in the same text edit field). + int _realInsertPosition = -1; + int _realCharsAdded = 0; + + std_::unique_ptr _tagMimeProcessor; + + const style::FlatTextarea &_st; + + bool _undoAvailable = false; + bool _redoAvailable = false; + bool _inDrop = false; + bool _inHeightCheck = false; + + int _fakeMargin = 0; + + QTimer _touchTimer; + bool _touchPress = false; + bool _touchRightButton = false; + bool _touchMove = false; + QPoint _touchStart; + + bool _correcting = false; + + struct LinkRange { + int start; + int length; + }; + friend bool operator==(const LinkRange &a, const LinkRange &b); + friend bool operator!=(const LinkRange &a, const LinkRange &b); + using LinkRanges = QVector; + LinkRanges _links; +}; + +inline bool operator==(const FlatTextarea::LinkRange &a, const FlatTextarea::LinkRange &b) { + return (a.start == b.start) && (a.length == b.length); +} +inline bool operator!=(const FlatTextarea::LinkRange &a, const FlatTextarea::LinkRange &b) { + return !(a == b); +} class FlatInput : public QLineEdit { Q_OBJECT T_WIDGET public: - FlatInput(QWidget *parent, const style::flatInput &st, const QString &ph = QString(), const QString &val = QString()); + FlatInput(QWidget *parent, const style::FlatInput &st, const QString &ph = QString(), const QString &val = QString()); void notaBene(); @@ -49,7 +267,7 @@ public: return _oldtext; } -public slots: + public slots: void onTextChange(const QString &text); void onTextEdited(); @@ -105,7 +323,7 @@ private: Animation _a_appearance; int _notingBene; - const style::flatInput &_st; + const style::FlatInput &_st; QTimer _touchTimer; bool _touchPress, _touchRightButton, _touchMove; @@ -116,9 +334,9 @@ class CountryCodeInput : public FlatInput { Q_OBJECT public: - CountryCodeInput(QWidget *parent, const style::flatInput &st); + CountryCodeInput(QWidget *parent, const style::FlatInput &st); -public slots: + public slots: void startErasing(QKeyEvent *e); void codeSelected(const QString &code); @@ -138,9 +356,9 @@ class PhonePartInput : public FlatInput { Q_OBJECT public: - PhonePartInput(QWidget *parent, const style::flatInput &st); + PhonePartInput(QWidget *parent, const style::FlatInput &st); -public slots: + public slots: void addedToNumber(const QString &added); void onChooseCode(const QString &code); @@ -221,7 +439,7 @@ public: _inner.clearFocus(); } -public slots: + public slots: void onTouchTimer(); void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); @@ -392,7 +610,7 @@ public: _inner.setTextCursor(c); } -public slots: + public slots: void onTouchTimer(); void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); @@ -497,7 +715,7 @@ private: class MaskedInputField : public QLineEdit { Q_OBJECT - T_WIDGET + T_WIDGET public: MaskedInputField(QWidget *parent, const style::InputField &st, const QString &placeholder = QString(), const QString &val = QString()); @@ -539,7 +757,7 @@ public: updatePlaceholder(); } -public slots: + public slots: void onTextChange(const QString &text); void onCursorPositionChanged(int oldPosition, int position); @@ -610,9 +828,9 @@ private: QPoint _touchStart; }; -class PasswordField : public MaskedInputField { +class PasswordInput : public MaskedInputField { public: - PasswordField(QWidget *parent, const style::InputField &st, const QString &ph = QString(), const QString &val = QString()); + PasswordInput(QWidget *parent, const style::InputField &st, const QString &ph = QString(), const QString &val = QString()); }; @@ -655,3 +873,5 @@ private: QVector _pattern; }; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.cpp b/Telegram/SourceFiles/ui/widgets/multi_select.cpp index dfcbb1b4e..6ad1eed9e 100644 --- a/Telegram/SourceFiles/ui/widgets/multi_select.cpp +++ b/Telegram/SourceFiles/ui/widgets/multi_select.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_widgets.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "lang.h" namespace Ui { diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.h b/Telegram/SourceFiles/ui/widgets/multi_select.h index 9ac568ee6..753a7b8c8 100644 --- a/Telegram/SourceFiles/ui/widgets/multi_select.h +++ b/Telegram/SourceFiles/ui/widgets/multi_select.h @@ -22,10 +22,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_widgets.h" -class InputField; - namespace Ui { +class InputField; class IconButton; class MultiSelect : public TWidget { @@ -153,7 +152,7 @@ private: int _fieldLeft = 0; int _fieldTop = 0; int _fieldWidth = 0; - ChildWidget _field; + ChildWidget _field; ChildWidget _cancel; int _newHeight = 0; diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index f3ba2c336..edadae64e 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -35,6 +35,12 @@ LinkButton { overFont: font; } +RippleAnimation { + color: color; + showDuration: int; + hideDuration: int; +} + FlatButton { color: color; overColor: color; @@ -53,6 +59,188 @@ FlatButton { overFont: font; duration: int; cursor: cursor; + + ripple: RippleAnimation; +} + +RoundButton { + textFg: color; + textFgOver: color; + textBg: color; // rect of textBg with rounded rect of textBgOver upon it + textBgOver: color; + + secondaryTextFg: color; + secondaryTextFgOver: color; + secondarySkip: pixels; + + width: pixels; + height: pixels; + padding: margins; + + textTop: pixels; + downTextTop: pixels; + + icon: icon; + + font: font; + + ripple: RippleAnimation; +} + +Checkbox { + textFg: color; + textBg: color; + + checkBg: color; + checkFg: color; + checkFgOver: color; + checkFgActive: color; + + width: pixels; + height: pixels; + + textPosition: point; + diameter: pixels; + thickness: pixels; + checkIcon: icon; + + font: font; + duration: int; +} + +Radiobutton { + textFg: color; + textBg: color; + + checkBg: color; + checkFg: color; + checkFgOver: color; + checkFgActive: color; + + width: pixels; + height: pixels; + + textPosition: point; + diameter: pixels; + thickness: pixels; + checkSkip: pixels; + + font: font; + duration: int; +} + +FlatTextarea { + textColor: color; + bgColor: color; + width: pixels; + textMrg: margins; + align: align; + font: font; + cursor: cursor; + + phColor: color; + phFocusColor: color; + phPos: point; + phAlign: align; + phShift: pixels; + phDuration: int; +} + +FlatInput { + textColor: color; + bgColor: color; + bgActive: color; + width: pixels; + height: pixels; + textMrg: margins; + align: align; + font: font; + cursor: cursor; + + icon: icon; + + borderWidth: pixels; + borderColor: color; + borderActive: color; + borderError: color; + + phColor: color; + phFocusColor: color; + phPos: point; + phAlign: align; + phShift: pixels; + phDuration: int; +} + +InputArea { + textBg: color; + textFg: color; + textMargins: margins; + + placeholderFg: color; + placeholderFgActive: color; + placeholderMargins: margins; + placeholderAlign: align; + placeholderShift: pixels; + + duration: int; + + borderFg: color; + borderFgActive: color; + borderFgError: color; + + border: pixels; + borderActive: pixels; + borderError: pixels; + + font: font; + + width: pixels; + heightMin: pixels; + heightMax: pixels; +} + +InputField { + textBg: color; + textFg: color; + textMargins: margins; + textAlign: align; + + placeholderFg: color; + placeholderFgActive: color; + placeholderMargins: margins; + placeholderAlign: align; + placeholderShift: pixels; + + duration: int; + + borderFg: color; + borderFgActive: color; + borderFgError: color; + + border: pixels; + borderActive: pixels; + borderError: pixels; + + font: font; + + width: pixels; + height: pixels; +} + +OutlineButton { + outlineWidth: pixels; + outlineFg: color; + outlineFgOver: color; + + textBg: color; + textBgOver: color; + + textFg: color; + textFgOver: color; + + font: font; + padding: margins; } IconButton { @@ -66,6 +254,10 @@ IconButton { iconPositionDown: point; duration: int; + + rippleAreaPosition: point; + rippleAreaSize: pixels; + ripple: RippleAnimation; } Shadow { @@ -232,6 +424,194 @@ defaultLinkButton: LinkButton { overFont: linkOverFont; } +defaultRippleAnimation: RippleAnimation { + color: windowOverBg; + showDuration: 200; + hideDuration: 200; +} + +emptyRippleAnimation: RippleAnimation { +} + +defaultActiveButton: RoundButton { + textFg: activeButtonFg; + textFgOver: activeButtonFgOver; + secondaryTextFg: activeButtonSecondaryFg; + secondaryTextFgOver: activeButtonSecondaryFgOver; + textBg: activeButtonBg; + textBgOver: activeButtonBgOver; + + secondarySkip: 7px; + + width: -34px; + height: 34px; + padding: margins(0px, 0px, 0px, 0px); + + textTop: 8px; + downTextTop: 8px; + + font: semiboldFont; + + ripple: RippleAnimation(defaultRippleAnimation) { + color: activeButtonBgRipple; + } +} + +defaultLightButton: RoundButton(defaultActiveButton) { + textFg: lightButtonFg; + textFgOver: lightButtonFgOver; + textBg: lightButtonBg; + textBgOver: lightButtonBgOver; + + ripple: RippleAnimation(defaultRippleAnimation) { + color: lightButtonBgRipple; + } +} + +defaultInputFont: font(17px); +defaultFlatInput: FlatInput { + textColor: #000000; + bgColor: #f2f2f2; + bgActive: #ffffff; + width: 210px; + height: 40px; + align: align(left); + textMrg: margins(5px, 5px, 5px, 5px); + font: defaultInputFont; + cursor: cursor(text); + + borderWidth: 2px; + borderColor: #f2f2f2; + borderActive: #54c3f3; + borderError: #ed8080; + + phColor: #808080; + phFocusColor: #aaaaaa; + phAlign: align(left); + phPos: point(2px, 0px); + phShift: 50px; + phDuration: 100; +} + +defaultLeftOutlineButton: OutlineButton { + outlineWidth: 3px; + outlineFg: windowBg; + outlineFgOver: windowActiveBg; + + textBg: windowBg; + textBgOver: #f2f7fa; + + textFg: windowActiveTextFg; + textFgOver: windowActiveTextFg; + + font: normalFont; + padding: margins(11px, 5px, 11px, 5px); +} +attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) { + outlineFgOver: #e43f3f; + + textBgOver: #faf2f2; + + textFg: #d15948; + textFgOver: #d15948; +} + +defaultInputArea: InputArea { + textBg: windowBg; + textFg: windowTextFg; + textMargins: margins(5px, 6px, 5px, 4px); + + placeholderFg: #999999; + placeholderFgActive: #aaaaaa; + placeholderMargins: margins(2px, 0px, 2px, 0px); + placeholderAlign: align(topleft); + placeholderShift: 50px; + duration: 120; + + borderFg: #e0e0e0; + borderFgActive: #62c0f7; + borderFgError: #e48383; + + border: 1px; + borderActive: 2px; + borderError: 2px; + + font: boxTextFont; + + heightMin: 32px; + heightMax: 128px; +} + +defaultInputField: InputField { + textBg: windowBg; + textFg: windowTextFg; + textMargins: margins(0px, 6px, 0px, 4px); + textAlign: align(topleft); + + placeholderFg: #999999; + placeholderFgActive: #aaaaaa; + placeholderMargins: margins(2px, 0px, 2px, 0px); + placeholderAlign: align(topleft); + placeholderShift: 50px; + duration: 120; + + borderFg: #e0e0e0; + borderFgActive: #62c0f7; + borderFgError: #e48383; + + border: 1px; + borderActive: 2px; + borderError: 2px; + + font: boxTextFont; + + height: 32px; +} + +defaultCheckboxIcon: icon {{ "default_checkbox_check", windowActiveFg, point(4px, 7px) }}; + +defaultCheckbox: Checkbox { + textFg: windowTextFg; + textBg: windowBg; + + checkBg: #ffffff; + checkFg: #b3b3b3; + checkFgOver: #b3b3b3; + checkFgActive: windowActiveBg; + + width: -44px; + height: 22px; + + textPosition: point(32px, 2px); + diameter: 22px; + thickness: 2px; + checkIcon: defaultCheckboxIcon; + + font: normalFont; + duration: 120; +} + +defaultRadiobutton: Radiobutton { + textFg: windowTextFg; + textBg: windowBg; + + checkBg: #ffffff; + checkFg: #b3b3b3; + checkFgOver: #bfbfbf; + checkFgActive: #4eb3ee; + + width: -46px; + height: 22px; + + textPosition: point(34px, 0px); + diameter: 22px; + thickness: 2px; + checkSkip: 65px; // * 0.1 + + font: boxTextFont; + duration: 120; +} + defaultIconButton: IconButton { iconPosition: point(-1px, -1px); iconPositionDown: point(-1px, -1px); diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index 3dec77fd9..11ec3e30e 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -58,6 +58,7 @@ void MainWindow::init() { } initSize(); + updateUnreadCounter(); } HitTestResult MainWindow::hitTest(const QPoint &p) const { diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 40218c278..67307b4f9 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -26,8 +26,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "lang.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "dialogs/dialogs_layout.h" #include "styles/style_dialogs.h" +#include "styles/style_boxes.h" #include "styles/style_window.h" namespace Window { @@ -225,7 +227,7 @@ void Manager::moveWidgets() { } if (count > 1 || !_queuedNotifications.isEmpty()) { - auto deltaY = st::notifyHideAll.height + st::notifyDeltaY; + auto deltaY = st::notifyHideAllHeight + st::notifyDeltaY; if (!_hideAll) { _hideAll = new HideAllButton(notificationStartPosition(), lastShiftCurrent, notificationShiftDirection()); } @@ -735,17 +737,17 @@ void Notification::showReplyField() { } stopHiding(); - _background = new Background(this); + _background.create(this); _background->setGeometry(0, st::notifyMinHeight, width(), st::notifySendReply.height + st::notifyBorderWidth); _background->show(); - _replyArea = new InputArea(this, st::notifyReplyArea, lang(lng_message_ph), QString()); + _replyArea.create(this, st::notifyReplyArea, lang(lng_message_ph), QString()); _replyArea->resize(width() - st::notifySendReply.width - 2 * st::notifyBorderWidth, st::notifySendReply.height); _replyArea->moveToLeft(st::notifyBorderWidth, st::notifyMinHeight); _replyArea->show(); _replyArea->setFocus(); _replyArea->setMaxLength(MaxMessageSize); - _replyArea->setCtrlEnterSubmit(CtrlEnterSubmitBoth); + _replyArea->setCtrlEnterSubmit(Ui::CtrlEnterSubmitBoth); // Catch mouse press event to activate the window. Sandbox::installEventFilter(this); @@ -860,8 +862,8 @@ Notification::~Notification() { HideAllButton::HideAllButton(QPoint startPosition, int shift, Direction shiftDirection) : Widget(startPosition, shift, shiftDirection) { setCursor(style::cur_pointer); - auto position = computePosition(st::notifyHideAll.height); - updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyHideAll.height); + auto position = computePosition(st::notifyHideAllHeight); + updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyHideAllHeight); hide(); createWinId(); @@ -913,7 +915,7 @@ void HideAllButton::paintEvent(QPaintEvent *e) { Painter p(this); p.setClipRect(e->rect()); - p.fillRect(rect(), _mouseOver ? st::notifyHideAll.textBgOver : st::notifyHideAll.textBg); + p.fillRect(rect(), _mouseOver ? st::lightButtonBgOver : st::lightButtonBg); p.fillRect(0, 0, width(), st::notifyBorderWidth, st::notifyBorder); p.fillRect(0, height() - st::notifyBorderWidth, width(), st::notifyBorderWidth, st::notifyBorder); p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, height() - 2 * st::notifyBorderWidth, st::notifyBorder); diff --git a/Telegram/SourceFiles/window/notifications_manager_default.h b/Telegram/SourceFiles/window/notifications_manager_default.h index 67b5956e6..b6384ac62 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.h +++ b/Telegram/SourceFiles/window/notifications_manager_default.h @@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Ui { class IconButton; class RoundButton; +class InputArea; } // namespace Ui namespace Window { @@ -248,7 +249,7 @@ private: ChildWidget _close; ChildWidget _reply; ChildWidget _background = { nullptr }; - ChildWidget _replyArea = { nullptr }; + ChildWidget _replyArea = { nullptr }; ChildWidget _replySend = { nullptr }; bool _waitingForInput = true; diff --git a/Telegram/SourceFiles/window/slide_animation.cpp b/Telegram/SourceFiles/window/slide_animation.cpp index f8e27c174..bca18bfe7 100644 --- a/Telegram/SourceFiles/window/slide_animation.cpp +++ b/Telegram/SourceFiles/window/slide_animation.cpp @@ -21,6 +21,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "window/slide_animation.h" +#include "styles/style_window.h" + namespace Window { void SlideAnimation::paintContents(Painter &p, const QRect &update) const { diff --git a/Telegram/SourceFiles/window/top_bar_widget.cpp b/Telegram/SourceFiles/window/top_bar_widget.cpp index 76978560f..92133fe09 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.cpp +++ b/Telegram/SourceFiles/window/top_bar_widget.cpp @@ -21,7 +21,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "window/top_bar_widget.h" -#include "styles/style_history.h" #include "styles/style_window.h" #include "boxes/addcontactbox.h" #include "boxes/confirmbox.h" @@ -41,7 +40,7 @@ TopBarWidget::TopBarWidget(MainWidget *w) : TWidget(w) , _clearSelection(this, lang(lng_selected_clear), st::topBarClearButton) , _forward(this, lang(lng_selected_forward), st::defaultActiveButton) , _delete(this, lang(lng_selected_delete), st::defaultActiveButton) -, _info(this, nullptr, st::infoButton) +, _info(this, nullptr, st::topBarInfoButton) , _mediaType(this, lang(lng_media_type), st::topBarButton) , _search(this, st::topBarSearch) , _menuToggle(this, st::topBarMenuToggle) { @@ -93,15 +92,23 @@ void TopBarWidget::onSearch() { void TopBarWidget::showMenu() { if (auto main = App::main()) { if (auto peer = main->peer()) { - _menu.create(App::main()); - App::main()->fillPeerMenu(peer, [this](const QString &text, base::lambda_unique callback) { - return _menu->addAction(text, std_::move(callback)); - }); - _menu->setHiddenCallback([this] { - _menu.destroyDelayed(); - }); - _menu->moveToRight(0, 0); - _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight); + if (auto menu = _menu.ptr()) { + _menu = nullptr; + _menuToggle->removeEventFilter(menu); + menu->setHiddenCallback([menu] { menu->deleteLater(); }); + menu->hideAnimated(Ui::DropdownMenu::HideOption::IgnoreShow); + } else { + _menu.create(App::main()); + _menuToggle->installEventFilter(_menu); + App::main()->fillPeerMenu(peer, [this](const QString &text, base::lambda_unique callback) { + return _menu->addAction(text, std_::move(callback)); + }); + _menu->setHiddenCallback([this] { + _menu.destroyDelayed(); + }); + _menu->moveToRight(st::topBarMenuPosition.x(), st::topBarMenuPosition.y()); + _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight); + } } } } diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index a6db6a26c..404a7945e 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -60,8 +60,7 @@ notifyDeltaX: 6px; notifyDeltaY: 7px; notifyActionsDuration: 200; -notifyHideAll: RoundButton(defaultBoxButton) { -} +notifyHideAllHeight: 36px; notifyReplyArea: InputArea(defaultInputArea) { font: normalFont; @@ -156,6 +155,78 @@ titleButtonClose: IconButton(titleButtonMinimize) { }; } +// Legacy top bar. +topBarHeight: 54px; +topBarMenuPosition: point(-2px, 37px); +topBarDuration: 200; +topBarBackward: icon {{ "title_back", #a3a3a3 }}; +topBarForwardAlpha: 0.6; +topBarBack: icon {{ "title_back", #259fd8 }}; +topBarBackAlpha: 0.8; +topBarBackColor: #005faf; +topBarBackFont: font(16px); +topBarArrowPadding: margins(39px, 8px, 17px, 8px); +topBarMinPadding: 5px; +topBarButton: RoundButton { + textFg: btnYesColor; + textFgOver: btnYesColor; + secondaryTextFg: btnYesColor; + secondaryTextFgOver: btnYesColor; + textBg: windowBg; + textBgOver: #edf4f7; + + width: -22px; + height: 28px; + padding: margins(0px, 14px, 12px, 12px); + + textTop: 6px; + downTextTop: 6px; + + font: font(fsize); + + ripple: RippleAnimation(defaultRippleAnimation) { + color: lightButtonBgRipple; + } +} +topBarClearButton: RoundButton(defaultLightButton) { + width: -18px; +} +topBarSearch: IconButton { + width: 44px; + height: topBarHeight; + + icon: icon {{ "title_search", menuIconFg }}; + iconOver: icon {{ "title_search", menuIconFgOver }}; + + iconPosition: point(15px, 18px); + iconPositionDown: point(15px, 18px); + + rippleAreaPosition: point(8px, 11px); + rippleAreaSize: 32px; + ripple: defaultRippleAnimation; +} +topBarMenuToggle: IconButton(topBarSearch) { + icon: icon {{ "title_menu_dots", menuIconFg }}; + iconOver: icon {{ "title_menu_dots", menuIconFgOver }}; + + iconPosition: point(15px, 17px); + iconPositionDown: point(15px, 17px); + + rippleAreaPosition: point(3px, 11px); + rippleAreaSize: 32px; + ripple: defaultRippleAnimation; +} +topBarActionSkip: 10px; + +PeerAvatarButton { + size: pixels; + photoSize: pixels; +} +topBarInfoButton: PeerAvatarButton { + size: topBarHeight; + photoSize: 42px; +} + // Mac specific macAccessoryWidth: 450.; diff --git a/Telegram/build/version b/Telegram/build/version index 10659728d..f08d2d9d9 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -3,4 +3,4 @@ AppVersionStrMajor 0.10 AppVersionStrSmall 0.10.20 AppVersionStr 0.10.20 AlphaChannel 0 -BetaVersion 10019006 +BetaVersion 10019007 diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index d19b437ca..43a82bef8 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -462,6 +462,8 @@ '<(src_loc)/ui/effects/radial_animation.h', '<(src_loc)/ui/effects/rect_shadow.cpp', '<(src_loc)/ui/effects/rect_shadow.h', + '<(src_loc)/ui/effects/ripple_animation.cpp', + '<(src_loc)/ui/effects/ripple_animation.h', '<(src_loc)/ui/effects/round_image_checkbox.cpp', '<(src_loc)/ui/effects/round_image_checkbox.h', '<(src_loc)/ui/effects/widget_fade_wrap.cpp', @@ -504,6 +506,8 @@ '<(src_loc)/ui/widgets/filled_slider.h', '<(src_loc)/ui/widgets/inner_dropdown.cpp', '<(src_loc)/ui/widgets/inner_dropdown.h', + '<(src_loc)/ui/widgets/input_fields.cpp', + '<(src_loc)/ui/widgets/input_fields.h', '<(src_loc)/ui/widgets/label_simple.cpp', '<(src_loc)/ui/widgets/label_simple.h', '<(src_loc)/ui/widgets/media_slider.cpp', @@ -528,12 +532,8 @@ '<(src_loc)/ui/emoji_config.h', '<(src_loc)/ui/filedialog.cpp', '<(src_loc)/ui/filedialog.h', - '<(src_loc)/ui/flatinput.cpp', - '<(src_loc)/ui/flatinput.h', '<(src_loc)/ui/flatlabel.cpp', '<(src_loc)/ui/flatlabel.h', - '<(src_loc)/ui/flattextarea.cpp', - '<(src_loc)/ui/flattextarea.h', '<(src_loc)/ui/images.cpp', '<(src_loc)/ui/images.h', '<(src_loc)/ui/scrollarea.cpp',