diff --git a/Telegram/Resources/art/sprite.png b/Telegram/Resources/art/sprite.png index 450efca53..def492ee1 100644 Binary files a/Telegram/Resources/art/sprite.png and b/Telegram/Resources/art/sprite.png differ diff --git a/Telegram/Resources/art/sprite_200x.png b/Telegram/Resources/art/sprite_200x.png index ed5987c10..9098c1f0d 100644 Binary files a/Telegram/Resources/art/sprite_200x.png and b/Telegram/Resources/art/sprite_200x.png differ diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 67e166752..50f2cbc91 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -91,15 +91,6 @@ boxTitleFont: font(boxFontSize bold); boxTitlePosition: point(26px, 28px); boxTitleHeight: 54px; -boxBlueTitleBg: #6393b5; -boxBlueTitleAdditionalFg: #dae9f5; -boxBlueTitleAdditionalSkip: 12px; -boxBlueTitlePosition: point(23px, 18px); -boxBlueCloseIcon: sprite(120px, 108px, 12px, 12px); -boxBlueCloseBg: #c8e1f0; -boxBlueCloseDuration: 150; -boxBlueShadow: sprite(132px, 108px, 1px, 4px); - boxButtonFont: font(boxFontSize semibold); defaultBoxButton: RoundButton { textFg: #2f9fea; @@ -193,6 +184,7 @@ defaultInputArea: InputArea { heightMax: 128px; } defaultInputField: InputField { + textBg: white; textFg: black; textMargins: margins(0px, 6px, 0px, 4px); textAlign: align(topleft); @@ -216,15 +208,6 @@ defaultInputField: InputField { height: 32px; } -dialogsSearchField: InputField(defaultInputField) { - textMargins: margins(34px, 7px, 34px, 7px); - - iconSprite: sprite(227px, 21px, 24px, 24px); - iconPosition: point(6px, 5px); - - width: 240px; - height: 34px; -} defaultCheckbox: Checkbox { textFg: black; textBg: white; @@ -343,52 +326,8 @@ boxScroll: flatScroll(solidScroll) { boxScrollSkip: 6px; boxScrollShadowBg: #00000012; -boxSearchField: InputField(defaultInputField) { - textMargins: margins(41px, 16px, 41px, 0px); - - placeholderFg: #999; - placeholderFgActive: #aaa; - placeholderMargins: margins(4px, 0px, 4px, 0px); - - border: 0px; - borderActive: 0px; - borderError: 0px; - - height: 48px; - - iconSprite: sprite(227px, 21px, 24px, 24px); - iconPosition: point(15px, 14px); - - font: normalFont; -} -boxSearchCancel: iconedButton { - color: white; - bgColor: white; - overBgColor: white; - font: font(fsize); - - opacity: 0.3; - overOpacity: 0.4; - - textPos: point(0px, 0px); - downTextPos: point(0px, 0px); - - duration: 150; - cursor: cursor(pointer); - - icon: sprite(133px, 108px, 12px, 12px); - iconPos: point(8px, 18px); - downIcon: sprite(133px, 108px, 12px, 12px); - downIconPos: point(8px, 18px); - - width: 41px; - height: 48px; -} - titleBg: #6389a8; titleHeight: 39px; -titleIconPos: point(7px, 7px); -titleIconImg: sprite(161px, 100px, 26px, 26px); titleFont: font(17px); titlePos: point(44px, 29px); titleMenuOffset: 36px; @@ -788,12 +727,11 @@ dlgFilter: flatInput(inpDefGray) { bgColor: #f2f2f2; phColor: #949494; phFocusColor: #a4a4a4; - imgRect: sprite(227px, 21px, 24px, 24px); + icon: icon {{ "box_search_icon", #aaaaaa, point(10px, 9px) }}; width: 240px; height: 34px; textMrg: margins(34px, 2px, 34px, 4px); - imgPos: point(6px, 5px); } topBarHeight: 54px; @@ -959,8 +897,6 @@ msgDateImgBgSelected: #1c4a7187; msgDateImgPadding: point(8px, 2px); msgDateImgCheckSpace: 4px; -msgDogImg: sprite(216px, 92px, 126px, 126px); - collapseButton: flatButton(btnDefFlat) { font: msgServiceFont; overFont: msgServiceFont; @@ -1340,9 +1276,6 @@ layerPadding: margins(10px, 10px, 10px, 10px); contactPadding: margins(49px, 22px, 0px, 6px); contactSkip: 13px; contactPhoneSkip: 30px; -contactUserIcon: sprite(120px, 90px, 18px, 18px); -contactPhoneIcon: sprite(138px, 90px, 18px, 18px); -contactIconTop: 10px; contactsPhotoSize: 42px; contactsPadding: margins(16px, 7px, 16px, 7px); @@ -1354,15 +1287,7 @@ contactsStatusFg: #999999; contactsStatusFgOver: #7c99b2; contactsStatusFgOnline: #3b8dcc; contactsBgOver: overBg; -contactsBgActive: #6f9cbd; contactsCheckPosition: point(8px, 16px); -contactsCheckIcon: sprite(187px, 61px, 18px, 14px); -contactsCheckActiveIcon: sprite(187px, 75px, 18px, 14px); -contactsNewItemHeight: 53px; -contactsNewItemIcon: sprite(307px, 248px, 22px, 16px); -contactsNewItemIconPosition: point(29px, 19px); -contactsNewItemTop: 18px; -contactsNewItemFg: #4b82af; contactsAboutBg: #f7f7f7; contactsAboutShadow: #0000001F; contactsAdminCheckbox: Checkbox(defaultBoxCheckbox) { @@ -1886,8 +1811,6 @@ medviewSaveMsgShown: 2000; medviewSaveMsgHiding: 2500; medviewSaveMsg: #000000b2; -mvTransparentBrush: sprite(9px, 124px, 8px, 8px); - // Mac specific macAccessoryWidth: 450.; diff --git a/Telegram/Resources/basic_types.style b/Telegram/Resources/basic_types.style index 73918d110..f3baf7e69 100644 --- a/Telegram/Resources/basic_types.style +++ b/Telegram/Resources/basic_types.style @@ -130,8 +130,7 @@ flatInput { font: font; cursor: cursor; - imgRect: sprite; - imgPos: point; + icon: icon; borderWidth: pixels; borderColor: color; @@ -394,6 +393,7 @@ InputArea { } InputField { + textBg: color; textFg: color; textMargins: margins; textAlign: align; @@ -418,9 +418,6 @@ InputField { width: pixels; height: pixels; - - iconSprite: sprite; - iconPosition: point; } PeerAvatarButton { diff --git a/Telegram/Resources/icons/add_contact_phone.png b/Telegram/Resources/icons/add_contact_phone.png new file mode 100644 index 000000000..f649ba2fd Binary files /dev/null and b/Telegram/Resources/icons/add_contact_phone.png differ diff --git a/Telegram/Resources/icons/add_contact_phone@2x.png b/Telegram/Resources/icons/add_contact_phone@2x.png new file mode 100644 index 000000000..b01522770 Binary files /dev/null and b/Telegram/Resources/icons/add_contact_phone@2x.png differ diff --git a/Telegram/Resources/icons/add_contact_user.png b/Telegram/Resources/icons/add_contact_user.png new file mode 100644 index 000000000..1d494cde8 Binary files /dev/null and b/Telegram/Resources/icons/add_contact_user.png differ diff --git a/Telegram/Resources/icons/add_contact_user@2x.png b/Telegram/Resources/icons/add_contact_user@2x.png new file mode 100644 index 000000000..a45ef3073 Binary files /dev/null and b/Telegram/Resources/icons/add_contact_user@2x.png differ diff --git a/Telegram/Resources/icons/box_button_close.png b/Telegram/Resources/icons/box_button_close.png new file mode 100644 index 000000000..57abcfb82 Binary files /dev/null and b/Telegram/Resources/icons/box_button_close.png differ diff --git a/Telegram/Resources/icons/box_button_close@2x.png b/Telegram/Resources/icons/box_button_close@2x.png new file mode 100644 index 000000000..bf2a19e72 Binary files /dev/null and b/Telegram/Resources/icons/box_button_close@2x.png differ diff --git a/Telegram/Resources/icons/box_search_cancel.png b/Telegram/Resources/icons/box_search_cancel.png new file mode 100644 index 000000000..7fa112a33 Binary files /dev/null and b/Telegram/Resources/icons/box_search_cancel.png differ diff --git a/Telegram/Resources/icons/box_search_cancel@2x.png b/Telegram/Resources/icons/box_search_cancel@2x.png new file mode 100644 index 000000000..40d3def3d Binary files /dev/null and b/Telegram/Resources/icons/box_search_cancel@2x.png differ diff --git a/Telegram/Resources/icons/box_search_icon.png b/Telegram/Resources/icons/box_search_icon.png new file mode 100644 index 000000000..047352607 Binary files /dev/null and b/Telegram/Resources/icons/box_search_icon.png differ diff --git a/Telegram/Resources/icons/box_search_icon@2x.png b/Telegram/Resources/icons/box_search_icon@2x.png new file mode 100644 index 000000000..4d2c66f86 Binary files /dev/null and b/Telegram/Resources/icons/box_search_icon@2x.png differ diff --git a/Telegram/Resources/icons/box_title_shadow.png b/Telegram/Resources/icons/box_title_shadow.png new file mode 100644 index 000000000..bba09b240 Binary files /dev/null and b/Telegram/Resources/icons/box_title_shadow.png differ diff --git a/Telegram/Resources/icons/box_title_shadow@2x.png b/Telegram/Resources/icons/box_title_shadow@2x.png new file mode 100644 index 000000000..7d97eb59e Binary files /dev/null and b/Telegram/Resources/icons/box_title_shadow@2x.png differ diff --git a/Telegram/Resources/icons/contacts_add.png b/Telegram/Resources/icons/contacts_add.png new file mode 100644 index 000000000..98f770cc6 Binary files /dev/null and b/Telegram/Resources/icons/contacts_add.png differ diff --git a/Telegram/Resources/icons/contacts_add@2x.png b/Telegram/Resources/icons/contacts_add@2x.png new file mode 100644 index 000000000..7fc3c170f Binary files /dev/null and b/Telegram/Resources/icons/contacts_add@2x.png differ diff --git a/Telegram/Resources/icons/history_empty_dog.png b/Telegram/Resources/icons/history_empty_dog.png new file mode 100644 index 000000000..3e6fef342 Binary files /dev/null and b/Telegram/Resources/icons/history_empty_dog.png differ diff --git a/Telegram/Resources/icons/history_empty_dog@2x.png b/Telegram/Resources/icons/history_empty_dog@2x.png new file mode 100644 index 000000000..248860d6b Binary files /dev/null and b/Telegram/Resources/icons/history_empty_dog@2x.png differ diff --git a/Telegram/Resources/icons/title_icon.png b/Telegram/Resources/icons/title_icon.png new file mode 100644 index 000000000..12772e6a3 Binary files /dev/null and b/Telegram/Resources/icons/title_icon.png differ diff --git a/Telegram/Resources/icons/title_icon@2x.png b/Telegram/Resources/icons/title_icon@2x.png new file mode 100644 index 000000000..ad40f1fea Binary files /dev/null and b/Telegram/Resources/icons/title_icon@2x.png differ diff --git a/Telegram/Resources/icons/title_icon_bg.png b/Telegram/Resources/icons/title_icon_bg.png new file mode 100644 index 000000000..ebdf86291 Binary files /dev/null and b/Telegram/Resources/icons/title_icon_bg.png differ diff --git a/Telegram/Resources/icons/title_icon_bg@2x.png b/Telegram/Resources/icons/title_icon_bg@2x.png new file mode 100644 index 000000000..33924433d Binary files /dev/null and b/Telegram/Resources/icons/title_icon_bg@2x.png differ diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index facd674d1..6bfed1347 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2819,41 +2819,7 @@ namespace { uint64 max = qMax(1ULL, components[maxtomin[0]]), mid = qMax(1ULL, components[maxtomin[1]]), min = qMax(1ULL, components[maxtomin[2]]); - QImage dog = App::sprite().toImage().copy(st::msgDogImg.rect()); - QImage::Format f = dog.format(); - if (f != QImage::Format_ARGB32 && f != QImage::Format_ARGB32_Premultiplied) { - dog = dog.convertToFormat(QImage::Format_ARGB32_Premultiplied); - } - uchar *dogBits = dog.bits(); - if (max != min) { - float64 coef = float64(mid - min) / float64(max - min); - for (int i = 0, s = dog.width() * dog.height() * 4; i < s; i += 4) { - int dogmaxtomin[3] = { i, i + 1, i + 2 }; - if (dogBits[dogmaxtomin[0]] < dogBits[dogmaxtomin[1]]) { - qSwap(dogmaxtomin[0], dogmaxtomin[1]); - } - if (dogBits[dogmaxtomin[1]] < dogBits[dogmaxtomin[2]]) { - qSwap(dogmaxtomin[1], dogmaxtomin[2]); - if (dogBits[dogmaxtomin[0]] < dogBits[dogmaxtomin[1]]) { - qSwap(dogmaxtomin[0], dogmaxtomin[1]); - } - } - uchar result[3]; - result[maxtomin[0]] = dogBits[dogmaxtomin[0]]; - result[maxtomin[2]] = dogBits[dogmaxtomin[2]]; - result[maxtomin[1]] = uchar(qRound(result[maxtomin[2]] + (result[maxtomin[0]] - result[maxtomin[2]]) * coef)); - dogBits[i] = result[2]; - dogBits[i + 1] = result[1]; - dogBits[i + 2] = result[0]; - } - } else { - for (int i = 0, s = dog.width() * dog.height() * 4; i < s; i += 4) { - uchar b = dogBits[i], g = dogBits[i + 1], r = dogBits[i + 2]; - dogBits[i] = dogBits[i + 1] = dogBits[i + 2] = (r + r + b + g + g + g) / 6; - } - } - - Window::chatBackground()->init(id, pixmapFromImageInPlace(std_::move(img)), pixmapFromImageInPlace(std_::move(dog))); + Window::chatBackground()->init(id, pixmapFromImageInPlace(std_::move(img))); memcpy(componentsScroll, components, sizeof(components)); memcpy(componentsPoint, components, sizeof(components)); diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp index a9f4974f4..6df51ba70 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.cpp +++ b/Telegram/SourceFiles/boxes/abstractbox.cpp @@ -26,16 +26,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "abstractbox.h" #include "mainwidget.h" #include "mainwindow.h" +#include "styles/style_boxes.h" void BlueTitleShadow::paintEvent(QPaintEvent *e) { Painter p(this); QRect r(e->rect()); - p.drawPixmap(QRect(r.left(), 0, r.width(), height()), App::sprite(), st::boxBlueShadow.rect()); + st::boxBlueTitleShadow.fill(p, QRect(r.left(), 0, r.width(), height())); } BlueTitleClose::BlueTitleClose(QWidget *parent) : Button(parent) -, a_iconFg(st::boxBlueCloseBg->c) +, a_iconFg(st::boxBlueCloseFg->c) , _a_over(animation(this, &BlueTitleClose::step_over)) { resize(st::boxTitleHeight, st::boxTitleHeight); setCursor(style::cur_pointer); @@ -44,7 +45,7 @@ BlueTitleClose::BlueTitleClose(QWidget *parent) : Button(parent) void BlueTitleClose::onStateChange(int oldState, ButtonStateChangeSource source) { if ((oldState & StateOver) != (_state & StateOver)) { - a_iconFg.start(((_state & StateOver) ? st::white : st::boxBlueCloseBg)->c); + a_iconFg.start(((_state & StateOver) ? st::boxBlueCloseOverFg : st::boxBlueCloseFg)->c); _a_over.start(); } } @@ -57,28 +58,23 @@ void BlueTitleClose::step_over(float64 ms, bool timer) { } else { a_iconFg.update(dt, anim::linear); } - if (timer) update((st::boxTitleHeight - st::boxBlueCloseIcon.pxWidth()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.pxHeight()) / 2, st::boxBlueCloseIcon.pxWidth(), st::boxBlueCloseIcon.pxHeight()); + if (timer) update((st::boxTitleHeight - st::boxBlueCloseIcon.width()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.height()) / 2, st::boxBlueCloseIcon.width(), st::boxBlueCloseIcon.height()); } void BlueTitleClose::paintEvent(QPaintEvent *e) { Painter p(this); - QRect r(e->rect()), s((st::boxTitleHeight - st::boxBlueCloseIcon.pxWidth()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.pxHeight()) / 2, st::boxBlueCloseIcon.pxWidth(), st::boxBlueCloseIcon.pxHeight()); + QRect r(e->rect()), s((st::boxTitleHeight - st::boxBlueCloseIcon.width()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.height()) / 2, st::boxBlueCloseIcon.width(), st::boxBlueCloseIcon.height()); if (!s.contains(r)) { - p.fillRect(r, st::boxBlueTitleBg->b); + p.fillRect(r, st::boxBlueTitleBg); } if (s.intersects(r)) { p.fillRect(s.intersected(r), a_iconFg.current()); - p.drawSprite(s.topLeft(), st::boxBlueCloseIcon); + st::boxBlueCloseIcon.paint(p, s.topLeft(), width()); } } -AbstractBox::AbstractBox(int32 w) : LayerWidget() -, _maxHeight(0) -, _closed(false) -, _blueTitle(false) -, _blueClose(0) -, _blueShadow(0) { +AbstractBox::AbstractBox(int w) : LayerWidget() { setAttribute(Qt::WA_OpaquePaintEvent); resize(w, 0); } @@ -101,7 +97,7 @@ void AbstractBox::resizeEvent(QResizeEvent *e) { } if (_blueShadow) { _blueShadow->moveToLeft(0, st::boxTitleHeight); - _blueShadow->resize(width(), st::boxBlueShadow.pxHeight()); + _blueShadow->resize(width(), st::boxBlueTitleShadow.height()); } LayerWidget::resizeEvent(e); } @@ -165,8 +161,8 @@ void AbstractBox::resizeMaxHeight(int32 newWidth, int32 maxHeight) { } } -int32 AbstractBox::countHeight() const { - return qMin(_maxHeight, App::wnd()->height() - int32(2 * st::boxVerticalMargin)); +int AbstractBox::countHeight() const { + return qMin(_maxHeight, App::wnd()->height() - 2 * st::boxVerticalMargin); } void AbstractBox::onClose() { @@ -201,24 +197,28 @@ ScrollableBox::ScrollableBox(const style::flatScroll &scroll, int32 w) : Abstrac } void ScrollableBox::resizeEvent(QResizeEvent *e) { - _scroll->setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip); + updateScrollGeometry(); AbstractBox::resizeEvent(e); } -void ScrollableBox::init(QWidget *inner, int bottomSkip, int topSkip) { - _bottomSkip = bottomSkip; - _topSkip = topSkip; - _scroll->setWidget(inner); - _scroll->setFocusPolicy(Qt::NoFocus); - ScrollableBox::resizeEvent(nullptr); -} - -void ScrollableBox::initOwned(QWidget *inner, int bottomSkip, int topSkip) { +void ScrollableBox::init(ScrolledWidget *inner, int bottomSkip, int topSkip) { _bottomSkip = bottomSkip; _topSkip = topSkip; _scroll->setOwnedWidget(inner); _scroll->setFocusPolicy(Qt::NoFocus); - ScrollableBox::resizeEvent(nullptr); + updateScrollGeometry(); +} + +void ScrollableBox::setScrollSkips(int bottomSkip, int topSkip) { + if (_topSkip != topSkip || _bottomSkip != bottomSkip) { + _topSkip = topSkip; + _bottomSkip = bottomSkip; + updateScrollGeometry(); + } +} + +void ScrollableBox::updateScrollGeometry() { + _scroll->setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip); } ItemListBox::ItemListBox(const style::flatScroll &scroll, int32 w) : ScrollableBox(scroll, w) { diff --git a/Telegram/SourceFiles/boxes/abstractbox.h b/Telegram/SourceFiles/boxes/abstractbox.h index c8a7a7f2f..4c6e28b1a 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.h +++ b/Telegram/SourceFiles/boxes/abstractbox.h @@ -52,7 +52,7 @@ class AbstractBox : public LayerWidget, protected base::Subscriber { Q_OBJECT public: - AbstractBox(int32 w = st::boxWideWidth); + AbstractBox(int w = st::boxWideWidth); void parentResized() override; void showDone() override { showAll(); @@ -83,14 +83,14 @@ protected: } private: - int32 _maxHeight; - int32 countHeight() const; + int _maxHeight = 0; + int countHeight() const; - bool _closed; + bool _closed = false; - bool _blueTitle; - BlueTitleClose *_blueClose; - BlueTitleShadow *_blueShadow; + bool _blueTitle = false; + BlueTitleClose *_blueClose = nullptr; + BlueTitleShadow *_blueShadow = nullptr; }; @@ -105,8 +105,8 @@ public: ScrollableBox(const style::flatScroll &scroll, int w = st::boxWideWidth); protected: - void init(QWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); - void initOwned(QWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); + void init(ScrolledWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); + void setScrollSkips(int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); void resizeEvent(QResizeEvent *e) override; @@ -115,8 +115,10 @@ protected: } private: + void updateScrollGeometry(); + ChildWidget _scroll; - int32 _topSkip, _bottomSkip; + int _topSkip, _bottomSkip; }; diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 5e561ff33..2ef31e477 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -109,8 +109,8 @@ void AddContactBox::paintEvent(QPaintEvent *e) { paintTitle(p, _boxTitle); if (_retry.isHidden()) { - p.drawSpriteLeft(st::boxPadding.left(), _first.y() + st::contactIconTop, width(), st::contactUserIcon); - p.drawSpriteLeft(st::boxPadding.left(), _phone.y() + st::contactIconTop, width(), st::contactPhoneIcon); + st::contactUserIcon.paint(p, st::boxPadding.left(), _first.y() + st::contactIconTop, width()); + st::contactPhoneIcon.paint(p, st::boxPadding.left(), _phone.y() + st::contactIconTop, width()); } else { p.setPen(st::black->p); p.setFont(st::boxTextFont->f); diff --git a/Telegram/SourceFiles/boxes/backgroundbox.cpp b/Telegram/SourceFiles/boxes/backgroundbox.cpp index 9f2700dea..181ff3d49 100644 --- a/Telegram/SourceFiles/boxes/backgroundbox.cpp +++ b/Telegram/SourceFiles/boxes/backgroundbox.cpp @@ -27,11 +27,41 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "window/chat_background.h" #include "styles/style_overview.h" -BackgroundInner::BackgroundInner() : -_bgCount(0), _rows(0), _over(-1), _overDown(-1) { +BackgroundBox::BackgroundBox() : ItemListBox(st::backgroundScroll) +, _inner(this) { + init(_inner); + + connect(_inner, SIGNAL(backgroundChosen(int)), this, SLOT(onBackgroundChosen(int))); + + prepare(); +} + +void BackgroundBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + paintTitle(p, lang(lng_backgrounds_header)); +} + +void BackgroundBox::onBackgroundChosen(int index) { + if (index >= 0 && index < App::cServerBackgrounds().size()) { + const App::WallPaper &paper(App::cServerBackgrounds().at(index)); + if (App::main()) App::main()->setChatBackground(paper); + + using Update = Window::ChatBackgroundUpdate; + Window::chatBackground()->notify(Update(Update::Type::Start, !paper.id)); + } + onClose(); +} + +BackgroundBox::Inner::Inner(QWidget *parent) : ScrolledWidget(parent) +, _bgCount(0) +, _rows(0) +, _over(-1) +, _overDown(-1) { if (App::cServerBackgrounds().isEmpty()) { resize(BackgroundsInRow * (st::backgroundSize.width() + st::backgroundPadding) + st::backgroundPadding, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding); - MTP::send(MTPaccount_GetWallPapers(), rpcDone(&BackgroundInner::gotWallpapers)); + MTP::send(MTPaccount_GetWallPapers(), rpcDone(&Inner::gotWallpapers)); } else { updateWallpapers(); } @@ -40,7 +70,7 @@ _bgCount(0), _rows(0), _over(-1), _overDown(-1) { setMouseTracking(true); } -void BackgroundInner::gotWallpapers(const MTPVector &result) { +void BackgroundBox::Inner::gotWallpapers(const MTPVector &result) { App::WallPapers wallpapers; wallpapers.push_back(App::WallPaper(0, ImagePtr(st::msgBG0), ImagePtr(st::msgBG0))); @@ -98,7 +128,7 @@ void BackgroundInner::gotWallpapers(const MTPVector &result) { updateWallpapers(); } -void BackgroundInner::updateWallpapers() { +void BackgroundBox::Inner::updateWallpapers() { _bgCount = App::cServerBackgrounds().size(); _rows = _bgCount / BackgroundsInRow; if (_bgCount % BackgroundsInRow) ++_rows; @@ -111,7 +141,7 @@ void BackgroundInner::updateWallpapers() { } } -void BackgroundInner::paintEvent(QPaintEvent *e) { +void BackgroundBox::Inner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); @@ -145,7 +175,7 @@ void BackgroundInner::paintEvent(QPaintEvent *e) { } } -void BackgroundInner::mouseMoveEvent(QMouseEvent *e) { +void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) { int x = e->pos().x(), y = e->pos().y(); int row = int((y - st::backgroundPadding) / (st::backgroundSize.height() + st::backgroundPadding)); if (y - row * (st::backgroundSize.height() + st::backgroundPadding) > st::backgroundPadding + st::backgroundSize.height()) row = _rows + 1; @@ -161,11 +191,11 @@ void BackgroundInner::mouseMoveEvent(QMouseEvent *e) { } } -void BackgroundInner::mousePressEvent(QMouseEvent *e) { +void BackgroundBox::Inner::mousePressEvent(QMouseEvent *e) { _overDown = _over; } -void BackgroundInner::mouseReleaseEvent(QMouseEvent *e) { +void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) { if (_overDown == _over && _over >= 0) { emit backgroundChosen(_over); } else if (_over < 0) { @@ -173,33 +203,5 @@ void BackgroundInner::mouseReleaseEvent(QMouseEvent *e) { } } -void BackgroundInner::resizeEvent(QResizeEvent *e) { -} - -BackgroundBox::BackgroundBox() : ItemListBox(st::backgroundScroll) -, _inner() { - - init(&_inner); - - connect(&_inner, SIGNAL(backgroundChosen(int)), this, SLOT(onBackgroundChosen(int))); - - prepare(); -} - -void BackgroundBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, lang(lng_backgrounds_header)); -} - -void BackgroundBox::onBackgroundChosen(int index) { - if (index >= 0 && index < App::cServerBackgrounds().size()) { - const App::WallPaper &paper(App::cServerBackgrounds().at(index)); - if (App::main()) App::main()->setChatBackground(paper); - - using Update = Window::ChatBackgroundUpdate; - Window::chatBackground()->notify(Update(Update::Type::Start, !paper.id)); - } - onClose(); +void BackgroundBox::Inner::resizeEvent(QResizeEvent *e) { } diff --git a/Telegram/SourceFiles/boxes/backgroundbox.h b/Telegram/SourceFiles/boxes/backgroundbox.h index ebdcab053..798593658 100644 --- a/Telegram/SourceFiles/boxes/backgroundbox.h +++ b/Telegram/SourceFiles/boxes/backgroundbox.h @@ -23,11 +23,30 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "abstractbox.h" #include "core/lambda_wrap.h" -class BackgroundInner : public TWidget, public RPCSender, private base::Subscriber { +class BackgroundBox : public ItemListBox { Q_OBJECT public: - BackgroundInner(); + BackgroundBox(); + +public slots: + void onBackgroundChosen(int index); + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + class Inner; + ChildWidget _inner; + +}; + +// This class is hold in header because it requires Qt preprocessing. +class BackgroundBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { + Q_OBJECT + +public: + Inner(QWidget *parent); signals: void backgroundChosen(int index); @@ -47,20 +66,3 @@ private: int32 _over, _overDown; }; - -class BackgroundBox : public ItemListBox { - Q_OBJECT - -public: - BackgroundBox(); - -public slots: - void onBackgroundChosen(int index); - -protected: - void paintEvent(QPaintEvent *e) override; - -private: - BackgroundInner _inner; - -}; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 306381f21..f50ed827e 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -19,6 +19,17 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ using "basic.style"; +using "ui/widgets/widgets.style"; + +boxBlueTitleBg: #6393b5; +boxBlueTitleAdditionalFg: #dae9f5; +boxBlueTitleAdditionalSkip: 12px; +boxBlueTitlePosition: point(23px, 18px); +boxBlueTitleShadow: icon {{ "box_title_shadow", windowShadowFg }}; +boxBlueCloseFg: #c8e1f0; +boxBlueCloseOverFg: #ffffff; +boxBlueCloseIcon: icon {{ "box_button_close", boxBlueTitleBg }}; +boxBlueCloseDuration: 150; confirmInviteTitle: flatLabel(labelDefFlat) { font: font(16px semibold); @@ -67,26 +78,108 @@ aboutRevokePublicLabel: flatLabel(labelDefFlat) { textFg: windowTextFg; } +contactUserIcon: icon {{ "add_contact_user", #999999 }}; +contactPhoneIcon: icon {{ "add_contact_phone", #999999 }}; +contactIconTop: 10px; + +contactsNewItemHeight: 53px; +contactsNewItemIcon: icon {{ "contacts_add", #749fc2, point(29px, 19px) }}; +contactsNewItemTop: 18px; +contactsNewItemFg: #4b82af; + +contactsMultiSelect: MultiSelect { + padding: margins(8px, 8px, 8px, 8px); + maxHeight: 104px; + scroll: flatScroll(solidScroll) { + deltat: 3px; + deltab: 3px; + round: 1px; + width: 8px; + deltax: 3px; + hiding: 1000; + } + + item: MultiSelectItem { + padding: margins(6px, 7px, 12px, 0px); + maxWidth: 128px; + height: 32px; + font: normalFont; + textBg: contactsBgOver; + textFg: windowTextFg; + textActiveBg: windowActiveBg; + textActiveFg: white; + deleteFg: white; + deleteLeft: 10px; + deleteStroke: 2px; + duration: 150; + minScale: 0.3; + } + itemSkip: 8px; + + field: InputField(defaultInputField) { + textBg: transparent; + textMargins: margins(2px, 7px, 2px, 0px); + + placeholderFg: #999; + placeholderFgActive: #aaa; + placeholderMargins: margins(2px, 0px, 2px, 0px); + + border: 0px; + borderActive: 0px; + borderError: 0px; + + height: 32px; + + font: normalFont; + } + fieldMinWidth: 42px; + fieldIcon: icon {{ "box_search_icon", #aaaaaa, point(11px, 9px) }}; + fieldIconSkip: 36px; + + fieldCancel: IconButton { + width: 41px; + height: 48px; + + opacity: 0.3; + overOpacity: 0.4; + + icon: icon {{ "box_search_cancel", #000000 }}; + iconPosition: point(8px, 18px); + downIconPosition: point(8px, 19px); + + duration: 150; + } + fieldCancelSkip: 34px; +} +contactsPhotoCheckbox: RoundImageCheckbox { + imageRadius: 21px; + imageSmallRadius: 18px; + selectWidth: 2px; + selectFg: windowActiveBg; + selectDuration: 150; + checkBorder: windowBg; + checkBg: windowActiveBg; + checkRadius: 10px; + checkSmallRadius: 3px; + checkIcon: icon {{ "default_checkbox_check", windowBg, point(3px, 6px) }}; +} +contactsPhotoDisabledCheckFg: #bbbbbb; +contactsNameCheckedFg: #2b88b8; + localStorageBoxSkip: 10px; shareRowsTop: 12px; shareRowHeight: 108px; -sharePhotoRadius: 28px; -sharePhotoSmallRadius: 24px; sharePhotoTop: 6px; -shareSelectWidth: 2px; -shareSelectFg: windowActiveBg; -shareCheckBorder: windowBg; -shareCheckBg: windowActiveBg; -shareCheckRadius: 10px; -shareCheckSmallRadius: 3px; -shareCheckIcon: icon {{ "default_checkbox_check", windowBg, point(3px, 6px) }}; +sharePhotoCheckbox: RoundImageCheckbox(contactsPhotoCheckbox) { + imageRadius: 28px; + imageSmallRadius: 24px; +} shareNameFont: font(11px); shareNameFg: windowTextFg; shareNameActiveFg: btnYesColor; shareNameTop: 6px; shareColumnSkip: 6px; -shareSelectDuration: 150; shareActivateDuration: 150; shareScrollDuration: 300; diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index b006dedbb..dff590fce 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -23,12 +23,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_indexed_list.h" #include "styles/style_dialogs.h" +#include "styles/style_history.h" +#include "styles/style_boxes.h" #include "lang.h" #include "boxes/addcontactbox.h" #include "mainwidget.h" #include "mainwindow.h" #include "application.h" #include "ui/filedialog.h" +#include "ui/widgets/multi_select.h" +#include "ui/widgets/widget_slide_wrap.h" #include "boxes/photocropbox.h" #include "boxes/confirmbox.h" #include "observer_peer.h" @@ -38,7 +42,520 @@ QString cantInviteError() { return lng_cant_invite_not_contact(lt_more_info, textcmdLink(qsl("https://telegram.me/spambot"), lang(lng_cant_more_info))); } -ContactsInner::ContactsInner(CreatingGroupType creating) : TWidget() +ContactsBox::ContactsBox() : ItemListBox(st::contactsScroll) +, _inner(this, CreatingGroupNone) +, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) +, _next(this, lang(lng_create_group_next), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _topShadow(this) { + init(); +} + +ContactsBox::ContactsBox(const QString &name, const QImage &photo) : ItemListBox(st::boxScroll) +, _inner(this, CreatingGroupGroup) +, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) +, _next(this, lang(lng_create_group_create), st::defaultBoxButton) +, _cancel(this, lang(lng_create_group_back), st::cancelBoxButton) +, _topShadow(this) +, _creationName(name) +, _creationPhoto(photo) { + init(); +} + +ContactsBox::ContactsBox(ChannelData *channel) : ItemListBox(st::boxScroll) +, _inner(this, channel, MembersFilter::Recent, MembersAlreadyIn()) +, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) +, _next(this, lang(lng_participant_invite), st::defaultBoxButton) +, _cancel(this, lang(lng_create_group_skip), st::cancelBoxButton) +, _topShadow(this) { + init(); +} + +ContactsBox::ContactsBox(ChannelData *channel, MembersFilter filter, const MembersAlreadyIn &already) : ItemListBox((filter == MembersFilter::Admins) ? st::contactsScroll : st::boxScroll) +, _inner(this, channel, filter, already) +, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) +, _next(this, lang(lng_participant_invite), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _topShadow(this) { + init(); +} + +ContactsBox::ContactsBox(ChatData *chat, MembersFilter filter) : ItemListBox(st::boxScroll) +, _inner(this, chat, filter) +, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) +, _next(this, lang((filter == MembersFilter::Admins) ? lng_settings_save : lng_participant_invite), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _topShadow(this) { + init(); +} + +ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::contactsScroll) +, _inner(this, bot) +, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) +, _next(this, lang(lng_create_group_next), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _topShadow(this) { + init(); +} + +void ContactsBox::init() { + _select->resizeToWidth(st::boxWideWidth); + myEnsureResized(_select); + + auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat(); + auto topSkip = getTopScrollSkip(); + auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip; + ItemListBox::init(_inner, bottomSkip, topSkip); + + connect(_inner, SIGNAL(addRequested()), App::wnd(), SLOT(onShowAddContact())); + _inner->setPeerSelectedChangedCallback([this](PeerData *peer, bool checked) { + onPeerSelectedChanged(peer, checked); + }); + for (auto i : _inner->selected()) { + addPeerToMultiSelect(i, true); + } + _inner->setAllAdminsChangedCallback([this] { + if (_inner->allAdmins()) { + _select->entity()->clearQuery(); + _select->slideUp(); + _inner->setFocus(); + } else { + _select->slideDown(); + _select->entity()->setInnerFocus(); + } + updateScrollSkips(); + }); + + if (_inner->channel() && _inner->membersFilter() == MembersFilter::Admins) { + _next.hide(); + _cancel.hide(); + } else if (_inner->chat() && _inner->membersFilter() == MembersFilter::Admins) { + connect(&_next, SIGNAL(clicked()), this, SLOT(onSaveAdmins())); + _bottomShadow = new ScrollableBoxShadow(this); + } else if (_inner->chat() || _inner->channel()) { + connect(&_next, SIGNAL(clicked()), this, SLOT(onInvite())); + _bottomShadow = new ScrollableBoxShadow(this); + } else if (_inner->creating() != CreatingGroupNone) { + connect(&_next, SIGNAL(clicked()), this, SLOT(onCreate())); + _bottomShadow = new ScrollableBoxShadow(this); + } else { + _next.hide(); + _cancel.hide(); + } + connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); + _select->entity()->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); + _select->entity()->setItemRemovedCallback([this](uint64 itemId) { + if (auto peer = App::peerLoaded(itemId)) { + _inner->peerUnselected(peer); + update(); + } + }); + _select->entity()->setSubmittedCallback([this](bool) { onSubmit(); }); + connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); + connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername())); + connect(_inner, SIGNAL(adminAdded()), this, SIGNAL(adminAdded())); + + _searchTimer.setSingleShot(true); + connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername())); + + prepare(); +} + +bool ContactsBox::onSearchByUsername(bool searchCache) { + auto q = _select->entity()->getQuery(); + if (q.isEmpty()) { + if (_peopleRequest) { + _peopleRequest = 0; + } + return true; + } + if (q.size() >= MinUsernameLength) { + if (searchCache) { + PeopleCache::const_iterator i = _peopleCache.constFind(q); + if (i != _peopleCache.cend()) { + _peopleQuery = q; + _peopleRequest = 0; + peopleReceived(i.value(), 0); + return true; + } + } else if (_peopleQuery != q) { + _peopleQuery = q; + _peopleFull = false; + _peopleRequest = MTP::send(MTPcontacts_Search(MTP_string(_peopleQuery), MTP_int(SearchPeopleLimit)), rpcDone(&ContactsBox::peopleReceived), rpcFail(&ContactsBox::peopleFailed)); + _peopleQueries.insert(_peopleRequest, _peopleQuery); + } + } + return false; +} + +void ContactsBox::onNeedSearchByUsername() { + if (!onSearchByUsername(true)) { + _searchTimer.start(AutoSearchTimeout); + } +} + +void ContactsBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId req) { + QString q = _peopleQuery; + + PeopleQueries::iterator i = _peopleQueries.find(req); + if (i != _peopleQueries.cend()) { + q = i.value(); + _peopleCache[q] = result; + _peopleQueries.erase(i); + } + + if (_peopleRequest == req) { + switch (result.type()) { + case mtpc_contacts_found: { + App::feedUsers(result.c_contacts_found().vusers); + App::feedChats(result.c_contacts_found().vchats); + _inner->peopleReceived(q, result.c_contacts_found().vresults.c_vector().v); + } break; + } + + _peopleRequest = 0; + _inner->updateSelection(); + onScroll(); + } +} + +bool ContactsBox::peopleFailed(const RPCError &error, mtpRequestId req) { + if (MTP::isDefaultHandledError(error)) return false; + + if (_peopleRequest == req) { + _peopleRequest = 0; + _peopleFull = true; + } + return true; +} + +void ContactsBox::showAll() { + if (_inner->chat() && _inner->membersFilter() == MembersFilter::Admins && _inner->allAdmins()) { + _select->hideFast(); + } else { + _select->showFast(); + } + if (_inner->channel() && _inner->membersFilter() == MembersFilter::Admins) { + _next.hide(); + _cancel.hide(); + } else if (_inner->chat() || _inner->channel()) { + _next.show(); + _cancel.show(); + } else if (_inner->creating() != CreatingGroupNone) { + _next.show(); + _cancel.show(); + } else { + _next.hide(); + _cancel.hide(); + } + _topShadow.show(); + if (_bottomShadow) _bottomShadow->show(); + ItemListBox::showAll(); +} + +void ContactsBox::doSetInnerFocus() { + if (_select->isHidden()) { + _inner->setFocus(); + } else { + _select->entity()->setInnerFocus(); + } +} + +void ContactsBox::onSubmit() { + _inner->chooseParticipant(); +} + +void ContactsBox::keyPressEvent(QKeyEvent *e) { + auto focused = focusWidget(); + if (_select == focused || _select->isAncestorOf(focusWidget())) { + if (e->key() == Qt::Key_Down) { + _inner->selectSkip(1); + } else if (e->key() == Qt::Key_Up) { + _inner->selectSkip(-1); + } else if (e->key() == Qt::Key_PageDown) { + _inner->selectSkipPage(scrollArea()->height(), 1); + } else if (e->key() == Qt::Key_PageUp) { + _inner->selectSkipPage(scrollArea()->height(), -1); + } else { + ItemListBox::keyPressEvent(e); + } + } else { + ItemListBox::keyPressEvent(e); + } +} + +void ContactsBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + bool addingAdmin = _inner->channel() && _inner->membersFilter() == MembersFilter::Admins; + if (_inner->chat() && _inner->membersFilter() == MembersFilter::Admins) { + paintTitle(p, lang(lng_channel_admins)); + } else if (_inner->chat() || _inner->creating() != CreatingGroupNone) { + QString title(lang(addingAdmin ? lng_channel_add_admin : lng_profile_add_participant)); + QString additional((addingAdmin || (_inner->channel() && !_inner->channel()->isMegagroup())) ? QString() : QString("%1 / %2").arg(_inner->selectedCount()).arg(Global::MegagroupSizeMax())); + paintTitle(p, title, additional); + } else if (_inner->sharingBotGame()) { + paintTitle(p, lang(lng_bot_choose_chat)); + } else if (_inner->bot()) { + paintTitle(p, lang(lng_bot_choose_group)); + } else { + paintTitle(p, lang(lng_contacts_header)); + } +} + +int ContactsBox::getTopScrollSkip() const { + auto result = st::boxTitleHeight; + if (!_select->isHidden()) { + result += _select->height(); + } + return result; +} + +void ContactsBox::updateScrollSkips() { + auto oldScrollHeight = scrollArea()->height(); + auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat(); + auto topSkip = getTopScrollSkip(); + auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip; + setScrollSkips(bottomSkip, topSkip); + auto scrollHeightDelta = scrollArea()->height() - oldScrollHeight; + if (scrollHeightDelta) { + scrollArea()->scrollToY(scrollArea()->scrollTop() - scrollHeightDelta); + } + + _topShadow.setGeometry(0, topSkip, width(), st::lineWidth); +} + +void ContactsBox::resizeEvent(QResizeEvent *e) { + ItemListBox::resizeEvent(e); + + _select->resizeToWidth(width()); + _select->moveToLeft(0, st::boxTitleHeight); + + updateScrollSkips(); + + _inner->resize(width(), _inner->height()); + _next.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _next.height()); + _cancel.moveToRight(st::boxButtonPadding.right() + _next.width() + st::boxButtonPadding.left(), _next.y()); + if (_bottomShadow) _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _next.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); +} + +void ContactsBox::closePressed() { + if (_inner->channel() && !_inner->hasAlreadyMembersInChannel()) { + Ui::showPeerHistory(_inner->channel(), ShowAtTheEndMsgId); + } +} + +void ContactsBox::onFilterUpdate(const QString &filter) { + scrollArea()->scrollToY(0); + _inner->updateFilter(filter); +} + +void ContactsBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) { + using AddItemWay = Ui::MultiSelect::AddItemWay; + auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default; + _select->entity()->addItem(peer->id, peer->shortName(), st::windowActiveBg, PaintUserpicCallback(peer), addItemWay); +} + +void ContactsBox::onPeerSelectedChanged(PeerData *peer, bool checked) { + if (checked) { + addPeerToMultiSelect(peer); + _select->entity()->clearQuery(); + } else { + _select->entity()->removeItem(peer->id); + } + update(); +} + +void ContactsBox::onInvite() { + QVector users(_inner->selected()); + if (users.isEmpty()) { + _select->entity()->setInnerFocus(); + return; + } + + App::main()->addParticipants(_inner->chat() ? (PeerData*)_inner->chat() : _inner->channel(), users); + if (_inner->chat()) { + Ui::hideLayer(); + Ui::showPeerHistory(_inner->chat(), ShowAtTheEndMsgId); + } else { + onClose(); + } +} + +void ContactsBox::onCreate() { + if (_saveRequestId) return; + + auto users = _inner->selectedInputs(); + if (users.isEmpty() || (users.size() == 1 && users.at(0).type() == mtpc_inputUserSelf)) { + _select->entity()->setInnerFocus(); + return; + } + _saveRequestId = MTP::send(MTPmessages_CreateChat(MTP_vector(users), MTP_string(_creationName)), rpcDone(&ContactsBox::creationDone), rpcFail(&ContactsBox::creationFail)); +} + +void ContactsBox::onSaveAdmins() { + if (_saveRequestId) return; + + _inner->saving(true); + _saveRequestId = MTP::send(MTPmessages_ToggleChatAdmins(_inner->chat()->inputChat, MTP_bool(!_inner->allAdmins())), rpcDone(&ContactsBox::saveAdminsDone), rpcFail(&ContactsBox::saveAdminsFail)); +} + +void ContactsBox::saveAdminsDone(const MTPUpdates &result) { + App::main()->sentUpdatesReceived(result); + saveSelectedAdmins(); +} + +void ContactsBox::saveSelectedAdmins() { + if (_inner->allAdmins() && !_inner->chat()->participants.isEmpty()) { + onClose(); + } else { + _saveRequestId = MTP::send(MTPmessages_GetFullChat(_inner->chat()->inputChat), rpcDone(&ContactsBox::getAdminsDone), rpcFail(&ContactsBox::saveAdminsFail)); + } +} + +void ContactsBox::getAdminsDone(const MTPmessages_ChatFull &result) { + App::api()->processFullPeer(_inner->chat(), result); + if (_inner->allAdmins()) { + onClose(); + return; + } + ChatData::Admins curadmins = _inner->chat()->admins; + QVector newadmins = _inner->selected(), appoint; + if (!newadmins.isEmpty()) { + appoint.reserve(newadmins.size()); + for (int32 i = 0, l = newadmins.size(); i < l; ++i) { + ChatData::Admins::iterator c = curadmins.find(newadmins.at(i)); + if (c == curadmins.cend()) { + if (newadmins.at(i)->id != peerFromUser(_inner->chat()->creator)) { + appoint.push_back(newadmins.at(i)); + } + } else { + curadmins.erase(c); + } + } + } + _saveRequestId = 0; + + for_const (UserData *user, curadmins) { + MTP::send(MTPmessages_EditChatAdmin(_inner->chat()->inputChat, user->inputUser, MTP_boolFalse()), rpcDone(&ContactsBox::removeAdminDone, user), rpcFail(&ContactsBox::editAdminFail), 0, 10); + } + for_const (UserData *user, appoint) { + MTP::send(MTPmessages_EditChatAdmin(_inner->chat()->inputChat, user->inputUser, MTP_boolTrue()), rpcDone(&ContactsBox::setAdminDone, user), rpcFail(&ContactsBox::editAdminFail), 0, 10); + } + MTP::sendAnything(); + + _saveRequestId = curadmins.size() + appoint.size(); + if (!_saveRequestId) { + onClose(); + } +} + +void ContactsBox::setAdminDone(UserData *user, const MTPBool &result) { + if (mtpIsTrue(result)) { + if (_inner->chat()->noParticipantInfo()) { + App::api()->requestFullPeer(_inner->chat()); + } else { + _inner->chat()->admins.insert(user); + } + } + --_saveRequestId; + if (!_saveRequestId) { + emit App::main()->peerUpdated(_inner->chat()); + onClose(); + } +} + +void ContactsBox::removeAdminDone(UserData *user, const MTPBool &result) { + if (mtpIsTrue(result)) { + _inner->chat()->admins.remove(user); + } + --_saveRequestId; + if (!_saveRequestId) { + emit App::main()->peerUpdated(_inner->chat()); + onClose(); + } +} + +bool ContactsBox::saveAdminsFail(const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return true; + _saveRequestId = 0; + _inner->saving(false); + if (error.type() == qstr("CHAT_NOT_MODIFIED")) { + saveSelectedAdmins(); + } + return false; +} + +bool ContactsBox::editAdminFail(const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return true; + --_saveRequestId; + _inner->chat()->invalidateParticipants(); + if (!_saveRequestId) { + if (error.type() == qstr("USER_RESTRICTED")) { + Ui::showLayer(new InformBox(lang(lng_cant_do_this))); + return true; + } + onClose(); + } + return false; +} + +void ContactsBox::onScroll() { + _inner->loadProfilePhotos(scrollArea()->scrollTop()); +} + +void ContactsBox::creationDone(const MTPUpdates &updates) { + Ui::hideLayer(); + + App::main()->sentUpdatesReceived(updates); + const QVector *v = 0; + switch (updates.type()) { + case mtpc_updates: v = &updates.c_updates().vchats.c_vector().v; break; + case mtpc_updatesCombined: v = &updates.c_updatesCombined().vchats.c_vector().v; break; + default: LOG(("API Error: unexpected update cons %1 (ContactsBox::creationDone)").arg(updates.type())); break; + } + + PeerData *peer = 0; + if (v && !v->isEmpty() && v->front().type() == mtpc_chat) { + peer = App::chat(v->front().c_chat().vid.v); + if (peer) { + if (!_creationPhoto.isNull()) { + App::app()->uploadProfilePhoto(_creationPhoto, peer->id); + } + Ui::showPeerHistory(peer, ShowAtUnreadMsgId); + } + } else { + LOG(("API Error: chat not found in updates (ContactsBox::creationDone)")); + } +} + +bool ContactsBox::creationFail(const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + _saveRequestId = 0; + if (error.type() == "NO_CHAT_TITLE") { + onClose(); + return true; + } else if (error.type() == "USERS_TOO_FEW") { + _select->entity()->setInnerFocus(); + return true; + } else if (error.type() == "PEER_FLOOD") { + Ui::showLayer(new InformBox(cantInviteError()), KeepOtherLayers); + return true; + } else if (error.type() == qstr("USER_RESTRICTED")) { + Ui::showLayer(new InformBox(lang(lng_cant_do_this))); + return true; + } + return false; +} + +ContactsBox::Inner::ContactData::ContactData(PeerData *peer, base::lambda_wrap updateCallback) +: checkbox(std_::make_unique(st::contactsPhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer))) { +} + +ContactsBox::Inner::Inner(QWidget *parent, CreatingGroupType creating) : ScrolledWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _newItemHeight(creating == CreatingGroupNone ? st::contactsNewItemHeight : 0) , _creating(creating) @@ -48,7 +565,7 @@ ContactsInner::ContactsInner(CreatingGroupType creating) : TWidget() init(); } -ContactsInner::ContactsInner(ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already) : TWidget() +ContactsBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already) : ScrolledWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _channel(channel) , _membersFilter(membersFilter) @@ -66,19 +583,19 @@ namespace { } } -ContactsInner::ContactsInner(ChatData *chat, MembersFilter membersFilter) : TWidget() +ContactsBox::Inner::Inner(QWidget *parent, ChatData *chat, MembersFilter membersFilter) : ScrolledWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _chat(chat) , _membersFilter(membersFilter) , _allAdmins(this, lang(lng_chat_all_members_admins), !_chat->adminsEnabled(), st::contactsAdminCheckbox) -, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right() - st::contactsCheckPosition.x() * 2 - st::contactsCheckIcon.pxWidth()) +, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right()) , _aboutAllAdmins(st::boxTextFont, lang(lng_chat_about_all_admins), _defaultOptions, _aboutWidth) , _aboutAdmins(st::boxTextFont, lang(lng_chat_about_admins), _defaultOptions, _aboutWidth) -, _customList((membersFilter == MembersFilterRecent) ? std_::unique_ptr() : std_::make_unique(Dialogs::SortMode::Add)) -, _contacts((membersFilter == MembersFilterRecent) ? App::main()->contactsList() : _customList.get()) +, _customList((membersFilter == MembersFilter::Recent) ? std_::unique_ptr() : std_::make_unique(Dialogs::SortMode::Add)) +, _contacts((membersFilter == MembersFilter::Recent) ? App::main()->contactsList() : _customList.get()) , _addContactLnk(this, lang(lng_add_contact_button)) { initList(); - if (membersFilter == MembersFilterAdmins) { + if (membersFilter == MembersFilter::Admins) { _newItemHeight = st::contactsNewItemHeight + qMax(_aboutAllAdmins.countHeight(_aboutWidth), _aboutAdmins.countHeight(_aboutWidth)) + st::contactsAboutHeight; if (_contacts->isEmpty()) { App::api()->requestFullPeer(_chat); @@ -88,7 +605,7 @@ ContactsInner::ContactsInner(ChatData *chat, MembersFilter membersFilter) : TWid } template -void ContactsInner::addDialogsToList(FilterCallback callback) { +void ContactsBox::Inner::addDialogsToList(FilterCallback callback) { auto v = App::main()->dialogsList(); for_const (auto row, *v) { auto peer = row->history()->peer; @@ -98,7 +615,7 @@ void ContactsInner::addDialogsToList(FilterCallback callback) { } } -ContactsInner::ContactsInner(UserData *bot) : TWidget() +ContactsBox::Inner::Inner(QWidget *parent, UserData *bot) : ScrolledWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _bot(bot) , _allAdmins(this, lang(lng_chat_all_members_admins), false, st::contactsAdminCheckbox) @@ -128,7 +645,7 @@ ContactsInner::ContactsInner(UserData *bot) : TWidget() init(); } -void ContactsInner::init() { +void ContactsBox::Inner::init() { subscribe(FileDownload::ImageLoaded(), [this] { update(); }); connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact())); connect(&_allAdmins, SIGNAL(changed()), this, SLOT(onAllAdminsChanged())); @@ -148,8 +665,8 @@ void ContactsInner::init() { connect(App::main(), SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(peerUpdated(PeerData*))); } -void ContactsInner::initList() { - if (!_chat || _membersFilter != MembersFilterAdmins) return; +void ContactsBox::Inner::initList() { + if (!_chat || _membersFilter != MembersFilter::Admins) return; QList admins, others; admins.reserve(_chat->admins.size() + 1); @@ -161,7 +678,9 @@ void ContactsInner::initList() { if (i.key()->id == peerFromUser(_chat->creator)) continue; if (!_allAdmins.checked() && _chat->admins.contains(i.key())) { admins.push_back(i.key()); - _checkedContacts.insert(i.key(), true); + if (!_checkedContacts.contains(i.key())) { + _checkedContacts.insert(i.key()); + } } else { others.push_back(i.key()); } @@ -181,14 +700,14 @@ void ContactsInner::initList() { } } -void ContactsInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { +void ContactsBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { if (bot()) { _contacts->peerNameChanged(peer, oldNames, oldChars); } peerUpdated(peer); } -void ContactsInner::onAddBot() { +void ContactsBox::Inner::onAddBot() { if (auto &info = _bot->botInfo) { if (!info->shareGameShortName.isEmpty()) { MTPmessages_SendMedia::Flags sendFlags = 0; @@ -212,27 +731,27 @@ void ContactsInner::onAddBot() { Ui::showPeerHistory(_addToPeer, ShowAtUnreadMsgId); } -void ContactsInner::onAddAdmin() { +void ContactsBox::Inner::onAddAdmin() { if (_addAdminRequestId) return; - _addAdminRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _addAdmin->inputUser, MTP_channelRoleEditor()), rpcDone(&ContactsInner::addAdminDone), rpcFail(&ContactsInner::addAdminFail)); + _addAdminRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _addAdmin->inputUser, MTP_channelRoleEditor()), rpcDone(&Inner::addAdminDone), rpcFail(&Inner::addAdminFail)); } -void ContactsInner::onNoAddAdminBox(QObject *obj) { +void ContactsBox::Inner::onNoAddAdminBox(QObject *obj) { if (obj == _addAdminBox) { _addAdminBox = 0; } } -void ContactsInner::onAllAdminsChanged() { - if (_saving) { - if (_allAdmins.checked() != _allAdminsChecked) { - _allAdmins.setChecked(_allAdminsChecked); - } +void ContactsBox::Inner::onAllAdminsChanged() { + if (_saving && _allAdmins.checked() != _allAdminsChecked) { + _allAdmins.setChecked(_allAdminsChecked); + } else if (_allAdminsChangedCallback) { + _allAdminsChangedCallback(); } update(); } -void ContactsInner::addAdminDone(const MTPUpdates &result, mtpRequestId req) { +void ContactsBox::Inner::addAdminDone(const MTPUpdates &result, mtpRequestId req) { if (App::main()) App::main()->sentUpdatesReceived(result); if (req != _addAdminRequestId) return; @@ -257,7 +776,7 @@ void ContactsInner::addAdminDone(const MTPUpdates &result, mtpRequestId req) { emit adminAdded(); } -bool ContactsInner::addAdminFail(const RPCError &error, mtpRequestId req) { +bool ContactsBox::Inner::addAdminFail(const RPCError &error, mtpRequestId req) { if (MTP::isDefaultHandledError(error)) return false; if (req != _addAdminRequestId) return true; @@ -276,16 +795,16 @@ bool ContactsInner::addAdminFail(const RPCError &error, mtpRequestId req) { return true; } -void ContactsInner::saving(bool flag) { +void ContactsBox::Inner::saving(bool flag) { _saving = flag; _allAdminsChecked = _allAdmins.checked(); update(); } -void ContactsInner::peerUpdated(PeerData *peer) { +void ContactsBox::Inner::peerUpdated(PeerData *peer) { if (_chat && (!peer || peer == _chat)) { bool inited = false; - if (_membersFilter == MembersFilterAdmins && _contacts->isEmpty() && !_chat->participants.isEmpty()) { + if (_membersFilter == MembersFilter::Admins && _contacts->isEmpty() && !_chat->participants.isEmpty()) { initList(); inited = true; } @@ -333,7 +852,7 @@ void ContactsInner::peerUpdated(PeerData *peer) { } } -void ContactsInner::loadProfilePhotos(int32 yFrom) { +void ContactsBox::Inner::loadProfilePhotos(int32 yFrom) { int32 yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5; MTP::clearLoaderPriorities(); @@ -364,47 +883,43 @@ void ContactsInner::loadProfilePhotos(int32 yFrom) { } } -ContactsInner::ContactData *ContactsInner::contactData(Dialogs::Row *row) { +ContactsBox::Inner::ContactData *ContactsBox::Inner::contactData(Dialogs::Row *row) { ContactData *data = (ContactData*)row->attached; if (!data) { PeerData *peer = row->history()->peer; ContactsData::const_iterator i = _contactsData.constFind(peer); if (i == _contactsData.cend()) { - _contactsData.insert(peer, data = new ContactData()); + data = usingMultiSelect() ? new ContactData(peer, [this, peer] { updateRowWithPeer(peer); }) : new ContactData(); + _contactsData.insert(peer, data); if (peer->isUser()) { if (_chat) { - if (_membersFilter == MembersFilterRecent) { - data->inchat = _chat->participants.contains(peer->asUser()); - } else { - data->inchat = false; + if (_membersFilter == MembersFilter::Recent) { + data->disabledChecked = _chat->participants.contains(peer->asUser()); } } else if (_creating == CreatingGroupGroup) { - data->inchat = (peerToUser(peer->id) == MTP::authedId()); + data->disabledChecked = (peerToUser(peer->id) == MTP::authedId()); } else if (_channel) { - data->inchat = (peerToUser(peer->id) == MTP::authedId()) || _already.contains(peer->asUser()); - } else { - data->inchat = false; + data->disabledChecked = (peerToUser(peer->id) == MTP::authedId()) || _already.contains(peer->asUser()); } - } else { - data->inchat = false; } - data->onlineColor = false; - data->check = _checkedContacts.contains(peer); + if (usingMultiSelect() && _checkedContacts.contains(peer)) { + data->checkbox->setChecked(true, Ui::RoundImageCheckbox::SetStyle::Fast); + } data->name.setText(st::contactsNameFont, peer->name, _textNameOptions); if (peer->isUser()) { - data->online = App::onlineText(peer->asUser(), _time); - data->onlineColor = App::onlineColorUse(peer->asUser(), _time); + data->statusText = App::onlineText(peer->asUser(), _time); + data->statusHasOnlineColor = App::onlineColorUse(peer->asUser(), _time); } else if (peer->isChat()) { ChatData *chat = peer->asChat(); if (!chat->amIn()) { - data->online = lang(lng_chat_status_unaccessible); + data->statusText = lang(lng_chat_status_unaccessible); } else { - data->online = lng_chat_status_members(lt_count, chat->count); + data->statusText = lng_chat_status_members(lt_count, chat->count); } } else if (peer->isMegagroup()) { - data->online = lang(lng_group_status); + data->statusText = lang(lng_group_status); } else if (peer->isChannel()) { - data->online = lang(lng_channel_status); + data->statusText = lang(lng_channel_status); } } else { data = i.value(); @@ -414,52 +929,61 @@ ContactsInner::ContactData *ContactsInner::contactData(Dialogs::Row *row) { return data; } -void ContactsInner::paintDialog(Painter &p, PeerData *peer, ContactData *data, bool sel) { +void ContactsBox::Inner::paintDialog(Painter &p, uint64 ms, PeerData *peer, ContactData *data, bool sel) { UserData *user = peer->asUser(); - bool inverse = data->inchat || data->check; - if (_chat && _membersFilter == MembersFilterAdmins) { - inverse = false; + if (_chat && _membersFilter == MembersFilter::Admins) { if (_allAdmins.checked() || peer->id == peerFromUser(_chat->creator) || _saving) { sel = false; } } else { - if (data->inchat || data->check || selectedCount() >= Global::MegagroupSizeMax()) { + if (data->disabledChecked || selectedCount() >= Global::MegagroupSizeMax()) { sel = false; } } - p.fillRect(0, 0, width(), _rowHeight, inverse ? st::contactsBgActive : (sel ? st::contactsBgOver : st::white)); - p.setPen(inverse ? st::white : st::black); - peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width()); - int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); - int32 iconw = (_chat || _creating != CreatingGroupNone) ? (st::contactsCheckPosition.x() * 2 + st::contactsCheckIcon.pxWidth()) : 0; - int32 namew = width() - namex - st::contactsPadding.right() - iconw; + auto paintDisabledCheck = data->disabledChecked; + if (_chat && _membersFilter == MembersFilter::Admins) { + if (peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) { + paintDisabledCheck = true; + } + } + + auto checkedRatio = 0.; + p.fillRect(0, 0, width(), _rowHeight, sel ? st::contactsBgOver : st::white); + if (paintDisabledCheck) { + paintDisabledCheckUserpic(p, peer, st::contactsPadding.left(), st::contactsPadding.top(), width()); + } else if (usingMultiSelect()) { + checkedRatio = data->checkbox->checkedAnimationRatio(); + data->checkbox->paint(p, ms, st::contactsPadding.left(), st::contactsPadding.top(), width()); + } else { + peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width()); + } + + int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); + int namew = width() - namex - st::contactsPadding.right(); if (peer->isVerified()) { auto icon = &st::dialogsVerifiedIcon; namew -= icon->width(); icon->paint(p, namex + qMin(data->name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width()); } + if (checkedRatio > 0) { + if (checkedRatio < 1) { + p.setPen(style::interpolate(st::black, st::contactsNameCheckedFg, checkedRatio)); + } else { + p.setPen(st::contactsNameCheckedFg); + } + } else { + p.setPen(st::black); + } data->name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width()); - if (_chat || (_creating != CreatingGroupNone && (!_channel || _membersFilter != MembersFilterAdmins))) { - if (_chat && _membersFilter == MembersFilterAdmins) { - if (sel || data->check || peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) { - if (!data->check || peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) p.setOpacity(0.5); - p.drawSpriteRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + st::contactsCheckPosition.y(), width(), st::contactsCheckIcon); - if (!data->check || peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) p.setOpacity(1); - } - } else if (sel || data->check) { - p.drawSpriteRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + st::contactsCheckPosition.y(), width(), (data->check ? st::contactsCheckActiveIcon : st::contactsCheckIcon)); - } - } - - bool uname = (user || peer->isChannel()) && (data->online.at(0) == '@'); + bool uname = (user || peer->isChannel()) && (data->statusText.at(0) == '@'); p.setFont(st::contactsStatusFont->f); - if (uname && !data->inchat && !data->check && !_lastQuery.isEmpty() && peer->userName().startsWith(_lastQuery, Qt::CaseInsensitive)) { - int32 availw = width() - namex - st::contactsPadding.right() - iconw; + if (uname && !_lastQuery.isEmpty() && peer->userName().startsWith(_lastQuery, Qt::CaseInsensitive)) { + int availw = width() - namex - st::contactsPadding.right(); QString first = '@' + peer->userName().mid(0, _lastQuery.size()), second = peer->userName().mid(_lastQuery.size()); - int32 w = st::contactsStatusFont->width(first); + int w = st::contactsStatusFont->width(first); if (w >= availw || second.isEmpty()) { p.setPen(st::contactsStatusFgOnline); p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), st::contactsStatusFont->elided(first, availw)); @@ -472,25 +996,59 @@ void ContactsInner::paintDialog(Painter &p, PeerData *peer, ContactData *data, b p.drawTextLeft(namex + w, st::contactsPadding.top() + st::contactsStatusTop, width() + w, second); } } else { - if (inverse) { - p.setPen(st::white); - } else if ((user && (uname || data->onlineColor)) || (peer->isChannel() && uname)) { + if ((user && (uname || data->statusHasOnlineColor)) || (peer->isChannel() && uname)) { p.setPen(st::contactsStatusFgOnline); } else { p.setPen(sel ? st::contactsStatusFgOver : st::contactsStatusFg); } - p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->online); + p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->statusText); } } -void ContactsInner::paintEvent(QPaintEvent *e) { +// Emulates Ui::RoundImageCheckbox::paint() in a checked state. +void ContactsBox::Inner::paintDisabledCheckUserpic(Painter &p, PeerData *peer, int x, int y, int outerWidth) const { + auto userpicRadius = st::contactsPhotoCheckbox.imageSmallRadius; + auto userpicShift = st::contactsPhotoCheckbox.imageRadius - userpicRadius; + auto userpicDiameter = st::contactsPhotoCheckbox.imageRadius * 2; + auto userpicLeft = x + userpicShift; + auto userpicTop = y + userpicShift; + auto userpicEllipse = rtlrect(x, y, userpicDiameter, userpicDiameter, outerWidth); + auto userpicBorderPen = st::contactsPhotoDisabledCheckFg->p; + userpicBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth); + + auto iconDiameter = 2 * st::contactsPhotoCheckbox.checkRadius; + auto iconLeft = x + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter; + auto iconTop = y + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter; + auto iconEllipse = rtlrect(iconLeft, iconTop, iconDiameter, iconDiameter, outerWidth); + auto iconBorderPen = st::contactsPhotoCheckbox.checkBorder->p; + iconBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth); + + peer->paintUserpicLeft(p, userpicRadius * 2, userpicLeft, userpicTop, width()); + + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + + p.setPen(userpicBorderPen); + p.setBrush(Qt::NoBrush); + p.drawEllipse(userpicEllipse); + + p.setPen(iconBorderPen); + p.setBrush(st::contactsPhotoDisabledCheckFg); + p.drawEllipse(iconEllipse); + + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + st::contactsPhotoCheckbox.checkIcon.paint(p, iconEllipse.topLeft(), outerWidth); +} + +void ContactsBox::Inner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); p.setClipRect(r); _time = unixtime(); - p.fillRect(r, st::white->b); + p.fillRect(r, st::white); + uint64 ms = getms(); int32 yFrom = r.y(), yTo = r.y() + r.height(); if (_filter.isEmpty()) { if (!_contacts->isEmpty() || !_byUsername.isEmpty()) { @@ -500,13 +1058,12 @@ void ContactsInner::paintEvent(QPaintEvent *e) { p.fillRect(0, _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, width(), st::lineWidth, st::shadowColor); p.setPen(st::black); p.drawTextLeft(st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(lng_chat_all_members_admins)); - int32 iconw = st::contactsCheckPosition.x() * 2 + st::contactsCheckIcon.pxWidth(); - int32 aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right() - iconw; + int aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right(); (_allAdmins.checked() ? _aboutAllAdmins : _aboutAdmins).draw(p, st::contactsPadding.left(), st::contactsNewItemHeight + st::contactsAboutTop, aboutw); } else { p.fillRect(0, 0, width(), st::contactsNewItemHeight, (_newItemSel ? st::contactsBgOver : st::white)->b); p.setFont(st::contactsNameFont); - p.drawSpriteLeft(st::contactsNewItemIconPosition.x(), st::contactsNewItemIconPosition.y(), width(), st::contactsNewItemIcon); + st::contactsNewItemIcon.paint(p, 0, 0, width()); p.setPen(st::contactsNewItemFg); p.drawTextLeft(st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(lng_add_contact_button)); } @@ -521,7 +1078,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) { if ((*i)->pos() * _rowHeight >= yTo) { break; } - paintDialog(p, (*i)->history()->peer, contactData(*i), (*i == _sel)); + paintDialog(p, ms, (*i)->history()->peer, contactData(*i), (*i == _sel)); p.translate(0, _rowHeight); } yFrom -= _contacts->size() * _rowHeight; @@ -541,7 +1098,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) { int32 to = ceilclamp(yTo, _rowHeight, 0, _byUsername.size()); p.translate(0, from * _rowHeight); for (; from < to; ++from) { - paintDialog(p, _byUsername[from], d_byUsername[from], (_byUsernameSel == from)); + paintDialog(p, ms, _byUsername[from], d_byUsername[from], (_byUsernameSel == from)); p.translate(0, _rowHeight); } } @@ -550,14 +1107,13 @@ void ContactsInner::paintEvent(QPaintEvent *e) { int32 skip = 0; if (bot()) { text = lang((cDialogsReceived() && !_searching) ? (sharingBotGame() ? lng_bot_no_chats : lng_bot_no_groups) : lng_contacts_loading); - } else if (_chat && _membersFilter == MembersFilterAdmins) { + } else if (_chat && _membersFilter == MembersFilter::Admins) { text = lang(lng_contacts_loading); p.fillRect(0, 0, width(), _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, st::contactsAboutBg); p.fillRect(0, _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, width(), st::lineWidth, st::shadowColor); p.setPen(st::black); p.drawTextLeft(st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(lng_chat_all_members_admins)); - int32 iconw = st::contactsCheckPosition.x() * 2 + st::contactsCheckIcon.pxWidth(); - int32 aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right() - iconw; + int aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right(); (_allAdmins.checked() ? _aboutAllAdmins : _aboutAdmins).draw(p, st::contactsPadding.left(), st::contactsNewItemHeight + st::contactsAboutTop, aboutw); p.translate(0, _newItemHeight); } else if (cContactsReceived() && !_searching) { @@ -577,7 +1133,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) { QString text; if (bot()) { text = lang((cDialogsReceived() && !_searching) ? (sharingBotGame() ? lng_bot_chats_not_found : lng_bot_groups_not_found) : lng_contacts_loading); - } else if (_chat && _membersFilter == MembersFilterAdmins) { + } else if (_chat && _membersFilter == MembersFilter::Admins) { text = lang(_chat->participants.isEmpty() ? lng_contacts_loading : lng_contacts_not_found); } else { text = lang((cContactsReceived() && !_searching) ? lng_contacts_not_found : lng_contacts_loading); @@ -589,7 +1145,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) { int32 to = ceilclamp(yTo, _rowHeight, 0, _filtered.size()); p.translate(0, from * _rowHeight); for (; from < to; ++from) { - paintDialog(p, _filtered[from]->history()->peer, contactData(_filtered[from]), (_filteredSel == from)); + paintDialog(p, ms, _filtered[from]->history()->peer, contactData(_filtered[from]), (_filteredSel == from)); p.translate(0, _rowHeight); } } @@ -606,7 +1162,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) { int32 to = ceilclamp(yTo, _rowHeight, 0, _byUsernameFiltered.size()); p.translate(0, from * _rowHeight); for (; from < to; ++from) { - paintDialog(p, _byUsernameFiltered[from], d_byUsernameFiltered[from], (_byUsernameSel == from)); + paintDialog(p, ms, _byUsernameFiltered[from], d_byUsernameFiltered[from], (_byUsernameSel == from)); p.translate(0, _rowHeight); } } @@ -614,32 +1170,77 @@ void ContactsInner::paintEvent(QPaintEvent *e) { } } -void ContactsInner::enterEvent(QEvent *e) { +void ContactsBox::Inner::enterEvent(QEvent *e) { setMouseTracking(true); } -void ContactsInner::updateSelectedRow() { +int ContactsBox::Inner::getSelectedRowTop() const { if (_filter.isEmpty()) { - if (_newItemSel) { - update(0, 0, width(), st::contactsNewItemHeight); - } if (_sel) { - update(0, _newItemHeight + _sel->pos() * _rowHeight, width(), _rowHeight); - } - if (_byUsernameSel >= 0) { - update(0, _newItemHeight + _contacts->size() * _rowHeight + st::searchedBarHeight + _byUsernameSel * _rowHeight, width(), _rowHeight); + return _newItemHeight + (_sel->pos() * _rowHeight); + } else if (_byUsernameSel >= 0) { + return _newItemHeight + (_contacts->size() * _rowHeight) + st::searchedBarHeight + (_byUsernameSel * _rowHeight); } } else { if (_filteredSel >= 0) { - update(0, _filteredSel * _rowHeight, width(), _rowHeight); + return (_filteredSel * _rowHeight); + } else if (_byUsernameSel >= 0) { + return (_filtered.size() * _rowHeight + st::searchedBarHeight + _byUsernameSel * _rowHeight); } - if (_byUsernameSel >= 0) { - update(0, _filtered.size() * _rowHeight + st::searchedBarHeight + _byUsernameSel * _rowHeight, width(), _rowHeight); + } + return -1; +} + +void ContactsBox::Inner::updateSelectedRow() { + if (_filter.isEmpty() && _newItemSel) { + update(0, 0, width(), st::contactsNewItemHeight); + } else { + auto rowTop = getSelectedRowTop(); + if (rowTop >= 0) { + updateRowWithTop(rowTop); } } } -void ContactsInner::leaveEvent(QEvent *e) { +void ContactsBox::Inner::updateRowWithTop(int rowTop) { + update(0, rowTop, width(), _rowHeight); +} + +int ContactsBox::Inner::getRowTopWithPeer(PeerData *peer) const { + if (_filter.isEmpty()) { + for (auto i = _contacts->cbegin(), end = _contacts->cend(); i != end; ++i) { + if ((*i)->history()->peer == peer) { + return _newItemHeight + ((*i)->pos() * _rowHeight); + } + } + for (auto i = 0, count = _byUsername.size(); i != count; ++i) { + if (_byUsername[i] == peer) { + return _newItemHeight + (_contacts->size() * _rowHeight) + st::searchedBarHeight + (i * _rowHeight); + } + } + } else { + for (auto i = 0, count = _filtered.size(); i != count; ++i) { + if (_filtered[i]->history()->peer == peer) { + return (i * _rowHeight); + } + } + for (auto i = 0, count = _byUsernameFiltered.size(); i != count; ++i) { + if (_byUsernameFiltered[i] == peer) { + return (_contacts->size() * _rowHeight) + st::searchedBarHeight + (i * _rowHeight); + } + } + } + return -1; +} + +void ContactsBox::Inner::updateRowWithPeer(PeerData *peer) { + auto rowTop = getRowTopWithPeer(peer); + if (rowTop >= 0) { + updateRowWithTop(rowTop); + } +} + +void ContactsBox::Inner::leaveEvent(QEvent *e) { _mouseSel = false; setMouseTracking(false); if (_newItemSel || _sel || _filteredSel >= 0 || _byUsernameSel >= 0) { @@ -650,51 +1251,57 @@ void ContactsInner::leaveEvent(QEvent *e) { } } -void ContactsInner::mouseMoveEvent(QMouseEvent *e) { +void ContactsBox::Inner::mouseMoveEvent(QMouseEvent *e) { _mouseSel = true; _lastMousePos = e->globalPos(); - updateSel(); + updateSelection(); } -void ContactsInner::mousePressEvent(QMouseEvent *e) { +void ContactsBox::Inner::mousePressEvent(QMouseEvent *e) { _mouseSel = true; _lastMousePos = e->globalPos(); - updateSel(); + updateSelection(); if (e->button() == Qt::LeftButton) { chooseParticipant(); } } -void ContactsInner::chooseParticipant() { +void ContactsBox::Inner::chooseParticipant() { if (_saving) return; - bool addingAdmin = (_channel && _membersFilter == MembersFilterAdmins); - if (!addingAdmin && (_chat || _creating != CreatingGroupNone)) { + bool addingAdmin = (_channel && _membersFilter == MembersFilter::Admins); + if (!addingAdmin && usingMultiSelect()) { _time = unixtime(); if (_filter.isEmpty()) { if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) { - if (d_byUsername[_byUsernameSel]->inchat) return; - changeCheckState(d_byUsername[_byUsernameSel], _byUsername[_byUsernameSel]); - } else { - if (!_sel || contactData(_sel)->inchat) return; + auto data = d_byUsername[_byUsernameSel]; + auto peer = _byUsername[_byUsernameSel]; + if (data->disabledChecked) return; + + changeCheckState(data, peer); + } else if (_sel) { + auto data = contactData(_sel); + auto peer = _sel->history()->peer; + if (data->disabledChecked) return; + changeCheckState(_sel); } } else { if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) { - if (d_byUsernameFiltered[_byUsernameSel]->inchat) return; - changeCheckState(d_byUsernameFiltered[_byUsernameSel], _byUsernameFiltered[_byUsernameSel]); + auto data = d_byUsernameFiltered[_byUsernameSel]; + auto peer = _byUsernameFiltered[_byUsernameSel]; + if (data->disabledChecked) return; - ContactData *moving = d_byUsernameFiltered[_byUsernameSel]; - int32 i = 0, l = d_byUsername.size(); + int i = 0, l = d_byUsername.size(); for (; i < l; ++i) { - if (d_byUsername[i] == moving) { + if (d_byUsername[i] == data) { break; } } if (i == l) { - d_byUsername.push_back(moving); - _byUsername.push_back(_byUsernameFiltered[_byUsernameSel]); + d_byUsername.push_back(data); + _byUsername.push_back(peer); for (i = 0, l = _byUsernameDatas.size(); i < l;) { - if (_byUsernameDatas[i] == moving) { + if (_byUsernameDatas[i] == data) { _byUsernameDatas.removeAt(i); --l; } else { @@ -702,11 +1309,15 @@ void ContactsInner::chooseParticipant() { } } } - } else { - if (_filteredSel < 0 || _filteredSel >= _filtered.size() || contactData(_filtered[_filteredSel])->inchat) return; - changeCheckState(_filtered[_filteredSel]); + + changeCheckState(data, peer); + } else if (_filteredSel >= 0 && _filteredSel < _filtered.size()) { + auto data = contactData(_filtered[_filteredSel]); + auto peer = _filtered[_filteredSel]->history()->peer; + if (data->disabledChecked) return; + + changeCheckState(data, peer); } - emit selectAllQuery(); } } else { PeerData *peer = 0; @@ -765,30 +1376,51 @@ void ContactsInner::chooseParticipant() { update(); } -void ContactsInner::changeCheckState(Dialogs::Row *row) { +void ContactsBox::Inner::changeCheckState(Dialogs::Row *row) { changeCheckState(contactData(row), row->history()->peer); } -void ContactsInner::changeCheckState(ContactData *data, PeerData *peer) { - int32 cnt = _selCount; - if (data->check) { - data->check = false; - _checkedContacts.remove(peer); - --_selCount; +void ContactsBox::Inner::changeCheckState(ContactData *data, PeerData *peer) { + t_assert(usingMultiSelect()); + + if (_chat && _membersFilter == MembersFilter::Admins && _allAdmins.checked()) { + } else if (data->checkbox->checked()) { + changePeerCheckState(data, peer, false); } else if (selectedCount() < ((_channel && _channel->isMegagroup()) ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) { - data->check = true; - _checkedContacts.insert(peer, true); - ++_selCount; + changePeerCheckState(data, peer, true); } else if (_channel && !_channel->isMegagroup()) { Ui::showLayer(new MaxInviteBox(_channel->inviteLink()), KeepOtherLayers); } else if (!_channel && selectedCount() >= Global::ChatSizeMax() && selectedCount() < Global::MegagroupSizeMax()) { Ui::showLayer(new InformBox(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), KeepOtherLayers); } - if (cnt != _selCount) emit chosenChanged(); } -int32 ContactsInner::selectedCount() const { - int32 result = _selCount; +void ContactsBox::Inner::peerUnselected(PeerData *peer) { + // If data is nullptr we simply won't do anything. + auto data = _contactsData.value(peer, nullptr); + changePeerCheckState(data, peer, false, ChangeStateWay::SkipCallback); +} + +void ContactsBox::Inner::setPeerSelectedChangedCallback(base::lambda_unique callback) { + _peerSelectedChangedCallback = std_::move(callback); +} + +void ContactsBox::Inner::changePeerCheckState(ContactData *data, PeerData *peer, bool checked, ChangeStateWay useCallback) { + if (data) { + data->checkbox->setChecked(checked); + } + if (checked) { + _checkedContacts.insert(peer); + } else { + _checkedContacts.remove(peer); + } + if (useCallback != ChangeStateWay::SkipCallback && _peerSelectedChangedCallback) { + _peerSelectedChangedCallback(peer, checked); + } +} + +int32 ContactsBox::Inner::selectedCount() const { + auto result = _checkedContacts.size(); if (_chat) { result += qMax(_chat->count, 1); } else if (_channel) { @@ -799,7 +1431,7 @@ int32 ContactsInner::selectedCount() const { return result; } -void ContactsInner::updateSel() { +void ContactsBox::Inner::updateSelection() { if (!_mouseSel) return; QPoint p(mapFromGlobal(_lastMousePos)); @@ -807,7 +1439,7 @@ void ContactsInner::updateSel() { if (_filter.isEmpty()) { bool newItemSel = false; if (_newItemHeight) { - if (in && (p.y() >= 0) && (p.y() < _newItemHeight) && !(_chat && _membersFilter == MembersFilterAdmins)) { + if (in && (p.y() >= 0) && (p.y() < _newItemHeight) && !(_chat && _membersFilter == MembersFilter::Admins)) { newItemSel = true; } p.setY(p.y() - _newItemHeight); @@ -835,7 +1467,7 @@ void ContactsInner::updateSel() { } } -void ContactsInner::updateFilter(QString filter) { +void ContactsBox::Inner::updateFilter(QString filter) { _lastQuery = filter.toLower().trimmed(); filter = textSearchKey(filter); @@ -933,14 +1565,14 @@ void ContactsInner::updateFilter(QString filter) { } _filteredSel = -1; if (!_filtered.isEmpty()) { - for (_filteredSel = 0; (_filteredSel < _filtered.size()) && contactData(_filtered[_filteredSel])->inchat;) { + for (_filteredSel = 0; (_filteredSel < _filtered.size()) && contactData(_filtered[_filteredSel])->disabledChecked;) { ++_filteredSel; } if (_filteredSel == _filtered.size()) _filteredSel = -1; } _byUsernameSel = -1; if (_filteredSel < 0 && !_byUsernameFiltered.isEmpty()) { - for (_byUsernameSel = 0; (_byUsernameSel < _byUsernameFiltered.size()) && d_byUsernameFiltered[_byUsernameSel]->inchat;) { + for (_byUsernameSel = 0; (_byUsernameSel < _byUsernameFiltered.size()) && d_byUsernameFiltered[_byUsernameSel]->disabledChecked;) { ++_byUsernameSel; } if (_byUsernameSel == _byUsernameFiltered.size()) _byUsernameSel = -1; @@ -948,7 +1580,7 @@ void ContactsInner::updateFilter(QString filter) { _mouseSel = false; refresh(); - if ((!bot() || sharingBotGame()) && (!_chat || _membersFilter != MembersFilterAdmins)) { + if ((!bot() || sharingBotGame()) && (!_chat || _membersFilter != MembersFilter::Admins)) { _searching = true; emit searchByUsername(); } @@ -958,7 +1590,7 @@ void ContactsInner::updateFilter(QString filter) { } } -void ContactsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow) { +void ContactsBox::Inner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow) { if (!_filter.isEmpty()) { for (FilteredDialogs::iterator i = _filtered.begin(), e = _filtered.end(); i != e;) { if (*i == oldRow) { // this row is shown in filtered and maybe is in contacts! @@ -986,64 +1618,66 @@ void ContactsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newR resize(width(), newh); } -void ContactsInner::peopleReceived(const QString &query, const QVector &people) { +void ContactsBox::Inner::peopleReceived(const QString &query, const QVector &people) { _lastQuery = query.toLower().trimmed(); if (_lastQuery.at(0) == '@') _lastQuery = _lastQuery.mid(1); int32 already = _byUsernameFiltered.size(); _byUsernameFiltered.reserve(already + people.size()); d_byUsernameFiltered.reserve(already + people.size()); for (QVector::const_iterator i = people.cbegin(), e = people.cend(); i != e; ++i) { - PeerId peerId = peerFromMTP(*i); - int32 j = 0; + auto peerId = peerFromMTP(*i); + int j = 0; for (; j < already; ++j) { if (_byUsernameFiltered[j]->id == peerId) break; } if (j == already) { - PeerData *p = App::peer(peerId); - if (!p) continue; + auto peer = App::peer(peerId); + if (!peer) continue; if (_channel || _chat || _creating != CreatingGroupNone) { - if (p->isUser()) { - if (p->asUser()->botInfo) { + if (peer->isUser()) { + if (peer->asUser()->botInfo) { if (_chat || _creating == CreatingGroupGroup) { // skip bot's that can't be invited to groups - if (p->asUser()->botInfo->cantJoinGroups) continue; + if (peer->asUser()->botInfo->cantJoinGroups) continue; } if (_channel) { - if (!_channel->isMegagroup() && _membersFilter != MembersFilterAdmins) continue; + if (!_channel->isMegagroup() && _membersFilter != MembersFilter::Admins) continue; } } } else { continue; // skip } } else if (sharingBotGame()) { - if (!p->canWrite()) { + if (!peer->canWrite()) { continue; } - if (auto channel = p->asChannel()) { + if (auto channel = peer->asChannel()) { if (channel->isBroadcast()) { continue; } } } - ContactData *d = new ContactData(); - _byUsernameDatas.push_back(d); - d->inchat = _chat ? _chat->participants.contains(p->asUser()) : ((_creating == CreatingGroupGroup || _channel) ? (p == App::self()) : false); - d->check = _checkedContacts.contains(p); - d->name.setText(st::contactsNameFont, p->name, _textNameOptions); - d->online = '@' + p->userName(); + auto data = usingMultiSelect() ? new ContactData(peer, [this, peer] { updateRowWithPeer(peer); }) : new ContactData(); + _byUsernameDatas.push_back(data); + data->disabledChecked = _chat ? _chat->participants.contains(peer->asUser()) : ((_creating == CreatingGroupGroup || _channel) ? (peer == App::self()) : false); + if (usingMultiSelect() && _checkedContacts.contains(peer)) { + data->checkbox->setChecked(true, Ui::RoundImageCheckbox::SetStyle::Fast); + } + data->name.setText(st::contactsNameFont, peer->name, _textNameOptions); + data->statusText = '@' + peer->userName(); - _byUsernameFiltered.push_back(p); - d_byUsernameFiltered.push_back(d); + _byUsernameFiltered.push_back(peer); + d_byUsernameFiltered.push_back(data); } } _searching = false; refresh(); } -void ContactsInner::refresh() { +void ContactsBox::Inner::refresh() { if (_filter.isEmpty()) { - if (_chat && _membersFilter == MembersFilterAdmins) { + if (_chat && _membersFilter == MembersFilter::Admins) { if (_allAdmins.isHidden()) _allAdmins.show(); } else { if (!_allAdmins.isHidden()) _allAdmins.hide(); @@ -1051,7 +1685,7 @@ void ContactsInner::refresh() { if (!_contacts->isEmpty() || !_byUsername.isEmpty()) { if (!_addContactLnk.isHidden()) _addContactLnk.hide(); resize(width(), _newItemHeight + (_contacts->size() * _rowHeight) + (_byUsername.isEmpty() ? 0 : (st::searchedBarHeight + _byUsername.size() * _rowHeight))); - } else if (_chat && _membersFilter == MembersFilterAdmins) { + } else if (_chat && _membersFilter == MembersFilter::Admins) { if (!_addContactLnk.isHidden()) _addContactLnk.hide(); resize(width(), _newItemHeight + st::noContactsHeight); } else { @@ -1074,33 +1708,33 @@ void ContactsInner::refresh() { update(); } -ChatData *ContactsInner::chat() const { +ChatData *ContactsBox::Inner::chat() const { return _chat; } -ChannelData *ContactsInner::channel() const { +ChannelData *ContactsBox::Inner::channel() const { return _channel; } -MembersFilter ContactsInner::membersFilter() const { +MembersFilter ContactsBox::Inner::membersFilter() const { return _membersFilter; } -UserData *ContactsInner::bot() const { +UserData *ContactsBox::Inner::bot() const { return _bot; } -bool ContactsInner::sharingBotGame() const { +bool ContactsBox::Inner::sharingBotGame() const { return (_bot && _bot->botInfo) ? !_bot->botInfo->shareGameShortName.isEmpty() : false; } -CreatingGroupType ContactsInner::creating() const { +CreatingGroupType ContactsBox::Inner::creating() const { return _creating; } -ContactsInner::~ContactsInner() { - for (ContactsData::iterator i = _contactsData.begin(), e = _contactsData.end(); i != e; ++i) { - delete *i; +ContactsBox::Inner::~Inner() { + for (auto contactData : base::take(_contactsData)) { + delete contactData; } if (_bot) { if (auto &info = _bot->botInfo) { @@ -1110,12 +1744,12 @@ ContactsInner::~ContactsInner() { } } -void ContactsInner::resizeEvent(QResizeEvent *e) { +void ContactsBox::Inner::resizeEvent(QResizeEvent *e) { _addContactLnk.move((width() - _addContactLnk.width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2); _allAdmins.moveToLeft(st::contactsPadding.left(), st::contactsNewItemTop); } -void ContactsInner::selectSkip(int32 dir) { +void ContactsBox::Inner::selectSkip(int32 dir) { _time = unixtime(); _mouseSel = false; if (_filter.isEmpty()) { @@ -1133,7 +1767,7 @@ void ContactsInner::selectSkip(int32 dir) { } cur += dir; if (cur <= 0) { - _newItemSel = (_chat && _membersFilter == MembersFilterAdmins) ? false : (_newItemHeight ? true : false); + _newItemSel = (_chat && _membersFilter == MembersFilter::Admins) ? false : (_newItemHeight ? true : false); _sel = (!_newItemHeight && !_contacts->isEmpty()) ? *_contacts->cbegin() : nullptr; _byUsernameSel = (!_newItemHeight && _contacts->isEmpty() && !_byUsername.isEmpty()) ? 0 : -1; } else if (cur >= _contacts->size() + (_newItemHeight ? 1 : 0)) { @@ -1160,33 +1794,33 @@ void ContactsInner::selectSkip(int32 dir) { _byUsernameSel = -1; } if (dir > 0) { - for (auto i = _contacts->cfind(_sel), end = _contacts->cend(); i != end && contactData(*i)->inchat; ++i) { + for (auto i = _contacts->cfind(_sel), end = _contacts->cend(); i != end && contactData(*i)->disabledChecked; ++i) { _sel = *i; } - if (_sel && contactData(_sel)->inchat) { + if (_sel && contactData(_sel)->disabledChecked) { _sel = nullptr; } if (!_sel) { if (!_byUsername.isEmpty()) { if (_byUsernameSel < 0) _byUsernameSel = 0; - for (; _byUsernameSel < _byUsername.size() && d_byUsername[_byUsernameSel]->inchat;) { + for (; _byUsernameSel < _byUsername.size() && d_byUsername[_byUsernameSel]->disabledChecked;) { ++_byUsernameSel; } if (_byUsernameSel == _byUsername.size()) _byUsernameSel = -1; } } } else { - while (_byUsernameSel >= 0 && d_byUsername[_byUsernameSel]->inchat) { + while (_byUsernameSel >= 0 && d_byUsername[_byUsernameSel]->disabledChecked) { --_byUsernameSel; } if (_byUsernameSel < 0) { if (!_contacts->isEmpty()) { if (!_newItemSel && !_sel) _sel = *(_contacts->cend() - 1); if (_sel) { - for (auto i = _contacts->cfind(_sel), b = _contacts->cbegin(); i != b && contactData(*i)->inchat; --i) { + for (auto i = _contacts->cfind(_sel), b = _contacts->cbegin(); i != b && contactData(*i)->disabledChecked; --i) { _sel = *i; } - if (contactData(_sel)->inchat) { + if (contactData(_sel)->disabledChecked) { _sel = nullptr; } } @@ -1215,27 +1849,27 @@ void ContactsInner::selectSkip(int32 dir) { _byUsernameSel = -1; } if (dir > 0) { - while (_filteredSel >= 0 && _filteredSel < _filtered.size() && contactData(_filtered[_filteredSel])->inchat) { + while (_filteredSel >= 0 && _filteredSel < _filtered.size() && contactData(_filtered[_filteredSel])->disabledChecked) { ++_filteredSel; } if (_filteredSel < 0 || _filteredSel >= _filtered.size()) { _filteredSel = -1; if (!_byUsernameFiltered.isEmpty()) { if (_byUsernameSel < 0) _byUsernameSel = 0; - for (; _byUsernameSel < _byUsernameFiltered.size() && d_byUsernameFiltered[_byUsernameSel]->inchat;) { + for (; _byUsernameSel < _byUsernameFiltered.size() && d_byUsernameFiltered[_byUsernameSel]->disabledChecked;) { ++_byUsernameSel; } if (_byUsernameSel == _byUsernameFiltered.size()) _byUsernameSel = -1; } } } else { - while (_byUsernameSel >= 0 && d_byUsernameFiltered[_byUsernameSel]->inchat) { + while (_byUsernameSel >= 0 && d_byUsernameFiltered[_byUsernameSel]->disabledChecked) { --_byUsernameSel; } if (_byUsernameSel < 0) { if (!_filtered.isEmpty()) { if (_filteredSel < 0) _filteredSel = _filtered.size() - 1; - for (; _filteredSel >= 0 && contactData(_filtered[_filteredSel])->inchat;) { + for (; _filteredSel >= 0 && contactData(_filtered[_filteredSel])->disabledChecked;) { --_filteredSel; } } @@ -1251,1126 +1885,58 @@ void ContactsInner::selectSkip(int32 dir) { update(); } -void ContactsInner::selectSkipPage(int32 h, int32 dir) { +void ContactsBox::Inner::selectSkipPage(int32 h, int32 dir) { int32 points = h / _rowHeight; if (!points) return; selectSkip(points * dir); } -QVector ContactsInner::selected() { +QVector ContactsBox::Inner::selected() { QVector result; + if (!usingMultiSelect()) { + return result; + } + for_const (auto row, *_contacts) { if (_checkedContacts.contains(row->history()->peer)) { contactData(row); // fill _contactsData } } result.reserve(_contactsData.size()); - for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) { - if (i.value()->check && i.key()->isUser()) { + for (auto i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) { + if (i.value()->checkbox->checked() && i.key()->isUser()) { result.push_back(i.key()->asUser()); } } - for (int32 i = 0, l = _byUsername.size(); i < l; ++i) { - if (d_byUsername[i]->check && _byUsername[i]->isUser()) { + for (int i = 0, l = _byUsername.size(); i < l; ++i) { + if (d_byUsername[i]->checkbox->checked() && _byUsername[i]->isUser()) { result.push_back(_byUsername[i]->asUser()); } } return result; } -QVector ContactsInner::selectedInputs() { +QVector ContactsBox::Inner::selectedInputs() { QVector result; + if (!usingMultiSelect()) { + return result; + } + for_const (auto row, *_contacts) { if (_checkedContacts.contains(row->history()->peer)) { contactData(row); // fill _contactsData } } result.reserve(_contactsData.size()); - for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) { - if (i.value()->check && i.key()->isUser()) { + for (auto i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) { + if (i.value()->checkbox->checked() && i.key()->isUser()) { result.push_back(i.key()->asUser()->inputUser); } } - for (int32 i = 0, l = _byUsername.size(); i < l; ++i) { - if (d_byUsername[i]->check && _byUsername[i]->isUser()) { + for (int i = 0, l = _byUsername.size(); i < l; ++i) { + if (d_byUsername[i]->checkbox->checked() && _byUsername[i]->isUser()) { result.push_back(_byUsername[i]->asUser()->inputUser); } } return result; } - -PeerData *ContactsInner::selectedUser() { - for_const (auto row, *_contacts) { - if (_checkedContacts.contains(row->history()->peer)) { - contactData(row); // fill _contactsData - } - } - for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) { - if (i.value()->check) { - return i.key(); - } - } - for (int32 i = 0, l = _byUsername.size(); i < l; ++i) { - if (d_byUsername[i]->check) { - return _byUsername[i]; - } - } - return 0; -} - -ContactsBox::ContactsBox() : ItemListBox(st::contactsScroll) -, _inner(CreatingGroupNone) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) -, _next(this, lang(lng_create_group_next), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) -, _bottomShadow(0) -, _saveRequestId(0) { - init(); -} - -ContactsBox::ContactsBox(const QString &name, const QImage &photo) : ItemListBox(st::boxScroll) -, _inner(CreatingGroupGroup) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) -, _next(this, lang(lng_create_group_create), st::defaultBoxButton) -, _cancel(this, lang(lng_create_group_back), st::cancelBoxButton) -, _topShadow(this) -, _bottomShadow(0) -, _saveRequestId(0) -, _creationName(name) -, _creationPhoto(photo) { - init(); -} - -ContactsBox::ContactsBox(ChannelData *channel) : ItemListBox(st::boxScroll) -, _inner(channel, MembersFilterRecent, MembersAlreadyIn()) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) -, _next(this, lang(lng_participant_invite), st::defaultBoxButton) -, _cancel(this, lang(lng_create_group_skip), st::cancelBoxButton) -, _topShadow(this) -, _bottomShadow(0) -, _saveRequestId(0) { - init(); -} - -ContactsBox::ContactsBox(ChannelData *channel, MembersFilter filter, const MembersAlreadyIn &already) : ItemListBox((filter == MembersFilterAdmins) ? st::contactsScroll : st::boxScroll) -, _inner(channel, filter, already) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) -, _next(this, lang(lng_participant_invite), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) -, _bottomShadow(0) -, _saveRequestId(0) { - init(); -} - -ContactsBox::ContactsBox(ChatData *chat, MembersFilter filter) : ItemListBox(st::boxScroll) -, _inner(chat, filter) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) -, _next(this, lang((filter == MembersFilterAdmins) ? lng_settings_save : lng_participant_invite), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) -, _bottomShadow(0) -, _saveRequestId(0) { - init(); -} - -ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::contactsScroll) -, _inner(bot) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) -, _next(this, lang(lng_create_group_next), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) -, _bottomShadow(0) -, _saveRequestId(0) { - init(); -} - -void ContactsBox::init() { - bool inviting = (_inner.creating() == CreatingGroupGroup) || (_inner.channel() && _inner.membersFilter() == MembersFilterRecent) || _inner.chat(); - int32 topSkip = st::boxTitleHeight + _filter.height(); - int32 bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip; - ItemListBox::init(&_inner, bottomSkip, topSkip); - - connect(&_inner, SIGNAL(chosenChanged()), this, SLOT(onChosenChanged())); - connect(&_inner, SIGNAL(addRequested()), App::wnd(), SLOT(onShowAddContact())); - if (_inner.channel() && _inner.membersFilter() == MembersFilterAdmins) { - _next.hide(); - _cancel.hide(); - } else if (_inner.chat() && _inner.membersFilter() == MembersFilterAdmins) { - connect(&_next, SIGNAL(clicked()), this, SLOT(onSaveAdmins())); - _bottomShadow = new ScrollableBoxShadow(this); - } else if (_inner.chat() || _inner.channel()) { - connect(&_next, SIGNAL(clicked()), this, SLOT(onInvite())); - _bottomShadow = new ScrollableBoxShadow(this); - } else if (_inner.creating() != CreatingGroupNone) { - connect(&_next, SIGNAL(clicked()), this, SLOT(onCreate())); - _bottomShadow = new ScrollableBoxShadow(this); - } else { - _next.hide(); - _cancel.hide(); - } - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); - connect(&_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); - connect(&_filter, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel())); - connect(&_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); - connect(&_inner, SIGNAL(selectAllQuery()), &_filter, SLOT(selectAll())); - connect(&_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername())); - connect(&_inner, SIGNAL(adminAdded()), this, SIGNAL(adminAdded())); - - _filterCancel.setAttribute(Qt::WA_OpaquePaintEvent); - - _searchTimer.setSingleShot(true); - connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername())); - - prepare(); -} - -bool ContactsBox::onSearchByUsername(bool searchCache) { - QString q = _filter.getLastText().trimmed(); - if (q.isEmpty()) { - if (_peopleRequest) { - _peopleRequest = 0; - } - return true; - } - if (q.size() >= MinUsernameLength) { - if (searchCache) { - PeopleCache::const_iterator i = _peopleCache.constFind(q); - if (i != _peopleCache.cend()) { - _peopleQuery = q; - _peopleRequest = 0; - peopleReceived(i.value(), 0); - return true; - } - } else if (_peopleQuery != q) { - _peopleQuery = q; - _peopleFull = false; - _peopleRequest = MTP::send(MTPcontacts_Search(MTP_string(_peopleQuery), MTP_int(SearchPeopleLimit)), rpcDone(&ContactsBox::peopleReceived), rpcFail(&ContactsBox::peopleFailed)); - _peopleQueries.insert(_peopleRequest, _peopleQuery); - } - } - return false; -} - -void ContactsBox::onNeedSearchByUsername() { - if (!onSearchByUsername(true)) { - _searchTimer.start(AutoSearchTimeout); - } -} - -void ContactsBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId req) { - QString q = _peopleQuery; - - PeopleQueries::iterator i = _peopleQueries.find(req); - if (i != _peopleQueries.cend()) { - q = i.value(); - _peopleCache[q] = result; - _peopleQueries.erase(i); - } - - if (_peopleRequest == req) { - switch (result.type()) { - case mtpc_contacts_found: { - App::feedUsers(result.c_contacts_found().vusers); - App::feedChats(result.c_contacts_found().vchats); - _inner.peopleReceived(q, result.c_contacts_found().vresults.c_vector().v); - } break; - } - - _peopleRequest = 0; - _inner.updateSel(); - onScroll(); - } -} - -bool ContactsBox::peopleFailed(const RPCError &error, mtpRequestId req) { - if (MTP::isDefaultHandledError(error)) return false; - - if (_peopleRequest == req) { - _peopleRequest = 0; - _peopleFull = true; - } - return true; -} - -void ContactsBox::showAll() { - _filter.show(); - if (_filter.getLastText().isEmpty()) { - _filterCancel.hide(); - } else { - _filterCancel.show(); - } - if (_inner.channel() && _inner.membersFilter() == MembersFilterAdmins) { - _next.hide(); - _cancel.hide(); - } else if (_inner.chat() || _inner.channel()) { - _next.show(); - _cancel.show(); - } else if (_inner.creating() != CreatingGroupNone) { - _next.show(); - _cancel.show(); - } else { - _next.hide(); - _cancel.hide(); - } - _topShadow.show(); - if (_bottomShadow) _bottomShadow->show(); - ItemListBox::showAll(); -} - -void ContactsBox::doSetInnerFocus() { - _filter.setFocus(); -} - -void ContactsBox::onSubmit() { - _inner.chooseParticipant(); -} - -void ContactsBox::keyPressEvent(QKeyEvent *e) { - if (_filter.hasFocus()) { - if (e->key() == Qt::Key_Down) { - _inner.selectSkip(1); - } else if (e->key() == Qt::Key_Up) { - _inner.selectSkip(-1); - } else if (e->key() == Qt::Key_PageDown) { - _inner.selectSkipPage(scrollArea()->height(), 1); - } else if (e->key() == Qt::Key_PageUp) { - _inner.selectSkipPage(scrollArea()->height(), -1); - } else { - ItemListBox::keyPressEvent(e); - } - } else { - ItemListBox::keyPressEvent(e); - } -} - -void ContactsBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - bool addingAdmin = _inner.channel() && _inner.membersFilter() == MembersFilterAdmins; - if (_inner.chat() && _inner.membersFilter() == MembersFilterAdmins) { - paintTitle(p, lang(lng_channel_admins)); - } else if (_inner.chat() || _inner.creating() != CreatingGroupNone) { - QString title(lang(addingAdmin ? lng_channel_add_admin : lng_profile_add_participant)); - QString additional((addingAdmin || (_inner.channel() && !_inner.channel()->isMegagroup())) ? QString() : QString("%1 / %2").arg(_inner.selectedCount()).arg(Global::MegagroupSizeMax())); - paintTitle(p, title, additional); - } else if (_inner.sharingBotGame()) { - paintTitle(p, lang(lng_bot_choose_chat)); - } else if (_inner.bot()) { - paintTitle(p, lang(lng_bot_choose_group)); - } else { - paintTitle(p, lang(lng_contacts_header)); - } -} - -void ContactsBox::resizeEvent(QResizeEvent *e) { - ItemListBox::resizeEvent(e); - _filter.resize(width(), _filter.height()); - _filter.moveToLeft(0, st::boxTitleHeight); - _filterCancel.moveToRight(0, st::boxTitleHeight); - _inner.resize(width(), _inner.height()); - _next.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _next.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _next.width() + st::boxButtonPadding.left(), _next.y()); - _topShadow.setGeometry(0, st::boxTitleHeight + _filter.height(), width(), st::lineWidth); - if (_bottomShadow) _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _next.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); -} - -void ContactsBox::closePressed() { - if (_inner.channel() && !_inner.hasAlreadyMembersInChannel()) { - Ui::showPeerHistory(_inner.channel(), ShowAtTheEndMsgId); - } -} - -void ContactsBox::onFilterCancel() { - _filter.setText(QString()); -} - -void ContactsBox::onFilterUpdate() { - scrollArea()->scrollToY(0); - if (_filter.getLastText().isEmpty()) { - _filterCancel.hide(); - } else { - _filterCancel.show(); - } - _inner.updateFilter(_filter.getLastText()); -} - -void ContactsBox::onChosenChanged() { - update(); -} - -void ContactsBox::onInvite() { - QVector users(_inner.selected()); - if (users.isEmpty()) { - _filter.setFocus(); - _filter.showError(); - return; - } - - App::main()->addParticipants(_inner.chat() ? (PeerData*)_inner.chat() : _inner.channel(), users); - if (_inner.chat()) { - Ui::hideLayer(); - Ui::showPeerHistory(_inner.chat(), ShowAtTheEndMsgId); - } else { - onClose(); - } -} - -void ContactsBox::onCreate() { - if (_saveRequestId) return; - - MTPVector users(MTP_vector(_inner.selectedInputs())); - const auto &v(users.c_vector().v); - if (v.isEmpty() || (v.size() == 1 && v.at(0).type() == mtpc_inputUserSelf)) { - _filter.setFocus(); - _filter.showError(); - return; - } - _saveRequestId = MTP::send(MTPmessages_CreateChat(MTP_vector(v), MTP_string(_creationName)), rpcDone(&ContactsBox::creationDone), rpcFail(&ContactsBox::creationFail)); -} - -void ContactsBox::onSaveAdmins() { - if (_saveRequestId) return; - - _inner.saving(true); - _saveRequestId = MTP::send(MTPmessages_ToggleChatAdmins(_inner.chat()->inputChat, MTP_bool(!_inner.allAdmins())), rpcDone(&ContactsBox::saveAdminsDone), rpcFail(&ContactsBox::saveAdminsFail)); -} - -void ContactsBox::saveAdminsDone(const MTPUpdates &result) { - App::main()->sentUpdatesReceived(result); - saveSelectedAdmins(); -} - -void ContactsBox::saveSelectedAdmins() { - if (_inner.allAdmins() && !_inner.chat()->participants.isEmpty()) { - onClose(); - } else { - _saveRequestId = MTP::send(MTPmessages_GetFullChat(_inner.chat()->inputChat), rpcDone(&ContactsBox::getAdminsDone), rpcFail(&ContactsBox::saveAdminsFail)); - } -} - -void ContactsBox::getAdminsDone(const MTPmessages_ChatFull &result) { - App::api()->processFullPeer(_inner.chat(), result); - if (_inner.allAdmins()) { - onClose(); - return; - } - ChatData::Admins curadmins = _inner.chat()->admins; - QVector newadmins = _inner.selected(), appoint; - if (!newadmins.isEmpty()) { - appoint.reserve(newadmins.size()); - for (int32 i = 0, l = newadmins.size(); i < l; ++i) { - ChatData::Admins::iterator c = curadmins.find(newadmins.at(i)); - if (c == curadmins.cend()) { - if (newadmins.at(i)->id != peerFromUser(_inner.chat()->creator)) { - appoint.push_back(newadmins.at(i)); - } - } else { - curadmins.erase(c); - } - } - } - _saveRequestId = 0; - - for_const (UserData *user, curadmins) { - MTP::send(MTPmessages_EditChatAdmin(_inner.chat()->inputChat, user->inputUser, MTP_boolFalse()), rpcDone(&ContactsBox::removeAdminDone, user), rpcFail(&ContactsBox::editAdminFail), 0, 10); - } - for_const (UserData *user, appoint) { - MTP::send(MTPmessages_EditChatAdmin(_inner.chat()->inputChat, user->inputUser, MTP_boolTrue()), rpcDone(&ContactsBox::setAdminDone, user), rpcFail(&ContactsBox::editAdminFail), 0, 10); - } - MTP::sendAnything(); - - _saveRequestId = curadmins.size() + appoint.size(); - if (!_saveRequestId) { - onClose(); - } -} - -void ContactsBox::setAdminDone(UserData *user, const MTPBool &result) { - if (mtpIsTrue(result)) { - if (_inner.chat()->noParticipantInfo()) { - App::api()->requestFullPeer(_inner.chat()); - } else { - _inner.chat()->admins.insert(user); - } - } - --_saveRequestId; - if (!_saveRequestId) { - emit App::main()->peerUpdated(_inner.chat()); - onClose(); - } -} - -void ContactsBox::removeAdminDone(UserData *user, const MTPBool &result) { - if (mtpIsTrue(result)) { - _inner.chat()->admins.remove(user); - } - --_saveRequestId; - if (!_saveRequestId) { - emit App::main()->peerUpdated(_inner.chat()); - onClose(); - } -} - -bool ContactsBox::saveAdminsFail(const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return true; - _saveRequestId = 0; - _inner.saving(false); - if (error.type() == qstr("CHAT_NOT_MODIFIED")) { - saveSelectedAdmins(); - } - return false; -} - -bool ContactsBox::editAdminFail(const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return true; - --_saveRequestId; - _inner.chat()->invalidateParticipants(); - if (!_saveRequestId) { - if (error.type() == qstr("USER_RESTRICTED")) { - Ui::showLayer(new InformBox(lang(lng_cant_do_this))); - return true; - } - onClose(); - } - return false; -} - -void ContactsBox::onScroll() { - _inner.loadProfilePhotos(scrollArea()->scrollTop()); -} - -void ContactsBox::creationDone(const MTPUpdates &updates) { - Ui::hideLayer(); - - App::main()->sentUpdatesReceived(updates); - const QVector *v = 0; - switch (updates.type()) { - case mtpc_updates: v = &updates.c_updates().vchats.c_vector().v; break; - case mtpc_updatesCombined: v = &updates.c_updatesCombined().vchats.c_vector().v; break; - default: LOG(("API Error: unexpected update cons %1 (ContactsBox::creationDone)").arg(updates.type())); break; - } - - PeerData *peer = 0; - if (v && !v->isEmpty() && v->front().type() == mtpc_chat) { - peer = App::chat(v->front().c_chat().vid.v); - if (peer) { - if (!_creationPhoto.isNull()) { - App::app()->uploadProfilePhoto(_creationPhoto, peer->id); - } - Ui::showPeerHistory(peer, ShowAtUnreadMsgId); - } - } else { - LOG(("API Error: chat not found in updates (ContactsBox::creationDone)")); - } -} - -bool ContactsBox::creationFail(const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - - _saveRequestId = 0; - if (error.type() == "NO_CHAT_TITLE") { - onClose(); - return true; - } else if (error.type() == "USERS_TOO_FEW") { - _filter.setFocus(); - _filter.showError(); - return true; - } else if (error.type() == "PEER_FLOOD") { - Ui::showLayer(new InformBox(cantInviteError()), KeepOtherLayers); - return true; - } else if (error.type() == qstr("USER_RESTRICTED")) { - Ui::showLayer(new InformBox(lang(lng_cant_do_this))); - return true; - } - return false; -} - -MembersInner::MembersInner(ChannelData *channel, MembersFilter filter) : TWidget() -, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) -, _newItemHeight((channel->amCreator() && (channel->membersCount() < (channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilterAdmins)) ? st::contactsNewItemHeight : 0) -, _newItemSel(false) -, _channel(channel) -, _filter(filter) -, _kickText(lang(lng_profile_kick)) -, _time(0) -, _kickWidth(st::normalFont->width(_kickText)) -, _sel(-1) -, _kickSel(-1) -, _kickDown(-1) -, _mouseSel(false) -, _kickConfirm(0) -, _kickRequestId(0) -, _kickBox(0) -, _loading(true) -, _loadingRequestId(0) -, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right()) -, _about(_aboutWidth) -, _aboutHeight(0) { - subscribe(FileDownload::ImageLoaded(), [this] { update(); }); - - connect(App::main(), SIGNAL(peerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&))); - connect(App::main(), SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(peerUpdated(PeerData*))); - - refresh(); - - load(); -} - -void MembersInner::load() { - if (!_loadingRequestId) { - _loadingRequestId = MTP::send(MTPchannels_GetParticipants(_channel->inputChannel, (_filter == MembersFilterRecent) ? MTP_channelParticipantsRecent() : MTP_channelParticipantsAdmins(), MTP_int(0), MTP_int(Global::ChatSizeMax())), rpcDone(&MembersInner::membersReceived), rpcFail(&MembersInner::membersFailed)); - } -} - -void MembersInner::paintEvent(QPaintEvent *e) { - QRect r(e->rect()); - Painter p(this); - - _time = unixtime(); - p.fillRect(r, st::white->b); - - int32 yFrom = r.y() - st::membersPadding.top(), yTo = r.y() + r.height() - st::membersPadding.top(); - p.translate(0, st::membersPadding.top()); - if (_rows.isEmpty()) { - p.setFont(st::noContactsFont->f); - p.setPen(st::noContactsColor->p); - p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); - } else { - if (_newItemHeight) { - p.fillRect(0, 0, width(), _newItemHeight, (_newItemSel ? st::contactsBgOver : st::white)->b); - p.drawSpriteLeft(st::contactsNewItemIconPosition.x(), st::contactsNewItemIconPosition.y(), width(), st::contactsNewItemIcon); - p.setFont(st::contactsNameFont); - p.setPen(st::contactsNewItemFg); - p.drawTextLeft(st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(_filter == MembersFilterAdmins ? lng_channel_add_admins : lng_channel_add_members)); - - yFrom -= _newItemHeight; - yTo -= _newItemHeight; - p.translate(0, _newItemHeight); - } - int32 from = floorclamp(yFrom, _rowHeight, 0, _rows.size()); - int32 to = ceilclamp(yTo, _rowHeight, 0, _rows.size()); - p.translate(0, from * _rowHeight); - for (; from < to; ++from) { - bool sel = (from == _sel); - bool kickSel = (from == _kickSel && (_kickDown < 0 || from == _kickDown)); - bool kickDown = kickSel && (from == _kickDown); - paintDialog(p, _rows[from], data(from), sel, kickSel, kickDown); - p.translate(0, _rowHeight); - } - if (to == _rows.size() && _filter == MembersFilterRecent && (_rows.size() < _channel->membersCount() || _rows.size() >= Global::ChatSizeMax())) { - p.setPen(st::stickersReorderFg); - _about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); - } - } -} - -void MembersInner::enterEvent(QEvent *e) { - setMouseTracking(true); -} - -void MembersInner::leaveEvent(QEvent *e) { - _mouseSel = false; - setMouseTracking(false); - if (_sel >= 0) { - clearSel(); - } -} - -void MembersInner::mouseMoveEvent(QMouseEvent *e) { - _mouseSel = true; - _lastMousePos = e->globalPos(); - updateSel(); -} - -void MembersInner::mousePressEvent(QMouseEvent *e) { - _mouseSel = true; - _lastMousePos = e->globalPos(); - updateSel(); - if (e->button() == Qt::LeftButton && _kickSel < 0) { - chooseParticipant(); - } - _kickDown = _kickSel; - update(); -} - -void MembersInner::mouseReleaseEvent(QMouseEvent *e) { - _mouseSel = true; - _lastMousePos = e->globalPos(); - updateSel(); - if (_kickDown >= 0 && _kickDown == _kickSel && !_kickRequestId) { - _kickConfirm = _rows.at(_kickSel); - if (_kickBox) _kickBox->deleteLater(); - _kickBox = new ConfirmBox((_filter == MembersFilterRecent ? (_channel->isMegagroup() ? lng_profile_sure_kick : lng_profile_sure_kick_channel) : lng_profile_sure_kick_admin)(lt_user, _kickConfirm->firstName)); - connect(_kickBox, SIGNAL(confirmed()), this, SLOT(onKickConfirm())); - connect(_kickBox, SIGNAL(destroyed(QObject*)), this, SLOT(onKickBoxDestroyed(QObject*))); - Ui::showLayer(_kickBox, KeepOtherLayers); - } - _kickDown = -1; -} - -void MembersInner::onKickBoxDestroyed(QObject *obj) { - if (_kickBox == obj) { - _kickBox = 0; - } -} - -void MembersInner::onKickConfirm() { - if (_filter == MembersFilterRecent) { - _kickRequestId = MTP::send(MTPchannels_KickFromChannel(_channel->inputChannel, _kickConfirm->inputUser, MTP_bool(true)), rpcDone(&MembersInner::kickDone), rpcFail(&MembersInner::kickFail)); - } else { - _kickRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _kickConfirm->inputUser, MTP_channelRoleEmpty()), rpcDone(&MembersInner::kickAdminDone), rpcFail(&MembersInner::kickFail)); - } -} - -void MembersInner::paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown) { - UserData *user = peer->asUser(); - - p.fillRect(0, 0, width(), _rowHeight, (sel ? st::contactsBgOver : st::white)->b); - peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width()); - - p.setPen(st::black); - - int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); - int32 namew = width() - namex - st::contactsPadding.right() - (data->canKick ? (_kickWidth + st::contactsCheckPosition.x() * 2) : 0); - if (peer->isVerified()) { - auto icon = &st::dialogsVerifiedIcon; - namew -= icon->width(); - icon->paint(p, namex + qMin(data->name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width()); - } - data->name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width()); - - if (data->canKick) { - p.setFont((kickSel ? st::linkOverFont : st::linkFont)->f); - if (kickDown) { - p.setPen(st::btnDefLink.downColor->p); - } else { - p.setPen(st::btnDefLink.color->p); - } - p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), _kickText, _kickWidth); - } - - p.setFont(st::contactsStatusFont->f); - p.setPen(data->onlineColor ? st::contactsStatusFgOnline : (sel ? st::contactsStatusFgOver : st::contactsStatusFg)); - p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->online); -} - -void MembersInner::selectSkip(int32 dir) { - _time = unixtime(); - _mouseSel = false; - - int cur = -1; - if (_newItemHeight && _newItemSel) { - cur = 0; - } else if (_sel >= 0) { - cur = _sel + (_newItemHeight ? 1 : 0); - } - cur += dir; - if (cur <= 0) { - _newItemSel = _newItemHeight ? true : false; - _sel = (_newItemSel || _rows.isEmpty()) ? -1 : 0; - } else if (cur >= _rows.size() + (_newItemHeight ? 1 : 0)) { - _sel = -1; - } else { - _sel = cur - (_newItemHeight ? 1 : 0); - } - if (dir > 0) { - if (_sel < 0 || _sel >= _rows.size()) { - _sel = -1; - } - } else { - if (!_rows.isEmpty()) { - if (_sel < 0 && !_newItemSel) _sel = _rows.size() - 1; - } - } - if (_newItemSel) { - emit mustScrollTo(0, _newItemHeight); - } else if (_sel >= 0) { - emit mustScrollTo(_newItemHeight + _sel * _rowHeight, _newItemHeight + (_sel + 1) * _rowHeight); - } - - update(); -} - -void MembersInner::selectSkipPage(int32 h, int32 dir) { - int32 points = h / _rowHeight; - if (!points) return; - selectSkip(points * dir); -} - -void MembersInner::loadProfilePhotos(int32 yFrom) { - int32 yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5; - MTP::clearLoaderPriorities(); - - if (yTo < 0) return; - if (yFrom < 0) yFrom = 0; - - if (!_rows.isEmpty()) { - int32 from = (yFrom - _newItemHeight) / _rowHeight; - if (from < 0) from = 0; - if (from < _rows.size()) { - int32 to = ((yTo - _newItemHeight) / _rowHeight) + 1; - if (to > _rows.size()) to = _rows.size(); - - for (; from < to; ++from) { - _rows[from]->loadUserpic(); - } - } - } -} - -void MembersInner::chooseParticipant() { - if (_newItemSel) { - emit addRequested(); - return; - } - if (_sel < 0 || _sel >= _rows.size()) return; - if (PeerData *peer = _rows[_sel]) { - Ui::hideLayer(); - Ui::showPeerProfile(peer); - } -} - -void MembersInner::refresh() { - if (_rows.isEmpty()) { - resize(width(), st::membersPadding.top() + st::noContactsHeight + st::membersPadding.bottom()); - _aboutHeight = 0; - } else { - _about.setText(st::boxTextFont, lng_channel_only_last_shown(lt_count, _rows.size())); - _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); - if (_filter != MembersFilterRecent || (_rows.size() >= _channel->membersCount() && _rows.size() < Global::ChatSizeMax())) { - _aboutHeight = 0; - } - resize(width(), st::membersPadding.top() + _newItemHeight + _rows.size() * _rowHeight + st::membersPadding.bottom() + _aboutHeight); - } - update(); -} - -ChannelData *MembersInner::channel() const { - return _channel; -} - -MembersFilter MembersInner::filter() const { - return _filter; -} - -MembersAlreadyIn MembersInner::already() const { - MembersAlreadyIn result; - for_const (auto peer, _rows) { - if (peer->isUser()) { - result.insert(peer->asUser()); - } - } - return result; -} - -void MembersInner::clearSel() { - updateSelectedRow(); - _newItemSel = false; - _sel = _kickSel = _kickDown = -1; - _lastMousePos = QCursor::pos(); - updateSel(); -} - -MembersInner::MemberData *MembersInner::data(int32 index) { - if (MemberData *result = _datas.at(index)) { - return result; - } - MemberData *result = _datas[index] = new MemberData(); - result->name.setText(st::contactsNameFont, _rows[index]->name, _textNameOptions); - int32 t = unixtime(); - result->online = App::onlineText(_rows[index], t);// lng_mediaview_date_time(lt_date, _dates[index].date().toString(qsl("dd.MM.yy")), lt_time, _dates[index].time().toString(cTimeFormat())); - result->onlineColor = App::onlineColorUse(_rows[index], t); - if (_filter == MembersFilterRecent) { - result->canKick = (_channel->amCreator() || _channel->amEditor() || _channel->amModerator()) ? (_roles[index] == MemberRoleNone) : false; - } else if (_filter == MembersFilterAdmins) { - result->canKick = _channel->amCreator() ? (_roles[index] == MemberRoleEditor || _roles[index] == MemberRoleModerator) : false; - } else { - result->canKick = false; - } - return result; -} - -void MembersInner::clear() { - for (int32 i = 0, l = _datas.size(); i < l; ++i) { - delete _datas.at(i); - } - _datas.clear(); - _rows.clear(); - _dates.clear(); - _roles.clear(); - if (_kickBox) _kickBox->deleteLater(); - clearSel(); -} - -MembersInner::~MembersInner() { - clear(); -} - -void MembersInner::updateSel() { - if (!_mouseSel) return; - - QPoint p(mapFromGlobal(_lastMousePos)); - p.setY(p.y() - st::membersPadding.top()); - bool in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePos)); - bool newItemSel = (in && p.y() >= 0 && p.y() < _newItemHeight); - int32 newSel = (in && !newItemSel && p.y() >= _newItemHeight && p.y() < _newItemHeight + _rows.size() * _rowHeight) ? ((p.y() - _newItemHeight) / _rowHeight) : -1; - int32 newKickSel = newSel; - if (newSel >= 0 && (!data(newSel)->canKick || !QRect(width() - _kickWidth - st::contactsPadding.right() - st::contactsCheckPosition.x(), _newItemHeight + newSel * _rowHeight + st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, _kickWidth, st::normalFont->height).contains(p))) { - newKickSel = -1; - } - if (newSel != _sel || newKickSel != _kickSel || newItemSel != _newItemSel) { - updateSelectedRow(); - _newItemSel = newItemSel; - _sel = newSel; - _kickSel = newKickSel; - updateSelectedRow(); - setCursor(_kickSel >= 0 ? style::cur_pointer : style::cur_default); - } -} - -void MembersInner::peerUpdated(PeerData *peer) { - update(); -} - -void MembersInner::updateSelectedRow() { - if (_newItemSel) { - update(0, st::membersPadding.top(), width(), _newItemHeight); - } - if (_sel >= 0) { - update(0, st::membersPadding.top() + _newItemHeight + _sel * _rowHeight, width(), _rowHeight); - } -} - -void MembersInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { - for (int32 i = 0, l = _rows.size(); i < l; ++i) { - if (_rows.at(i) == peer) { - if (_datas.at(i)) { - _datas.at(i)->name.setText(st::contactsNameFont, peer->name, _textNameOptions); - update(0, st::membersPadding.top() + i * _rowHeight, width(), _rowHeight); - } else { - break; - } - } - } -} - -void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req) { - clear(); - _loadingRequestId = 0; - - if (result.type() == mtpc_channels_channelParticipants) { - const auto &d(result.c_channels_channelParticipants()); - const auto &v(d.vparticipants.c_vector().v); - _rows.reserve(v.size()); - _datas.reserve(v.size()); - _dates.reserve(v.size()); - _roles.reserve(v.size()); - - if (_filter == MembersFilterRecent && _channel->membersCount() < d.vcount.v) { - _channel->setMembersCount(d.vcount.v); - if (App::main()) emit App::main()->peerUpdated(_channel); - } else if (_filter == MembersFilterAdmins && _channel->adminsCount() < d.vcount.v) { - _channel->setAdminsCount(d.vcount.v); - if (App::main()) emit App::main()->peerUpdated(_channel); - } - App::feedUsers(d.vusers); - - for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - int32 userId = 0, addedTime = 0; - MemberRole role = MemberRoleNone; - switch (i->type()) { - case mtpc_channelParticipant: - userId = i->c_channelParticipant().vuser_id.v; - addedTime = i->c_channelParticipant().vdate.v; - break; - case mtpc_channelParticipantSelf: - role = MemberRoleSelf; - userId = i->c_channelParticipantSelf().vuser_id.v; - addedTime = i->c_channelParticipantSelf().vdate.v; - break; - case mtpc_channelParticipantModerator: - role = MemberRoleModerator; - userId = i->c_channelParticipantModerator().vuser_id.v; - addedTime = i->c_channelParticipantModerator().vdate.v; - break; - case mtpc_channelParticipantEditor: - role = MemberRoleEditor; - userId = i->c_channelParticipantEditor().vuser_id.v; - addedTime = i->c_channelParticipantEditor().vdate.v; - break; - case mtpc_channelParticipantKicked: - userId = i->c_channelParticipantKicked().vuser_id.v; - addedTime = i->c_channelParticipantKicked().vdate.v; - role = MemberRoleKicked; - break; - case mtpc_channelParticipantCreator: - userId = i->c_channelParticipantCreator().vuser_id.v; - addedTime = _channel->date; - role = MemberRoleCreator; - break; - } - if (UserData *user = App::userLoaded(userId)) { - _rows.push_back(user); - _dates.push_back(date(addedTime)); - _roles.push_back(role); - _datas.push_back(0); - } - } - - // update admins if we got all of them - if (_filter == MembersFilterAdmins && _channel->isMegagroup() && _rows.size() < Global::ChatSizeMax()) { - _channel->mgInfo->lastAdmins.clear(); - for (int32 i = 0, l = _rows.size(); i != l; ++i) { - if (_roles.at(i) == MemberRoleCreator || _roles.at(i) == MemberRoleEditor) { - _channel->mgInfo->lastAdmins.insert(_rows.at(i)); - } - } - - Notify::peerUpdatedDelayed(_channel, Notify::PeerUpdate::Flag::AdminsChanged); - } - } - if (_rows.isEmpty()) { - _rows.push_back(App::self()); - _dates.push_back(date(MTP_int(_channel->date))); - _roles.push_back(MemberRoleSelf); - _datas.push_back(0); - } - - clearSel(); - _loading = false; - refresh(); - - emit loaded(); -} - -bool MembersInner::membersFailed(const RPCError &error, mtpRequestId req) { - if (MTP::isDefaultHandledError(error)) return false; - - Ui::hideLayer(); - return true; -} - -void MembersInner::kickDone(const MTPUpdates &result, mtpRequestId req) { - App::main()->sentUpdatesReceived(result); - - if (_kickRequestId != req) return; - removeKicked(); - if (_kickBox) _kickBox->onClose(); -} - -void MembersInner::kickAdminDone(const MTPUpdates &result, mtpRequestId req) { - if (_kickRequestId != req) return; - if (App::main()) App::main()->sentUpdatesReceived(result); - removeKicked(); - if (_kickBox) _kickBox->onClose(); -} - -bool MembersInner::kickFail(const RPCError &error, mtpRequestId req) { - if (MTP::isDefaultHandledError(error)) return false; - - if (_kickBox) _kickBox->onClose(); - load(); - return true; -} - -void MembersInner::removeKicked() { - _kickRequestId = 0; - int32 index = _rows.indexOf(_kickConfirm); - if (index >= 0) { - _rows.removeAt(index); - delete _datas.at(index); - _datas.removeAt(index); - _dates.removeAt(index); - _roles.removeAt(index); - clearSel(); - if (_filter == MembersFilterRecent && _channel->membersCount() > 1) { - _channel->setMembersCount(_channel->membersCount() - 1); - if (App::main()) emit App::main()->peerUpdated(_channel); - } else if (_filter == MembersFilterAdmins && _channel->adminsCount() > 1) { - _channel->setAdminsCount(_channel->adminsCount() - 1); - if (App::main()) emit App::main()->peerUpdated(_channel); - } - refresh(); - } - _kickConfirm = 0; -} - -MembersBox::MembersBox(ChannelData *channel, MembersFilter filter) : ItemListBox(st::boxScroll) -, _inner(channel, filter) -, _addBox(0) { - ItemListBox::init(&_inner); - - connect(&_inner, SIGNAL(addRequested()), this, SLOT(onAdd())); - - connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); - connect(&_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); - connect(&_inner, SIGNAL(loaded()), this, SLOT(onLoaded())); - - connect(&_loadTimer, SIGNAL(timeout()), &_inner, SLOT(load())); - - prepare(); -} - -void MembersBox::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Down) { - _inner.selectSkip(1); - } else if (e->key() == Qt::Key_Up) { - _inner.selectSkip(-1); - } else if (e->key() == Qt::Key_PageDown) { - _inner.selectSkipPage(scrollArea()->height(), 1); - } else if (e->key() == Qt::Key_PageUp) { - _inner.selectSkipPage(scrollArea()->height(), -1); - } else { - ItemListBox::keyPressEvent(e); - } -} - -void MembersBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - QString title(lang(_inner.filter() == MembersFilterRecent ? lng_channel_members : lng_channel_admins)); - paintTitle(p, title); -} - -void MembersBox::resizeEvent(QResizeEvent *e) { - ItemListBox::resizeEvent(e); - _inner.resize(width(), _inner.height()); -} - -void MembersBox::onScroll() { - _inner.loadProfilePhotos(scrollArea()->scrollTop()); -} - -void MembersBox::onAdd() { - if (_inner.filter() == MembersFilterRecent && _inner.channel()->membersCount() >= (_inner.channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) { - Ui::showLayer(new MaxInviteBox(_inner.channel()->inviteLink()), KeepOtherLayers); - return; - } - ContactsBox *box = new ContactsBox(_inner.channel(), _inner.filter(), _inner.already()); - if (_inner.filter() == MembersFilterRecent) { - Ui::showLayer(box); - } else { - _addBox = box; - connect(_addBox, SIGNAL(adminAdded()), this, SLOT(onAdminAdded())); - Ui::showLayer(_addBox, KeepOtherLayers); - } -} - -void MembersBox::onAdminAdded() { - if (!_addBox) return; - _addBox->onClose(); - _addBox = 0; - _loadTimer.start(ReloadChannelMembersTimeout); -} diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h index 646421224..0db72c0dd 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.h +++ b/Telegram/SourceFiles/boxes/contactsbox.h @@ -22,174 +22,27 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "abstractbox.h" #include "core/single_timer.h" +#include "ui/effects/round_image_checkbox.h" +#include "boxes/members_box.h" namespace Dialogs { class Row; class IndexedList; } // namespace Dialogs -enum MembersFilter { - MembersFilterRecent, - MembersFilterAdmins, -}; -using MembersAlreadyIn = OrderedSet; +namespace Ui { +class MultiSelect; +template +class WidgetSlideWrap; +} // namespace Ui QString cantInviteError(); -class ConfirmBox; -class ContactsInner : public TWidget, public RPCSender, private base::Subscriber { - Q_OBJECT - -private: - struct ContactData; - -public: - ContactsInner(CreatingGroupType creating = CreatingGroupNone); - ContactsInner(ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already); - ContactsInner(ChatData *chat, MembersFilter membersFilter); - ContactsInner(UserData *bot); - void init(); - void initList(); - - void paintDialog(Painter &p, PeerData *peer, ContactData *data, bool sel); - void updateFilter(QString filter = QString()); - - void selectSkip(int32 dir); - void selectSkipPage(int32 h, int32 dir); - - QVector selected(); - QVector selectedInputs(); - PeerData *selectedUser(); - bool allAdmins() const { - return _allAdmins.checked(); - } - - void loadProfilePhotos(int32 yFrom); - void chooseParticipant(); - void changeCheckState(Dialogs::Row *row); - void changeCheckState(ContactData *data, PeerData *peer); - - void peopleReceived(const QString &query, const QVector &people); - - void refresh(); - - ChatData *chat() const; - ChannelData *channel() const; - MembersFilter membersFilter() const; - UserData *bot() const; - CreatingGroupType creating() const; - - bool sharingBotGame() const; - - int32 selectedCount() const; - bool hasAlreadyMembersInChannel() const { - return !_already.isEmpty(); - } - - void saving(bool flag); - - ~ContactsInner(); - -signals: - void mustScrollTo(int ymin, int ymax); - void selectAllQuery(); - void searchByUsername(); - void chosenChanged(); - void adminAdded(); - void addRequested(); - -public slots: - void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow); - - void updateSel(); - void peerUpdated(PeerData *peer); - void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); - - void onAddBot(); - void onAddAdmin(); - void onNoAddAdminBox(QObject *obj); - - void onAllAdminsChanged(); - -protected: - void paintEvent(QPaintEvent *e) override; - void enterEvent(QEvent *e) override; - void leaveEvent(QEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - -private: - void updateSelectedRow(); - void addAdminDone(const MTPUpdates &result, mtpRequestId req); - bool addAdminFail(const RPCError &error, mtpRequestId req); - - template - void addDialogsToList(FilterCallback callback); - - int32 _rowHeight; - int _newItemHeight = 0; - bool _newItemSel = false; - - ChatData *_chat = nullptr; - ChannelData *_channel = nullptr; - MembersFilter _membersFilter = MembersFilterRecent; - UserData *_bot = nullptr; - CreatingGroupType _creating = CreatingGroupNone; - MembersAlreadyIn _already; - - Checkbox _allAdmins; - int32 _aboutWidth; - Text _aboutAllAdmins, _aboutAdmins; - - PeerData *_addToPeer = nullptr; - UserData *_addAdmin = nullptr; - mtpRequestId _addAdminRequestId = 0; - ConfirmBox *_addAdminBox = nullptr; - - int32 _time; - - std_::unique_ptr _customList; - Dialogs::IndexedList *_contacts = nullptr; - Dialogs::Row *_sel = nullptr; - QString _filter; - typedef QVector FilteredDialogs; - FilteredDialogs _filtered; - int _filteredSel = -1; - bool _mouseSel = false; - - int _selCount = 0; - - struct ContactData { - Text name; - QString online; - bool onlineColor; - bool inchat; - bool check; +inline Ui::RoundImageCheckbox::PaintRoundImage PaintUserpicCallback(PeerData *peer) { + return [peer](Painter &p, int x, int y, int outerWidth, int size) { + peer->paintUserpicLeft(p, size, x, y, outerWidth); }; - typedef QMap ContactsData; - ContactsData _contactsData; - typedef QMap CheckedContacts; - CheckedContacts _checkedContacts; - - ContactData *contactData(Dialogs::Row *row); - - bool _searching = false; - QString _lastQuery; - typedef QVector ByUsernameRows; - typedef QVector ByUsernameDatas; - ByUsernameRows _byUsername, _byUsernameFiltered; - ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas - ByUsernameDatas _byUsernameDatas; - int _byUsernameSel = -1; - - QPoint _lastMousePos; - LinkButton _addContactLnk; - - bool _saving = false; - bool _allAdminsChecked = false; - -}; +} class ContactsBox : public ItemListBox, public RPCSender { Q_OBJECT @@ -205,10 +58,7 @@ public: signals: void adminAdded(); -public slots: - void onFilterUpdate(); - void onFilterCancel(); - void onChosenChanged(); +private slots: void onScroll(); void onInvite(); @@ -231,15 +81,21 @@ protected: private: void init(); + int getTopScrollSkip() const; + void updateScrollSkips(); + void onFilterUpdate(const QString &filter); + void onPeerSelectedChanged(PeerData *peer, bool checked); + void addPeerToMultiSelect(PeerData *peer, bool skipAnimation = false); - ContactsInner _inner; - InputField _filter; - IconedButton _filterCancel; + class Inner; + ChildWidget _inner; + ChildWidget> _select; BoxButton _next, _cancel; MembersFilter _membersFilter; - ScrollableBoxShadow _topShadow, *_bottomShadow; + ScrollableBoxShadow _topShadow; + ScrollableBoxShadow *_bottomShadow = nullptr; void peopleReceived(const MTPcontacts_Found &result, mtpRequestId req); bool peopleFailed(const RPCError &error, mtpRequestId req); @@ -255,7 +111,7 @@ private: typedef QMap PeopleQueries; PeopleQueries _peopleQueries; - int32 _saveRequestId; + mtpRequestId _saveRequestId = 0; // saving admins void saveAdminsDone(const MTPUpdates &result); @@ -275,50 +131,75 @@ private: }; -class MembersInner : public TWidget, public RPCSender, private base::Subscriber { +// This class is hold in header because it requires Qt preprocessing. +class ContactsBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { Q_OBJECT -private: - struct MemberData; - public: - MembersInner(ChannelData *channel, MembersFilter filter); + Inner(QWidget *parent, CreatingGroupType creating = CreatingGroupNone); + Inner(QWidget *parent, ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already); + Inner(QWidget *parent, ChatData *chat, MembersFilter membersFilter); + Inner(QWidget *parent, UserData *bot); - void paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown); + void setPeerSelectedChangedCallback(base::lambda_unique callback); + void peerUnselected(PeerData *peer); + + void updateFilter(QString filter = QString()); + void updateSelection(); void selectSkip(int32 dir); void selectSkipPage(int32 h, int32 dir); + QVector selected(); + QVector selectedInputs(); + bool allAdmins() const { + return _allAdmins.checked(); + } + void setAllAdminsChangedCallback(base::lambda_unique allAdminsChangedCallback) { + _allAdminsChangedCallback = std_::move(allAdminsChangedCallback); + } + void loadProfilePhotos(int32 yFrom); void chooseParticipant(); + void peopleReceived(const QString &query, const QVector &people); + void refresh(); + ChatData *chat() const; ChannelData *channel() const; - MembersFilter filter() const; + MembersFilter membersFilter() const; + UserData *bot() const; + CreatingGroupType creating() const; - bool isLoaded() const { - return !_loading; + bool sharingBotGame() const; + + int32 selectedCount() const; + bool hasAlreadyMembersInChannel() const { + return !_already.isEmpty(); } - void clearSel(); - MembersAlreadyIn already() const; + void saving(bool flag); - ~MembersInner(); + ~Inner(); signals: void mustScrollTo(int ymin, int ymax); + void searchByUsername(); + void adminAdded(); void addRequested(); - void loaded(); -public slots: - void load(); +private slots: + void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow); - void updateSel(); void peerUpdated(PeerData *peer); void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); - void onKickConfirm(); - void onKickBoxDestroyed(QObject *obj); + + void onAddBot(); + void onAddAdmin(); + void onNoAddAdminBox(QObject *obj); + + void onAllAdminsChanged(); protected: void paintEvent(QPaintEvent *e) override; @@ -326,96 +207,103 @@ protected: void leaveEvent(QEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - -private: - void updateSelectedRow(); - MemberData *data(int32 index); - - void membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req); - bool membersFailed(const RPCError &error, mtpRequestId req); - - void kickDone(const MTPUpdates &result, mtpRequestId req); - void kickAdminDone(const MTPUpdates &result, mtpRequestId req); - bool kickFail(const RPCError &error, mtpRequestId req); - void removeKicked(); - - void clear(); - - int32 _rowHeight, _newItemHeight; - bool _newItemSel; - - ChannelData *_channel; - MembersFilter _filter; - - QString _kickText; - int32 _time, _kickWidth; - - int32 _sel, _kickSel, _kickDown; - bool _mouseSel; - - UserData *_kickConfirm; - mtpRequestId _kickRequestId; - - ConfirmBox *_kickBox; - - enum MemberRole { - MemberRoleNone, - MemberRoleSelf, - MemberRoleCreator, - MemberRoleEditor, - MemberRoleModerator, - MemberRoleKicked - }; - - struct MemberData { - Text name; - QString online; - bool onlineColor; - bool canKick; - }; - - bool _loading; - mtpRequestId _loadingRequestId; - typedef QVector MemberRows; - typedef QVector MemberDates; - typedef QVector MemberRoles; - typedef QVector MemberDatas; - MemberRows _rows; - MemberDates _dates; - MemberRoles _roles; - MemberDatas _datas; - - int32 _aboutWidth; - Text _about; - int32 _aboutHeight; - - QPoint _lastMousePos; - -}; - -class MembersBox : public ItemListBox { - Q_OBJECT - -public: - MembersBox(ChannelData *channel, MembersFilter filter); - -public slots: - void onScroll(); - - void onAdd(); - void onAdminAdded(); - -protected: - void keyPressEvent(QKeyEvent *e) override; - void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: - MembersInner _inner; + struct ContactData { + ContactData() = default; + ContactData(PeerData *peer, base::lambda_wrap updateCallback); - ContactsBox *_addBox; + std_::unique_ptr checkbox; + Text name; + QString statusText; + bool statusHasOnlineColor = false; + bool disabledChecked = false; + }; - SingleTimer _loadTimer; + void init(); + void initList(); + + void updateRowWithTop(int rowTop); + int getSelectedRowTop() const; + void updateSelectedRow(); + int getRowTopWithPeer(PeerData *peer) const; + void updateRowWithPeer(PeerData *peer); + void addAdminDone(const MTPUpdates &result, mtpRequestId req); + bool addAdminFail(const RPCError &error, mtpRequestId req); + + void paintDialog(Painter &p, uint64 ms, PeerData *peer, ContactData *data, bool sel); + void paintDisabledCheckUserpic(Painter &p, PeerData *peer, int x, int y, int outerWidth) const; + + void changeCheckState(Dialogs::Row *row); + void changeCheckState(ContactData *data, PeerData *peer); + enum class ChangeStateWay { + Default, + SkipCallback, + }; + void changePeerCheckState(ContactData *data, PeerData *peer, bool checked, ChangeStateWay useCallback = ChangeStateWay::Default); + + template + void addDialogsToList(FilterCallback callback); + + bool usingMultiSelect() const { + return (_chat != nullptr) || (_creating != CreatingGroupNone && (!_channel || _membersFilter != MembersFilter::Admins)); + } + + base::lambda_unique _peerSelectedChangedCallback; + + int32 _rowHeight; + int _newItemHeight = 0; + bool _newItemSel = false; + + ChatData *_chat = nullptr; + ChannelData *_channel = nullptr; + MembersFilter _membersFilter = MembersFilter::Recent; + UserData *_bot = nullptr; + CreatingGroupType _creating = CreatingGroupNone; + MembersAlreadyIn _already; + + Checkbox _allAdmins; + int32 _aboutWidth; + Text _aboutAllAdmins, _aboutAdmins; + base::lambda_unique _allAdminsChangedCallback; + + PeerData *_addToPeer = nullptr; + UserData *_addAdmin = nullptr; + mtpRequestId _addAdminRequestId = 0; + ConfirmBox *_addAdminBox = nullptr; + + int32 _time; + + std_::unique_ptr _customList; + Dialogs::IndexedList *_contacts = nullptr; + Dialogs::Row *_sel = nullptr; + QString _filter; + using FilteredDialogs = QVector; + FilteredDialogs _filtered; + int _filteredSel = -1; + bool _mouseSel = false; + + using ContactsData = QMap; + ContactsData _contactsData; + using CheckedContacts = OrderedSet; + CheckedContacts _checkedContacts; + + ContactData *contactData(Dialogs::Row *row); + + bool _searching = false; + QString _lastQuery; + using ByUsernameRows = QVector; + using ByUsernameDatas = QVector; + ByUsernameRows _byUsername, _byUsernameFiltered; + ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas + ByUsernameDatas _byUsernameDatas; + int _byUsernameSel = -1; + + QPoint _lastMousePos; + LinkButton _addContactLnk; + + bool _saving = false; + bool _allAdminsChecked = false; }; diff --git a/Telegram/SourceFiles/boxes/members_box.cpp b/Telegram/SourceFiles/boxes/members_box.cpp new file mode 100644 index 000000000..8290a5bec --- /dev/null +++ b/Telegram/SourceFiles/boxes/members_box.cpp @@ -0,0 +1,608 @@ +/* +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 "boxes/members_box.h" + +#include "styles/style_boxes.h" +#include "styles/style_dialogs.h" +#include "lang.h" +#include "mainwidget.h" +#include "mainwindow.h" +#include "boxes/contactsbox.h" +#include "boxes/confirmbox.h" +#include "observer_peer.h" + +MembersBox::MembersBox(ChannelData *channel, MembersFilter filter) : ItemListBox(st::boxScroll) +, _inner(this, channel, filter) { + ItemListBox::init(_inner); + + connect(_inner, SIGNAL(addRequested()), this, SLOT(onAdd())); + + connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); + + connect(&_loadTimer, SIGNAL(timeout()), _inner, SLOT(load())); + + prepare(); +} + +void MembersBox::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Down) { + _inner->selectSkip(1); + } else if (e->key() == Qt::Key_Up) { + _inner->selectSkip(-1); + } else if (e->key() == Qt::Key_PageDown) { + _inner->selectSkipPage(scrollArea()->height(), 1); + } else if (e->key() == Qt::Key_PageUp) { + _inner->selectSkipPage(scrollArea()->height(), -1); + } else { + ItemListBox::keyPressEvent(e); + } +} + +void MembersBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + QString title(lang(_inner->filter() == MembersFilter::Recent ? lng_channel_members : lng_channel_admins)); + paintTitle(p, title); +} + +void MembersBox::resizeEvent(QResizeEvent *e) { + ItemListBox::resizeEvent(e); + _inner->resize(width(), _inner->height()); +} + +void MembersBox::onScroll() { + _inner->loadProfilePhotos(scrollArea()->scrollTop()); +} + +void MembersBox::onAdd() { + if (_inner->filter() == MembersFilter::Recent && _inner->channel()->membersCount() >= (_inner->channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) { + Ui::showLayer(new MaxInviteBox(_inner->channel()->inviteLink()), KeepOtherLayers); + return; + } + ContactsBox *box = new ContactsBox(_inner->channel(), _inner->filter(), _inner->already()); + if (_inner->filter() == MembersFilter::Recent) { + Ui::showLayer(box); + } else { + _addBox = box; + connect(_addBox, SIGNAL(adminAdded()), this, SLOT(onAdminAdded())); + Ui::showLayer(_addBox, KeepOtherLayers); + } +} + +void MembersBox::onAdminAdded() { + if (!_addBox) return; + _addBox->onClose(); + _addBox = 0; + _loadTimer.start(ReloadChannelMembersTimeout); +} + +MembersBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter filter) : ScrolledWidget(parent) +, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) +, _newItemHeight((channel->amCreator() && (channel->membersCount() < (channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilter::Admins)) ? st::contactsNewItemHeight : 0) +, _newItemSel(false) +, _channel(channel) +, _filter(filter) +, _kickText(lang(lng_profile_kick)) +, _time(0) +, _kickWidth(st::normalFont->width(_kickText)) +, _sel(-1) +, _kickSel(-1) +, _kickDown(-1) +, _mouseSel(false) +, _kickConfirm(0) +, _kickRequestId(0) +, _kickBox(0) +, _loading(true) +, _loadingRequestId(0) +, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right()) +, _about(_aboutWidth) +, _aboutHeight(0) { + subscribe(FileDownload::ImageLoaded(), [this] { update(); }); + + connect(App::main(), SIGNAL(peerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&))); + connect(App::main(), SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(peerUpdated(PeerData*))); + + refresh(); + + load(); +} + +void MembersBox::Inner::load() { + if (!_loadingRequestId) { + _loadingRequestId = MTP::send(MTPchannels_GetParticipants(_channel->inputChannel, (_filter == MembersFilter::Recent) ? MTP_channelParticipantsRecent() : MTP_channelParticipantsAdmins(), MTP_int(0), MTP_int(Global::ChatSizeMax())), rpcDone(&Inner::membersReceived), rpcFail(&Inner::membersFailed)); + } +} + +void MembersBox::Inner::paintEvent(QPaintEvent *e) { + QRect r(e->rect()); + Painter p(this); + + _time = unixtime(); + p.fillRect(r, st::white->b); + + int32 yFrom = r.y() - st::membersPadding.top(), yTo = r.y() + r.height() - st::membersPadding.top(); + p.translate(0, st::membersPadding.top()); + if (_rows.isEmpty()) { + p.setFont(st::noContactsFont->f); + p.setPen(st::noContactsColor->p); + p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); + } else { + if (_newItemHeight) { + p.fillRect(0, 0, width(), _newItemHeight, (_newItemSel ? st::contactsBgOver : st::white)->b); + st::contactsNewItemIcon.paint(p, 0, 0, width()); + p.setFont(st::contactsNameFont); + p.setPen(st::contactsNewItemFg); + p.drawTextLeft(st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(_filter == MembersFilter::Admins ? lng_channel_add_admins : lng_channel_add_members)); + + yFrom -= _newItemHeight; + yTo -= _newItemHeight; + p.translate(0, _newItemHeight); + } + int32 from = floorclamp(yFrom, _rowHeight, 0, _rows.size()); + int32 to = ceilclamp(yTo, _rowHeight, 0, _rows.size()); + p.translate(0, from * _rowHeight); + for (; from < to; ++from) { + bool sel = (from == _sel); + bool kickSel = (from == _kickSel && (_kickDown < 0 || from == _kickDown)); + bool kickDown = kickSel && (from == _kickDown); + paintDialog(p, _rows[from], data(from), sel, kickSel, kickDown); + p.translate(0, _rowHeight); + } + if (to == _rows.size() && _filter == MembersFilter::Recent && (_rows.size() < _channel->membersCount() || _rows.size() >= Global::ChatSizeMax())) { + p.setPen(st::stickersReorderFg); + _about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); + } + } +} + +void MembersBox::Inner::enterEvent(QEvent *e) { + setMouseTracking(true); +} + +void MembersBox::Inner::leaveEvent(QEvent *e) { + _mouseSel = false; + setMouseTracking(false); + if (_sel >= 0) { + clearSel(); + } +} + +void MembersBox::Inner::mouseMoveEvent(QMouseEvent *e) { + _mouseSel = true; + _lastMousePos = e->globalPos(); + updateSel(); +} + +void MembersBox::Inner::mousePressEvent(QMouseEvent *e) { + _mouseSel = true; + _lastMousePos = e->globalPos(); + updateSel(); + if (e->button() == Qt::LeftButton && _kickSel < 0) { + chooseParticipant(); + } + _kickDown = _kickSel; + update(); +} + +void MembersBox::Inner::mouseReleaseEvent(QMouseEvent *e) { + _mouseSel = true; + _lastMousePos = e->globalPos(); + updateSel(); + if (_kickDown >= 0 && _kickDown == _kickSel && !_kickRequestId) { + _kickConfirm = _rows.at(_kickSel); + if (_kickBox) _kickBox->deleteLater(); + _kickBox = new ConfirmBox((_filter == MembersFilter::Recent ? (_channel->isMegagroup() ? lng_profile_sure_kick : lng_profile_sure_kick_channel) : lng_profile_sure_kick_admin)(lt_user, _kickConfirm->firstName)); + connect(_kickBox, SIGNAL(confirmed()), this, SLOT(onKickConfirm())); + connect(_kickBox, SIGNAL(destroyed(QObject*)), this, SLOT(onKickBoxDestroyed(QObject*))); + Ui::showLayer(_kickBox, KeepOtherLayers); + } + _kickDown = -1; +} + +void MembersBox::Inner::onKickBoxDestroyed(QObject *obj) { + if (_kickBox == obj) { + _kickBox = 0; + } +} + +void MembersBox::Inner::onKickConfirm() { + if (_filter == MembersFilter::Recent) { + _kickRequestId = MTP::send(MTPchannels_KickFromChannel(_channel->inputChannel, _kickConfirm->inputUser, MTP_bool(true)), rpcDone(&Inner::kickDone), rpcFail(&Inner::kickFail)); + } else { + _kickRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _kickConfirm->inputUser, MTP_channelRoleEmpty()), rpcDone(&Inner::kickAdminDone), rpcFail(&Inner::kickFail)); + } +} + +void MembersBox::Inner::paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown) { + UserData *user = peer->asUser(); + + p.fillRect(0, 0, width(), _rowHeight, (sel ? st::contactsBgOver : st::white)->b); + peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width()); + + p.setPen(st::black); + + int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); + int32 namew = width() - namex - st::contactsPadding.right() - (data->canKick ? (_kickWidth + st::contactsCheckPosition.x() * 2) : 0); + if (peer->isVerified()) { + auto icon = &st::dialogsVerifiedIcon; + namew -= icon->width(); + icon->paint(p, namex + qMin(data->name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width()); + } + data->name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width()); + + if (data->canKick) { + p.setFont((kickSel ? st::linkOverFont : st::linkFont)->f); + if (kickDown) { + p.setPen(st::btnDefLink.downColor->p); + } else { + p.setPen(st::btnDefLink.color->p); + } + p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), _kickText, _kickWidth); + } + + p.setFont(st::contactsStatusFont->f); + p.setPen(data->onlineColor ? st::contactsStatusFgOnline : (sel ? st::contactsStatusFgOver : st::contactsStatusFg)); + p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->online); +} + +void MembersBox::Inner::selectSkip(int32 dir) { + _time = unixtime(); + _mouseSel = false; + + int cur = -1; + if (_newItemHeight && _newItemSel) { + cur = 0; + } else if (_sel >= 0) { + cur = _sel + (_newItemHeight ? 1 : 0); + } + cur += dir; + if (cur <= 0) { + _newItemSel = _newItemHeight ? true : false; + _sel = (_newItemSel || _rows.isEmpty()) ? -1 : 0; + } else if (cur >= _rows.size() + (_newItemHeight ? 1 : 0)) { + _sel = -1; + } else { + _sel = cur - (_newItemHeight ? 1 : 0); + } + if (dir > 0) { + if (_sel < 0 || _sel >= _rows.size()) { + _sel = -1; + } + } else { + if (!_rows.isEmpty()) { + if (_sel < 0 && !_newItemSel) _sel = _rows.size() - 1; + } + } + if (_newItemSel) { + emit mustScrollTo(0, _newItemHeight); + } else if (_sel >= 0) { + emit mustScrollTo(_newItemHeight + _sel * _rowHeight, _newItemHeight + (_sel + 1) * _rowHeight); + } + + update(); +} + +void MembersBox::Inner::selectSkipPage(int32 h, int32 dir) { + int32 points = h / _rowHeight; + if (!points) return; + selectSkip(points * dir); +} + +void MembersBox::Inner::loadProfilePhotos(int32 yFrom) { + int32 yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5; + MTP::clearLoaderPriorities(); + + if (yTo < 0) return; + if (yFrom < 0) yFrom = 0; + + if (!_rows.isEmpty()) { + int32 from = (yFrom - _newItemHeight) / _rowHeight; + if (from < 0) from = 0; + if (from < _rows.size()) { + int32 to = ((yTo - _newItemHeight) / _rowHeight) + 1; + if (to > _rows.size()) to = _rows.size(); + + for (; from < to; ++from) { + _rows[from]->loadUserpic(); + } + } + } +} + +void MembersBox::Inner::chooseParticipant() { + if (_newItemSel) { + emit addRequested(); + return; + } + if (_sel < 0 || _sel >= _rows.size()) return; + if (PeerData *peer = _rows[_sel]) { + Ui::hideLayer(); + Ui::showPeerProfile(peer); + } +} + +void MembersBox::Inner::refresh() { + if (_rows.isEmpty()) { + resize(width(), st::membersPadding.top() + st::noContactsHeight + st::membersPadding.bottom()); + _aboutHeight = 0; + } else { + _about.setText(st::boxTextFont, lng_channel_only_last_shown(lt_count, _rows.size())); + _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); + if (_filter != MembersFilter::Recent || (_rows.size() >= _channel->membersCount() && _rows.size() < Global::ChatSizeMax())) { + _aboutHeight = 0; + } + resize(width(), st::membersPadding.top() + _newItemHeight + _rows.size() * _rowHeight + st::membersPadding.bottom() + _aboutHeight); + } + update(); +} + +ChannelData *MembersBox::Inner::channel() const { + return _channel; +} + +MembersFilter MembersBox::Inner::filter() const { + return _filter; +} + +MembersAlreadyIn MembersBox::Inner::already() const { + MembersAlreadyIn result; + for_const (auto peer, _rows) { + if (peer->isUser()) { + result.insert(peer->asUser()); + } + } + return result; +} + +void MembersBox::Inner::clearSel() { + updateSelectedRow(); + _newItemSel = false; + _sel = _kickSel = _kickDown = -1; + _lastMousePos = QCursor::pos(); + updateSel(); +} + +MembersBox::Inner::MemberData *MembersBox::Inner::data(int32 index) { + if (MemberData *result = _datas.at(index)) { + return result; + } + MemberData *result = _datas[index] = new MemberData(); + result->name.setText(st::contactsNameFont, _rows[index]->name, _textNameOptions); + int32 t = unixtime(); + result->online = App::onlineText(_rows[index], t);// lng_mediaview_date_time(lt_date, _dates[index].date().toString(qsl("dd.MM.yy")), lt_time, _dates[index].time().toString(cTimeFormat())); + result->onlineColor = App::onlineColorUse(_rows[index], t); + if (_filter == MembersFilter::Recent) { + result->canKick = (_channel->amCreator() || _channel->amEditor() || _channel->amModerator()) ? (_roles[index] == MemberRole::None) : false; + } else if (_filter == MembersFilter::Admins) { + result->canKick = _channel->amCreator() ? (_roles[index] == MemberRole::Editor || _roles[index] == MemberRole::Moderator) : false; + } else { + result->canKick = false; + } + return result; +} + +void MembersBox::Inner::clear() { + for (int32 i = 0, l = _datas.size(); i < l; ++i) { + delete _datas.at(i); + } + _datas.clear(); + _rows.clear(); + _dates.clear(); + _roles.clear(); + if (_kickBox) _kickBox->deleteLater(); + clearSel(); +} + +MembersBox::Inner::~Inner() { + clear(); +} + +void MembersBox::Inner::updateSel() { + if (!_mouseSel) return; + + QPoint p(mapFromGlobal(_lastMousePos)); + p.setY(p.y() - st::membersPadding.top()); + bool in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePos)); + bool newItemSel = (in && p.y() >= 0 && p.y() < _newItemHeight); + int32 newSel = (in && !newItemSel && p.y() >= _newItemHeight && p.y() < _newItemHeight + _rows.size() * _rowHeight) ? ((p.y() - _newItemHeight) / _rowHeight) : -1; + int32 newKickSel = newSel; + if (newSel >= 0 && (!data(newSel)->canKick || !QRect(width() - _kickWidth - st::contactsPadding.right() - st::contactsCheckPosition.x(), _newItemHeight + newSel * _rowHeight + st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, _kickWidth, st::normalFont->height).contains(p))) { + newKickSel = -1; + } + if (newSel != _sel || newKickSel != _kickSel || newItemSel != _newItemSel) { + updateSelectedRow(); + _newItemSel = newItemSel; + _sel = newSel; + _kickSel = newKickSel; + updateSelectedRow(); + setCursor(_kickSel >= 0 ? style::cur_pointer : style::cur_default); + } +} + +void MembersBox::Inner::peerUpdated(PeerData *peer) { + update(); +} + +void MembersBox::Inner::updateSelectedRow() { + if (_newItemSel) { + update(0, st::membersPadding.top(), width(), _newItemHeight); + } + if (_sel >= 0) { + update(0, st::membersPadding.top() + _newItemHeight + _sel * _rowHeight, width(), _rowHeight); + } +} + +void MembersBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { + for (int32 i = 0, l = _rows.size(); i < l; ++i) { + if (_rows.at(i) == peer) { + if (_datas.at(i)) { + _datas.at(i)->name.setText(st::contactsNameFont, peer->name, _textNameOptions); + update(0, st::membersPadding.top() + i * _rowHeight, width(), _rowHeight); + } else { + break; + } + } + } +} + +void MembersBox::Inner::membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req) { + clear(); + _loadingRequestId = 0; + + if (result.type() == mtpc_channels_channelParticipants) { + const auto &d(result.c_channels_channelParticipants()); + const auto &v(d.vparticipants.c_vector().v); + _rows.reserve(v.size()); + _datas.reserve(v.size()); + _dates.reserve(v.size()); + _roles.reserve(v.size()); + + if (_filter == MembersFilter::Recent && _channel->membersCount() < d.vcount.v) { + _channel->setMembersCount(d.vcount.v); + if (App::main()) emit App::main()->peerUpdated(_channel); + } else if (_filter == MembersFilter::Admins && _channel->adminsCount() < d.vcount.v) { + _channel->setAdminsCount(d.vcount.v); + if (App::main()) emit App::main()->peerUpdated(_channel); + } + App::feedUsers(d.vusers); + + for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { + int32 userId = 0, addedTime = 0; + MemberRole role = MemberRole::None; + switch (i->type()) { + case mtpc_channelParticipant: + userId = i->c_channelParticipant().vuser_id.v; + addedTime = i->c_channelParticipant().vdate.v; + break; + case mtpc_channelParticipantSelf: + role = MemberRole::Self; + userId = i->c_channelParticipantSelf().vuser_id.v; + addedTime = i->c_channelParticipantSelf().vdate.v; + break; + case mtpc_channelParticipantModerator: + role = MemberRole::Moderator; + userId = i->c_channelParticipantModerator().vuser_id.v; + addedTime = i->c_channelParticipantModerator().vdate.v; + break; + case mtpc_channelParticipantEditor: + role = MemberRole::Editor; + userId = i->c_channelParticipantEditor().vuser_id.v; + addedTime = i->c_channelParticipantEditor().vdate.v; + break; + case mtpc_channelParticipantKicked: + userId = i->c_channelParticipantKicked().vuser_id.v; + addedTime = i->c_channelParticipantKicked().vdate.v; + role = MemberRole::Kicked; + break; + case mtpc_channelParticipantCreator: + userId = i->c_channelParticipantCreator().vuser_id.v; + addedTime = _channel->date; + role = MemberRole::Creator; + break; + } + if (UserData *user = App::userLoaded(userId)) { + _rows.push_back(user); + _dates.push_back(date(addedTime)); + _roles.push_back(role); + _datas.push_back(0); + } + } + + // update admins if we got all of them + if (_filter == MembersFilter::Admins && _channel->isMegagroup() && _rows.size() < Global::ChatSizeMax()) { + _channel->mgInfo->lastAdmins.clear(); + for (int32 i = 0, l = _rows.size(); i != l; ++i) { + if (_roles.at(i) == MemberRole::Creator || _roles.at(i) == MemberRole::Editor) { + _channel->mgInfo->lastAdmins.insert(_rows.at(i)); + } + } + + Notify::peerUpdatedDelayed(_channel, Notify::PeerUpdate::Flag::AdminsChanged); + } + } + if (_rows.isEmpty()) { + _rows.push_back(App::self()); + _dates.push_back(date(MTP_int(_channel->date))); + _roles.push_back(MemberRole::Self); + _datas.push_back(0); + } + + clearSel(); + _loading = false; + refresh(); + + emit loaded(); +} + +bool MembersBox::Inner::membersFailed(const RPCError &error, mtpRequestId req) { + if (MTP::isDefaultHandledError(error)) return false; + + Ui::hideLayer(); + return true; +} + +void MembersBox::Inner::kickDone(const MTPUpdates &result, mtpRequestId req) { + App::main()->sentUpdatesReceived(result); + + if (_kickRequestId != req) return; + removeKicked(); + if (_kickBox) _kickBox->onClose(); +} + +void MembersBox::Inner::kickAdminDone(const MTPUpdates &result, mtpRequestId req) { + if (_kickRequestId != req) return; + if (App::main()) App::main()->sentUpdatesReceived(result); + removeKicked(); + if (_kickBox) _kickBox->onClose(); +} + +bool MembersBox::Inner::kickFail(const RPCError &error, mtpRequestId req) { + if (MTP::isDefaultHandledError(error)) return false; + + if (_kickBox) _kickBox->onClose(); + load(); + return true; +} + +void MembersBox::Inner::removeKicked() { + _kickRequestId = 0; + int32 index = _rows.indexOf(_kickConfirm); + if (index >= 0) { + _rows.removeAt(index); + delete _datas.at(index); + _datas.removeAt(index); + _dates.removeAt(index); + _roles.removeAt(index); + clearSel(); + if (_filter == MembersFilter::Recent && _channel->membersCount() > 1) { + _channel->setMembersCount(_channel->membersCount() - 1); + if (App::main()) emit App::main()->peerUpdated(_channel); + } else if (_filter == MembersFilter::Admins && _channel->adminsCount() > 1) { + _channel->setAdminsCount(_channel->adminsCount() - 1); + if (App::main()) emit App::main()->peerUpdated(_channel); + } + refresh(); + } + _kickConfirm = 0; +} diff --git a/Telegram/SourceFiles/boxes/members_box.h b/Telegram/SourceFiles/boxes/members_box.h new file mode 100644 index 000000000..f93f7a7b2 --- /dev/null +++ b/Telegram/SourceFiles/boxes/members_box.h @@ -0,0 +1,178 @@ +/* +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 "abstractbox.h" +#include "core/single_timer.h" +#include "ui/effects/round_image_checkbox.h" + +class ContactsBox; +class ConfirmBox; + +enum class MembersFilter { + Recent, + Admins, +}; +using MembersAlreadyIn = OrderedSet; + +class MembersBox : public ItemListBox { + Q_OBJECT + +public: + MembersBox(ChannelData *channel, MembersFilter filter); + +public slots: + void onScroll(); + + void onAdd(); + void onAdminAdded(); + +protected: + void keyPressEvent(QKeyEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private: + class Inner; + ChildWidget _inner; + + ContactsBox *_addBox = nullptr; + + SingleTimer _loadTimer; + +}; + +// This class is hold in header because it requires Qt preprocessing. +class MembersBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { + Q_OBJECT + +public: + Inner(QWidget *parent, ChannelData *channel, MembersFilter filter); + + void selectSkip(int32 dir); + void selectSkipPage(int32 h, int32 dir); + + void loadProfilePhotos(int32 yFrom); + void chooseParticipant(); + + void refresh(); + + ChannelData *channel() const; + MembersFilter filter() const; + + bool isLoaded() const { + return !_loading; + } + void clearSel(); + + MembersAlreadyIn already() const; + + ~Inner(); + +signals: + void mustScrollTo(int ymin, int ymax); + void addRequested(); + void loaded(); + +public slots: + void load(); + + void updateSel(); + void peerUpdated(PeerData *peer); + void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); + void onKickConfirm(); + void onKickBoxDestroyed(QObject *obj); + +protected: + void paintEvent(QPaintEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + +private: + struct MemberData { + Text name; + QString online; + bool onlineColor; + bool canKick; + }; + + void updateSelectedRow(); + MemberData *data(int32 index); + + void paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown); + + void membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req); + bool membersFailed(const RPCError &error, mtpRequestId req); + + void kickDone(const MTPUpdates &result, mtpRequestId req); + void kickAdminDone(const MTPUpdates &result, mtpRequestId req); + bool kickFail(const RPCError &error, mtpRequestId req); + void removeKicked(); + + void clear(); + + int32 _rowHeight, _newItemHeight; + bool _newItemSel; + + ChannelData *_channel; + MembersFilter _filter; + + QString _kickText; + int32 _time, _kickWidth; + + int32 _sel, _kickSel, _kickDown; + bool _mouseSel; + + UserData *_kickConfirm; + mtpRequestId _kickRequestId; + + ConfirmBox *_kickBox; + + enum class MemberRole { + None, + Self, + Creator, + Editor, + Moderator, + Kicked + }; + + bool _loading; + mtpRequestId _loadingRequestId; + typedef QVector MemberRows; + typedef QVector MemberDates; + typedef QVector MemberRoles; + typedef QVector MemberDatas; + MemberRows _rows; + MemberDates _dates; + MemberRoles _roles; + MemberDatas _datas; + + int32 _aboutWidth; + Text _about; + int32 _aboutHeight; + + QPoint _lastMousePos; + +}; diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp index b2fc879fa..52003f6ff 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.cpp +++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp @@ -30,213 +30,23 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "countries.h" #include "confirmbox.h" -SessionsInner::SessionsInner(SessionsList *list, SessionData *current) : TWidget() -, _list(list) -, _current(current) -, _terminating(0) -, _terminateAll(this, lang(lng_sessions_terminate_all), st::redBoxLinkButton) -, _terminateBox(0) { - connect(&_terminateAll, SIGNAL(clicked()), this, SLOT(onTerminateAll())); - _terminateAll.hide(); - setAttribute(Qt::WA_OpaquePaintEvent); -} - -void SessionsInner::paintEvent(QPaintEvent *e) { - QRect r(e->rect()); - Painter p(this); - - p.fillRect(r, st::white->b); - int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.iconPos.x();// st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip; - int32 w = width(); - - if (_current->active.isEmpty() && _list->isEmpty()) { - p.setFont(st::noContactsFont->f); - p.setPen(st::noContactsColor->p); - p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); - return; - } - - if (r.y() <= st::sessionCurrentHeight) { - p.translate(0, st::sessionCurrentPadding.top()); - p.setFont(st::sessionNameFont->f); - p.setPen(st::black->p); - p.drawTextLeft(x, st::sessionPadding.top(), w, _current->name, _current->nameWidth); - - p.setFont(st::sessionActiveFont->f); - p.setPen(st::sessionActiveColor->p); - p.drawTextRight(x, st::sessionPadding.top(), w, _current->active, _current->activeWidth); - - p.setFont(st::sessionInfoFont->f); - p.setPen(st::black->p); - p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, _current->info, _current->infoWidth); - p.setPen(st::sessionInfoColor->p); - p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, _current->ip, _current->ipWidth); - } - p.translate(0, st::sessionCurrentHeight - st::sessionCurrentPadding.top()); - if (_list->isEmpty()) { - p.setFont(st::sessionInfoFont->f); - p.setPen(st::sessionInfoColor->p); - p.drawText(QRect(st::sessionPadding.left(), 0, width() - st::sessionPadding.left() - st::sessionPadding.right(), st::noContactsHeight), lang(lng_sessions_other_desc), style::al_topleft); - return; - } - - p.setFont(st::linkFont->f); - int32 count = _list->size(); - int32 from = floorclamp(r.y() - st::sessionCurrentHeight, st::sessionHeight, 0, count); - int32 to = ceilclamp(r.y() + r.height() - st::sessionCurrentHeight, st::sessionHeight, 0, count); - p.translate(0, from * st::sessionHeight); - for (int32 i = from; i < to; ++i) { - const SessionData &auth(_list->at(i)); - - p.setFont(st::sessionNameFont->f); - p.setPen(st::black->p); - p.drawTextLeft(x, st::sessionPadding.top(), w, auth.name, auth.nameWidth); - - p.setFont(st::sessionActiveFont->f); - p.setPen(st::sessionActiveColor->p); - p.drawTextRight(xact, st::sessionPadding.top(), w, auth.active, auth.activeWidth); - - p.setFont(st::sessionInfoFont->f); - p.setPen(st::black->p); - p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, auth.info, auth.infoWidth); - p.setPen(st::sessionInfoColor->p); - p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, auth.ip, auth.ipWidth); - - p.translate(0, st::sessionHeight); - } -} - -void SessionsInner::onTerminate() { - for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) { - if (i.value()->getState() & Button::StateOver) { - _terminating = i.key(); - - if (_terminateBox) _terminateBox->deleteLater(); - _terminateBox = new ConfirmBox(lang(lng_settings_reset_one_sure), lang(lng_settings_reset_button), st::attentionBoxButton); - connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateSure())); - connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*))); - Ui::showLayer(_terminateBox, KeepOtherLayers); - } - } -} - -void SessionsInner::onTerminateSure() { - if (_terminateBox) { - _terminateBox->onClose(); - _terminateBox = 0; - } - MTP::send(MTPaccount_ResetAuthorization(MTP_long(_terminating)), rpcDone(&SessionsInner::terminateDone, _terminating), rpcFail(&SessionsInner::terminateFail, _terminating)); - TerminateButtons::iterator i = _terminateButtons.find(_terminating); - if (i != _terminateButtons.cend()) { - i.value()->clearState(); - i.value()->hide(); - } -} - -void SessionsInner::onTerminateAll() { - if (_terminateBox) _terminateBox->deleteLater(); - _terminateBox = new ConfirmBox(lang(lng_settings_reset_sure), lang(lng_settings_reset_button), st::attentionBoxButton); - connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateAllSure())); - connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*))); - Ui::showLayer(_terminateBox, KeepOtherLayers); -} - -void SessionsInner::onTerminateAllSure() { - if (_terminateBox) { - _terminateBox->onClose(); - _terminateBox = 0; - } - MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&SessionsInner::terminateAllDone), rpcFail(&SessionsInner::terminateAllFail)); - emit terminateAll(); -} - -void SessionsInner::onNoTerminateBox(QObject *obj) { - if (obj == _terminateBox) _terminateBox = 0; -} - -void SessionsInner::terminateDone(uint64 hash, const MTPBool &result) { - for (int32 i = 0, l = _list->size(); i < l; ++i) { - if (_list->at(i).hash == hash) { - _list->removeAt(i); - break; - } - } - listUpdated(); - emit oneTerminated(); -} - -bool SessionsInner::terminateFail(uint64 hash, const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - - TerminateButtons::iterator i = _terminateButtons.find(hash); - if (i != _terminateButtons.end()) { - i.value()->show(); - return true; - } - return false; -} - -void SessionsInner::terminateAllDone(const MTPBool &result) { - emit allTerminated(); -} - -bool SessionsInner::terminateAllFail(const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - emit allTerminated(); - return true; -} - -void SessionsInner::resizeEvent(QResizeEvent *e) { - _terminateAll.moveToLeft(st::sessionPadding.left(), st::sessionCurrentPadding.top() + st::sessionHeight + st::sessionCurrentPadding.bottom()); -} - -void SessionsInner::listUpdated() { - if (_list->isEmpty()) { - _terminateAll.hide(); - } else { - _terminateAll.show(); - } - for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) { - i.value()->move(0, -1); - } - for (int32 i = 0, l = _list->size(); i < l; ++i) { - TerminateButtons::iterator j = _terminateButtons.find(_list->at(i).hash); - if (j == _terminateButtons.cend()) { - j = _terminateButtons.insert(_list->at(i).hash, new IconedButton(this, st::sessionTerminate)); - connect(j.value(), SIGNAL(clicked()), this, SLOT(onTerminate())); - } - j.value()->moveToRight(st::sessionTerminateSkip, st::sessionCurrentHeight + i * st::sessionHeight + st::sessionTerminateTop, width()); - j.value()->show(); - } - for (TerminateButtons::iterator i = _terminateButtons.begin(); i != _terminateButtons.cend();) { - if (i.value()->y() >= 0) { - ++i; - } else { - delete i.value(); - i = _terminateButtons.erase(i); - } - } - resize(width(), _list->isEmpty() ? (st::sessionCurrentHeight + st::noContactsHeight) : (st::sessionCurrentHeight + _list->size() * st::sessionHeight)); - update(); -} - SessionsBox::SessionsBox() : ScrollableBox(st::sessionsScroll) , _loading(true) -, _inner(&_list, &_current) +, _inner(this, &_list, &_current) , _shadow(this) , _done(this, lang(lng_about_done), st::defaultBoxButton) , _shortPollRequest(0) { setMaxHeight(st::sessionsHeight); connect(&_done, SIGNAL(clicked()), this, SLOT(onClose())); - connect(&_inner, SIGNAL(oneTerminated()), this, SLOT(onOneTerminated())); - connect(&_inner, SIGNAL(allTerminated()), this, SLOT(onAllTerminated())); - connect(&_inner, SIGNAL(terminateAll()), this, SLOT(onTerminateAll())); + connect(_inner, SIGNAL(oneTerminated()), this, SLOT(onOneTerminated())); + connect(_inner, SIGNAL(allTerminated()), this, SLOT(onAllTerminated())); + connect(_inner, SIGNAL(terminateAll()), this, SLOT(onTerminateAll())); connect(App::wnd(), SIGNAL(newAuthorization()), this, SLOT(onNewAuthorization())); connect(&_shortPollTimer, SIGNAL(timeout()), this, SLOT(onShortPollAuthorizations())); - init(&_inner, st::boxButtonPadding.bottom() + _done.height() + st::boxButtonPadding.top(), st::boxTitleHeight); - _inner.resize(width(), st::noContactsHeight); + init(_inner, st::boxButtonPadding.bottom() + _done.height() + st::boxButtonPadding.top(), st::boxTitleHeight); + _inner->resize(width(), st::noContactsHeight); prepare(); @@ -291,7 +101,7 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { for (int32 i = 0; i < l; ++i) { const auto &d(v.at(i).c_authorization()); - SessionData data; + Data data; data.hash = d.vhash.v; QString appName, appVer = qs(d.vapp_version), systemVer = qs(d.vsystem_version), deviceModel = qs(d.vdevice_model); @@ -383,7 +193,7 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { } } } - _inner.listUpdated(); + _inner->listUpdated(); if (!_done.isHidden()) { showAll(); update(); @@ -430,4 +240,194 @@ void SessionsBox::onTerminateAll() { showAll(); update(); } -} \ No newline at end of file +} + +SessionsBox::Inner::Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current) : ScrolledWidget(parent) +, _list(list) +, _current(current) +, _terminating(0) +, _terminateAll(this, lang(lng_sessions_terminate_all), st::redBoxLinkButton) +, _terminateBox(0) { + connect(&_terminateAll, SIGNAL(clicked()), this, SLOT(onTerminateAll())); + _terminateAll.hide(); + setAttribute(Qt::WA_OpaquePaintEvent); +} + +void SessionsBox::Inner::paintEvent(QPaintEvent *e) { + QRect r(e->rect()); + Painter p(this); + + p.fillRect(r, st::white->b); + int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.iconPos.x();// st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip; + int32 w = width(); + + if (_current->active.isEmpty() && _list->isEmpty()) { + p.setFont(st::noContactsFont->f); + p.setPen(st::noContactsColor->p); + p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); + return; + } + + if (r.y() <= st::sessionCurrentHeight) { + p.translate(0, st::sessionCurrentPadding.top()); + p.setFont(st::sessionNameFont->f); + p.setPen(st::black->p); + p.drawTextLeft(x, st::sessionPadding.top(), w, _current->name, _current->nameWidth); + + p.setFont(st::sessionActiveFont->f); + p.setPen(st::sessionActiveColor->p); + p.drawTextRight(x, st::sessionPadding.top(), w, _current->active, _current->activeWidth); + + p.setFont(st::sessionInfoFont->f); + p.setPen(st::black->p); + p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, _current->info, _current->infoWidth); + p.setPen(st::sessionInfoColor->p); + p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, _current->ip, _current->ipWidth); + } + p.translate(0, st::sessionCurrentHeight - st::sessionCurrentPadding.top()); + if (_list->isEmpty()) { + p.setFont(st::sessionInfoFont->f); + p.setPen(st::sessionInfoColor->p); + p.drawText(QRect(st::sessionPadding.left(), 0, width() - st::sessionPadding.left() - st::sessionPadding.right(), st::noContactsHeight), lang(lng_sessions_other_desc), style::al_topleft); + return; + } + + p.setFont(st::linkFont->f); + int32 count = _list->size(); + int32 from = floorclamp(r.y() - st::sessionCurrentHeight, st::sessionHeight, 0, count); + int32 to = ceilclamp(r.y() + r.height() - st::sessionCurrentHeight, st::sessionHeight, 0, count); + p.translate(0, from * st::sessionHeight); + for (int32 i = from; i < to; ++i) { + const SessionsBox::Data &auth(_list->at(i)); + + p.setFont(st::sessionNameFont->f); + p.setPen(st::black->p); + p.drawTextLeft(x, st::sessionPadding.top(), w, auth.name, auth.nameWidth); + + p.setFont(st::sessionActiveFont->f); + p.setPen(st::sessionActiveColor->p); + p.drawTextRight(xact, st::sessionPadding.top(), w, auth.active, auth.activeWidth); + + p.setFont(st::sessionInfoFont->f); + p.setPen(st::black->p); + p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, auth.info, auth.infoWidth); + p.setPen(st::sessionInfoColor->p); + p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, auth.ip, auth.ipWidth); + + p.translate(0, st::sessionHeight); + } +} + +void SessionsBox::Inner::onTerminate() { + for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) { + if (i.value()->getState() & Button::StateOver) { + _terminating = i.key(); + + if (_terminateBox) _terminateBox->deleteLater(); + _terminateBox = new ConfirmBox(lang(lng_settings_reset_one_sure), lang(lng_settings_reset_button), st::attentionBoxButton); + connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateSure())); + connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*))); + Ui::showLayer(_terminateBox, KeepOtherLayers); + } + } +} + +void SessionsBox::Inner::onTerminateSure() { + if (_terminateBox) { + _terminateBox->onClose(); + _terminateBox = 0; + } + MTP::send(MTPaccount_ResetAuthorization(MTP_long(_terminating)), rpcDone(&Inner::terminateDone, _terminating), rpcFail(&Inner::terminateFail, _terminating)); + TerminateButtons::iterator i = _terminateButtons.find(_terminating); + if (i != _terminateButtons.cend()) { + i.value()->clearState(); + i.value()->hide(); + } +} + +void SessionsBox::Inner::onTerminateAll() { + if (_terminateBox) _terminateBox->deleteLater(); + _terminateBox = new ConfirmBox(lang(lng_settings_reset_sure), lang(lng_settings_reset_button), st::attentionBoxButton); + connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateAllSure())); + connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*))); + Ui::showLayer(_terminateBox, KeepOtherLayers); +} + +void SessionsBox::Inner::onTerminateAllSure() { + if (_terminateBox) { + _terminateBox->onClose(); + _terminateBox = 0; + } + MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&Inner::terminateAllDone), rpcFail(&Inner::terminateAllFail)); + emit terminateAll(); +} + +void SessionsBox::Inner::onNoTerminateBox(QObject *obj) { + if (obj == _terminateBox) _terminateBox = 0; +} + +void SessionsBox::Inner::terminateDone(uint64 hash, const MTPBool &result) { + for (int32 i = 0, l = _list->size(); i < l; ++i) { + if (_list->at(i).hash == hash) { + _list->removeAt(i); + break; + } + } + listUpdated(); + emit oneTerminated(); +} + +bool SessionsBox::Inner::terminateFail(uint64 hash, const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + TerminateButtons::iterator i = _terminateButtons.find(hash); + if (i != _terminateButtons.end()) { + i.value()->show(); + return true; + } + return false; +} + +void SessionsBox::Inner::terminateAllDone(const MTPBool &result) { + emit allTerminated(); +} + +bool SessionsBox::Inner::terminateAllFail(const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + emit allTerminated(); + return true; +} + +void SessionsBox::Inner::resizeEvent(QResizeEvent *e) { + _terminateAll.moveToLeft(st::sessionPadding.left(), st::sessionCurrentPadding.top() + st::sessionHeight + st::sessionCurrentPadding.bottom()); +} + +void SessionsBox::Inner::listUpdated() { + if (_list->isEmpty()) { + _terminateAll.hide(); + } else { + _terminateAll.show(); + } + for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) { + i.value()->move(0, -1); + } + for (int32 i = 0, l = _list->size(); i < l; ++i) { + TerminateButtons::iterator j = _terminateButtons.find(_list->at(i).hash); + if (j == _terminateButtons.cend()) { + j = _terminateButtons.insert(_list->at(i).hash, new IconedButton(this, st::sessionTerminate)); + connect(j.value(), SIGNAL(clicked()), this, SLOT(onTerminate())); + } + j.value()->moveToRight(st::sessionTerminateSkip, st::sessionCurrentHeight + i * st::sessionHeight + st::sessionTerminateTop, width()); + j.value()->show(); + } + for (TerminateButtons::iterator i = _terminateButtons.begin(); i != _terminateButtons.cend();) { + if (i.value()->y() >= 0) { + ++i; + } else { + delete i.value(); + i = _terminateButtons.erase(i); + } + } + resize(width(), _list->isEmpty() ? (st::sessionCurrentHeight + st::noContactsHeight) : (st::sessionCurrentHeight + _list->size() * st::sessionHeight)); + update(); +} diff --git a/Telegram/SourceFiles/boxes/sessionsbox.h b/Telegram/SourceFiles/boxes/sessionsbox.h index 851bfa8fb..f591464f4 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.h +++ b/Telegram/SourceFiles/boxes/sessionsbox.h @@ -25,20 +25,58 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org class ConfirmBox; -struct SessionData { - uint64 hash; - - int32 activeTime; - int32 nameWidth, activeWidth, infoWidth, ipWidth; - QString name, active, info, ip; -}; -typedef QList SessionsList; - -class SessionsInner : public TWidget, public RPCSender { +class SessionsBox : public ScrollableBox, public RPCSender { Q_OBJECT public: - SessionsInner(SessionsList *list, SessionData *current); + SessionsBox(); + +public slots: + void onOneTerminated(); + void onAllTerminated(); + void onTerminateAll(); + void onShortPollAuthorizations(); + void onNewAuthorization(); + +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + + void showAll() override; + +private: + struct Data { + uint64 hash; + + int32 activeTime; + int32 nameWidth, activeWidth, infoWidth, ipWidth; + QString name, active, info, ip; + }; + using List = QList; + + void gotAuthorizations(const MTPaccount_Authorizations &result); + + bool _loading; + + Data _current; + List _list; + + class Inner; + ChildWidget _inner; + ScrollableBoxShadow _shadow; + BoxButton _done; + + SingleTimer _shortPollTimer; + mtpRequestId _shortPollRequest; + +}; + +// This class is hold in header because it requires Qt preprocessing. +class SessionsBox::Inner : public ScrolledWidget, public RPCSender { + Q_OBJECT + +public: + Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current); void listUpdated(); @@ -65,8 +103,8 @@ private: void terminateAllDone(const MTPBool &res); bool terminateAllFail(const RPCError &error); - SessionsList *_list; - SessionData *_current; + SessionsBox::List *_list; + SessionsBox::Data *_current; typedef QMap TerminateButtons; TerminateButtons _terminateButtons; @@ -76,39 +114,3 @@ private: ConfirmBox *_terminateBox; }; - -class SessionsBox : public ScrollableBox, public RPCSender { - Q_OBJECT - -public: - SessionsBox(); - -public slots: - void onOneTerminated(); - void onAllTerminated(); - void onTerminateAll(); - void onShortPollAuthorizations(); - void onNewAuthorization(); - -protected: - void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; - - void showAll() override; - -private: - void gotAuthorizations(const MTPaccount_Authorizations &result); - - bool _loading; - - SessionData _current; - SessionsList _list; - - SessionsInner _inner; - ScrollableBoxShadow _shadow; - BoxButton _done; - - SingleTimer _shortPollTimer; - mtpRequestId _shortPollRequest; - -}; diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp index a7cb219ce..a0054c2d2 100644 --- a/Telegram/SourceFiles/boxes/sharebox.cpp +++ b/Telegram/SourceFiles/boxes/sharebox.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_indexed_list.h" #include "styles/style_boxes.h" +#include "styles/style_history.h" #include "observer_peer.h" #include "lang.h" #include "mainwindow.h" @@ -32,36 +33,46 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/confirmbox.h" #include "apiwrap.h" #include "ui/toast/toast.h" +#include "ui/widgets/multi_select.h" #include "history/history_media_types.h" +#include "boxes/contactsbox.h" ShareBox::ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback) : ItemListBox(st::boxScroll) , _copyCallback(std_::move(copyCallback)) , _submitCallback(std_::move(submitCallback)) , _inner(this, std_::move(filterCallback)) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) +, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) , _copy(this, lang(lng_share_copy_link), st::defaultBoxButton) , _share(this, lang(lng_share_confirm), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _topShadow(this) , _bottomShadow(this) { - int topSkip = st::boxTitleHeight + _filter->height(); - int bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom(); + _select->resizeToWidth(st::boxWideWidth); + myEnsureResized(_select); + + auto topSkip = getTopScrollSkip(); + auto bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom(); init(_inner, bottomSkip, topSkip); - connect(_inner, SIGNAL(selectedChanged()), this, SLOT(onSelectedChanged())); connect(_inner, SIGNAL(mustScrollTo(int,int)), this, SLOT(onMustScrollTo(int,int))); connect(_copy, SIGNAL(clicked()), this, SLOT(onCopyLink())); connect(_share, SIGNAL(clicked()), this, SLOT(onSubmit())); connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); - connect(_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); - connect(_filter, SIGNAL(submitted(bool)), _inner, SLOT(onSelectActive())); - connect(_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel())); - connect(_inner, SIGNAL(filterCancel()), this, SLOT(onFilterCancel())); + _select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); + _select->setItemRemovedCallback([this](uint64 itemId) { + if (auto peer = App::peerLoaded(itemId)) { + _inner->peerUnselected(peer); + onSelectedChanged(); + update(); + } + }); + _select->setResizedCallback([this] { updateScrollSkips(); }); + _select->setSubmittedCallback([this](bool) { _inner->onSelectActive(); }); connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername())); - - _filterCancel->setAttribute(Qt::WA_OpaquePaintEvent); + _inner->setPeerSelectedChangedCallback([this](PeerData *peer, bool checked) { + onPeerSelectedChanged(peer, checked); + }); _searchTimer.setSingleShot(true); connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername())); @@ -71,8 +82,29 @@ ShareBox::ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, prepare(); } +int ShareBox::getTopScrollSkip() const { + auto result = st::boxTitleHeight; + if (!_select->isHidden()) { + result += _select->height(); + } + return result; +} + +void ShareBox::updateScrollSkips() { + auto oldScrollHeight = scrollArea()->height(); + auto topSkip = getTopScrollSkip(); + auto bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom(); + setScrollSkips(bottomSkip, topSkip); + auto scrollHeightDelta = scrollArea()->height() - oldScrollHeight; + if (scrollHeightDelta) { + scrollArea()->scrollToY(scrollArea()->scrollTop() - scrollHeightDelta); + } + + _topShadow->setGeometry(0, topSkip, width(), st::lineWidth); +} + bool ShareBox::onSearchByUsername(bool searchCache) { - auto query = _filter->getLastText().trimmed(); + auto query = _select->getQuery(); if (query.isEmpty()) { if (_peopleRequest) { _peopleRequest = 0; @@ -140,7 +172,7 @@ bool ShareBox::peopleFailed(const RPCError &error, mtpRequestId requestId) { } void ShareBox::doSetInnerFocus() { - _filter->setFocus(); + _select->setInnerFocus(); } void ShareBox::paintEvent(QPaintEvent *e) { @@ -152,17 +184,21 @@ void ShareBox::paintEvent(QPaintEvent *e) { void ShareBox::resizeEvent(QResizeEvent *e) { ItemListBox::resizeEvent(e); - _filter->resize(width(), _filter->height()); - _filter->moveToLeft(0, st::boxTitleHeight); - _filterCancel->moveToRight(0, st::boxTitleHeight); + + _select->resizeToWidth(width()); + _select->moveToLeft(0, st::boxTitleHeight); + + updateScrollSkips(); + _inner->resizeToWidth(width()); moveButtons(); - _topShadow->setGeometry(0, st::boxTitleHeight + _filter->height(), width(), st::lineWidth); + _topShadow->setGeometry(0, getTopScrollSkip(), width(), st::lineWidth); _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _share->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); } void ShareBox::keyPressEvent(QKeyEvent *e) { - if (_filter->hasFocus()) { + auto focused = focusWidget(); + if (_select == focused || _select->isAncestorOf(focusWidget())) { if (e->key() == Qt::Key_Up) { _inner->activateSkipColumn(-1); } else if (e->key() == Qt::Key_Down) { @@ -192,13 +228,26 @@ void ShareBox::updateButtonsVisibility() { _cancel->setVisible(hasSelected); } -void ShareBox::onFilterCancel() { - _filter->setText(QString()); +void ShareBox::onFilterUpdate(const QString &query) { + scrollArea()->scrollToY(0); + _inner->updateFilter(query); } -void ShareBox::onFilterUpdate() { - _filterCancel->setVisible(!_filter->getLastText().isEmpty()); - _inner->updateFilter(_filter->getLastText()); +void ShareBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) { + using AddItemWay = Ui::MultiSelect::AddItemWay; + auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default; + _select->addItem(peer->id, peer->shortName(), st::windowActiveBg, PaintUserpicCallback(peer), addItemWay); +} + +void ShareBox::onPeerSelectedChanged(PeerData *peer, bool checked) { + if (checked) { + addPeerToMultiSelect(peer); + _select->clearQuery(); + } else { + _select->removeItem(peer->id); + } + onSelectedChanged(); + update(); } void ShareBox::onSubmit() { @@ -240,9 +289,7 @@ void ShareBox::onScroll() { _inner->setVisibleTopBottom(scrollTop, scrollTop + scroll->height()); } -namespace internal { - -ShareInner::ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : ScrolledWidget(parent) +ShareBox::Inner::Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : ScrolledWidget(parent) , _filterCallback(std_::move(filterCallback)) , _chatsIndexed(std_::make_unique(Dialogs::SortMode::Add)) { _rowsTop = st::shareRowsTop; @@ -260,8 +307,6 @@ ShareInner::ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallbac _filter = qsl("a"); updateFilter(); - prepareWideCheckIcons(); - using UpdateFlag = Notify::PeerUpdate::Flag; auto observeEvents = UpdateFlag::NameChanged | UpdateFlag::PhotoChanged; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) { @@ -270,19 +315,19 @@ ShareInner::ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallbac subscribe(FileDownload::ImageLoaded(), [this] { update(); }); } -void ShareInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { +void ShareBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) { loadProfilePhotos(visibleTop); } -void ShareInner::activateSkipRow(int direction) { +void ShareBox::Inner::activateSkipRow(int direction) { activateSkipColumn(direction * _columnCount); } -int ShareInner::displayedChatsCount() const { +int ShareBox::Inner::displayedChatsCount() const { return _filter.isEmpty() ? _chatsIndexed->size() : (_filtered.size() + d_byUsernameFiltered.size()); } -void ShareInner::activateSkipColumn(int direction) { +void ShareBox::Inner::activateSkipColumn(int direction) { if (_active < 0) { if (direction > 0) { setActive(0); @@ -300,11 +345,11 @@ void ShareInner::activateSkipColumn(int direction) { setActive(active); } -void ShareInner::activateSkipPage(int pageHeight, int direction) { +void ShareBox::Inner::activateSkipPage(int pageHeight, int direction) { activateSkipRow(direction * (pageHeight / _rowHeight)); } -void ShareInner::notifyPeerUpdated(const Notify::PeerUpdate &update) { +void ShareBox::Inner::notifyPeerUpdated(const Notify::PeerUpdate &update) { if (update.flags & Notify::PeerUpdate::Flag::NameChanged) { _chatsIndexed->peerNameChanged(update.peer, update.oldNames, update.oldNameFirstChars); } @@ -312,7 +357,7 @@ void ShareInner::notifyPeerUpdated(const Notify::PeerUpdate &update) { updateChat(update.peer); } -void ShareInner::updateChat(PeerData *peer) { +void ShareBox::Inner::updateChat(PeerData *peer) { auto i = _dataMap.find(peer); if (i != _dataMap.cend()) { updateChatName(i.value(), peer); @@ -320,11 +365,11 @@ void ShareInner::updateChat(PeerData *peer) { } } -void ShareInner::updateChatName(Chat *chat, PeerData *peer) { +void ShareBox::Inner::updateChatName(Chat *chat, PeerData *peer) { chat->name.setText(st::shareNameFont, peer->name, _textNameOptions); } -void ShareInner::repaintChatAtIndex(int index) { +void ShareBox::Inner::repaintChatAtIndex(int index) { if (index < 0) return; auto row = index / _columnCount; @@ -332,7 +377,7 @@ void ShareInner::repaintChatAtIndex(int index) { update(rtlrect(_rowsLeft + qFloor(column * _rowWidthReal), row * _rowHeight, _rowWidth, _rowHeight, width())); } -ShareInner::Chat *ShareInner::getChatAtIndex(int index) { +ShareBox::Inner::Chat *ShareBox::Inner::getChatAtIndex(int index) { if (index < 0) return nullptr; auto row = ([this, index]() -> Dialogs::Row* { if (_filter.isEmpty()) return _chatsIndexed->rowAtY(index, 1); @@ -351,11 +396,11 @@ ShareInner::Chat *ShareInner::getChatAtIndex(int index) { return nullptr; } -void ShareInner::repaintChat(PeerData *peer) { +void ShareBox::Inner::repaintChat(PeerData *peer) { repaintChatAtIndex(chatIndex(peer)); } -int ShareInner::chatIndex(PeerData *peer) const { +int ShareBox::Inner::chatIndex(PeerData *peer) const { int index = 0; if (_filter.isEmpty()) { for_const (auto row, _chatsIndexed->all()) { @@ -381,7 +426,7 @@ int ShareInner::chatIndex(PeerData *peer) const { return -1; } -void ShareInner::loadProfilePhotos(int yFrom) { +void ShareBox::Inner::loadProfilePhotos(int yFrom) { if (yFrom < 0) { yFrom = 0; } @@ -420,13 +465,14 @@ void ShareInner::loadProfilePhotos(int yFrom) { } } -ShareInner::Chat *ShareInner::getChat(Dialogs::Row *row) { +ShareBox::Inner::Chat *ShareBox::Inner::getChat(Dialogs::Row *row) { auto data = static_cast(row->attached); if (!data) { auto peer = row->history()->peer; auto i = _dataMap.constFind(peer); if (i == _dataMap.cend()) { - _dataMap.insert(peer, data = new Chat(peer)); + data = new Chat(peer, [this, peer] { repaintChat(peer); }); + _dataMap.insert(peer, data); updateChatName(data, peer); } else { data = i.value(); @@ -436,12 +482,12 @@ ShareInner::Chat *ShareInner::getChat(Dialogs::Row *row) { return data; } -void ShareInner::setActive(int active) { +void ShareBox::Inner::setActive(int active) { if (active != _active) { auto changeNameFg = [this](int index, style::color from, style::color to) { if (auto chat = getChatAtIndex(index)) { - chat->nameFg.start([this, chat] { - repaintChat(chat->peer); + chat->nameFg.start([this, peer = chat->peer] { + repaintChat(peer); }, from->c, to->c, st::shareActivateDuration); } }; @@ -453,71 +499,14 @@ void ShareInner::setActive(int active) { emit mustScrollTo(y, y + _rowHeight); } -void ShareInner::paintChat(Painter &p, Chat *chat, int index) { +void ShareBox::Inner::paintChat(Painter &p, uint64 ms, Chat *chat, int index) { auto x = _rowsLeft + qFloor((index % _columnCount) * _rowWidthReal); auto y = _rowsTop + (index / _columnCount) * _rowHeight; - auto selectionLevel = chat->selection.current(chat->selected ? 1. : 0.); - - auto w = width(); - auto photoLeft = (_rowWidth - (st::sharePhotoRadius * 2)) / 2; + auto outerWidth = width(); + auto photoLeft = (_rowWidth - (st::sharePhotoCheckbox.imageRadius * 2)) / 2; auto photoTop = st::sharePhotoTop; - if (chat->selection.animating()) { - p.setRenderHint(QPainter::SmoothPixmapTransform, true); - auto userpicRadius = qRound(WideCacheScale * (st::sharePhotoRadius + (st::sharePhotoSmallRadius - st::sharePhotoRadius) * selectionLevel)); - auto userpicShift = WideCacheScale * st::sharePhotoRadius - userpicRadius; - auto userpicLeft = x + photoLeft - (WideCacheScale - 1) * st::sharePhotoRadius + userpicShift; - auto userpicTop = y + photoTop - (WideCacheScale - 1) * st::sharePhotoRadius + userpicShift; - auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2); - auto from = QRect(QPoint(0, 0), chat->wideUserpicCache.size()); - p.drawPixmapLeft(to, w, chat->wideUserpicCache, from); - p.setRenderHint(QPainter::SmoothPixmapTransform, false); - } else { - if (!chat->wideUserpicCache.isNull()) { - chat->wideUserpicCache = QPixmap(); - } - auto userpicRadius = chat->selected ? st::sharePhotoSmallRadius : st::sharePhotoRadius; - auto userpicShift = st::sharePhotoRadius - userpicRadius; - auto userpicLeft = x + photoLeft + userpicShift; - auto userpicTop = y + photoTop + userpicShift; - chat->peer->paintUserpicLeft(p, userpicRadius * 2, userpicLeft, userpicTop, w); - } - - if (selectionLevel > 0) { - p.setRenderHint(QPainter::HighQualityAntialiasing, true); - p.setOpacity(snap(selectionLevel, 0., 1.)); - p.setBrush(Qt::NoBrush); - QPen pen = st::shareSelectFg; - pen.setWidth(st::shareSelectWidth); - p.setPen(pen); - p.drawEllipse(myrtlrect(x + photoLeft, y + photoTop, st::sharePhotoRadius * 2, st::sharePhotoRadius * 2)); - p.setOpacity(1.); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - } - - removeFadeOutedIcons(chat); - p.setRenderHint(QPainter::SmoothPixmapTransform, true); - for (auto &icon : chat->icons) { - auto fadeIn = icon.fadeIn.current(1.); - auto fadeOut = icon.fadeOut.current(1.); - auto iconRadius = qRound(WideCacheScale * (st::shareCheckSmallRadius + fadeOut * (st::shareCheckRadius - st::shareCheckSmallRadius))); - auto iconShift = WideCacheScale * st::shareCheckRadius - iconRadius; - auto iconLeft = x + photoLeft + 2 * st::sharePhotoRadius + st::shareSelectWidth - 2 * st::shareCheckRadius - (WideCacheScale - 1) * st::shareCheckRadius + iconShift; - auto iconTop = y + photoTop + 2 * st::sharePhotoRadius + st::shareSelectWidth - 2 * st::shareCheckRadius - (WideCacheScale - 1) * st::shareCheckRadius + iconShift; - auto to = QRect(iconLeft, iconTop, iconRadius * 2, iconRadius * 2); - auto from = QRect(QPoint(0, 0), _wideCheckIconCache.size()); - auto opacity = fadeIn * fadeOut; - p.setOpacity(opacity); - if (fadeOut < 1.) { - p.drawPixmapLeft(to, w, icon.wideCheckCache, from); - } else { - auto divider = qRound((WideCacheScale - 2) * st::shareCheckRadius + fadeIn * 3 * st::shareCheckRadius); - p.drawPixmapLeft(QRect(iconLeft, iconTop, divider, iconRadius * 2), w, _wideCheckIconCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckIconCache.height())); - p.drawPixmapLeft(QRect(iconLeft + divider, iconTop, iconRadius * 2 - divider, iconRadius * 2), w, _wideCheckCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckCache.width() - divider * cIntRetinaFactor(), _wideCheckCache.height())); - } - } - p.setRenderHint(QPainter::SmoothPixmapTransform, false); - p.setOpacity(1.); + chat->checkbox.paint(p, ms, x + photoLeft, y + photoTop, outerWidth); if (chat->nameFg.animating()) { p.setPen(chat->nameFg.current()); @@ -527,16 +516,20 @@ void ShareInner::paintChat(Painter &p, Chat *chat, int index) { auto nameWidth = (_rowWidth - st::shareColumnSkip); auto nameLeft = st::shareColumnSkip / 2; - auto nameTop = photoTop + st::sharePhotoRadius * 2 + st::shareNameTop; - chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, w, 2, style::al_top, 0, -1, 0, true); + auto nameTop = photoTop + st::sharePhotoCheckbox.imageRadius * 2 + st::shareNameTop; + chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, outerWidth, 2, style::al_top, 0, -1, 0, true); } -ShareInner::Chat::Chat(PeerData *peer) : peer(peer), name(st::sharePhotoRadius * 2) { +ShareBox::Inner::Chat::Chat(PeerData *peer, base::lambda_wrap updateCallback) +: peer(peer) +, checkbox(st::sharePhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer)) +, name(st::sharePhotoCheckbox.imageRadius * 2) { } -void ShareInner::paintEvent(QPaintEvent *e) { +void ShareBox::Inner::paintEvent(QPaintEvent *e) { Painter p(this); + auto ms = getms(); auto r = e->rect(); p.setClipRect(r); p.fillRect(r, st::white); @@ -552,7 +545,7 @@ void ShareInner::paintEvent(QPaintEvent *e) { if (indexFrom >= indexTo) { break; } - paintChat(p, getChat(*i), indexFrom); + paintChat(p, ms, getChat(*i), indexFrom); ++indexFrom; } } else { @@ -573,7 +566,7 @@ void ShareInner::paintEvent(QPaintEvent *e) { if (indexFrom >= _filtered.size()) { break; } - paintChat(p, getChat(_filtered[indexFrom]), indexFrom); + paintChat(p, ms, getChat(_filtered[indexFrom]), indexFrom); ++indexFrom; } indexFrom -= filteredSize; @@ -585,7 +578,7 @@ void ShareInner::paintEvent(QPaintEvent *e) { if (indexFrom >= d_byUsernameFiltered.size()) { break; } - paintChat(p, d_byUsernameFiltered[indexFrom], filteredSize + indexFrom); + paintChat(p, ms, d_byUsernameFiltered[indexFrom], filteredSize + indexFrom); ++indexFrom; } } @@ -593,27 +586,27 @@ void ShareInner::paintEvent(QPaintEvent *e) { } } -void ShareInner::enterEvent(QEvent *e) { +void ShareBox::Inner::enterEvent(QEvent *e) { setMouseTracking(true); } -void ShareInner::leaveEvent(QEvent *e) { +void ShareBox::Inner::leaveEvent(QEvent *e) { setMouseTracking(false); } -void ShareInner::mouseMoveEvent(QMouseEvent *e) { +void ShareBox::Inner::mouseMoveEvent(QMouseEvent *e) { updateUpon(e->pos()); setCursor((_upon >= 0) ? style::cur_pointer : style::cur_default); } -void ShareInner::updateUpon(const QPoint &pos) { +void ShareBox::Inner::updateUpon(const QPoint &pos) { auto x = pos.x(), y = pos.y(); auto row = (y - _rowsTop) / _rowHeight; auto column = qFloor((x - _rowsLeft) / _rowWidthReal); auto left = _rowsLeft + qFloor(column * _rowWidthReal) + st::shareColumnSkip / 2; auto top = _rowsTop + row * _rowHeight + st::sharePhotoTop; auto xupon = (x >= left) && (x < left + (_rowWidth - st::shareColumnSkip)); - auto yupon = (y >= top) && (y < top + st::sharePhotoRadius * 2 + st::shareNameTop + st::shareNameFont->height * 2); + auto yupon = (y >= top) && (y < top + st::sharePhotoCheckbox.imageRadius * 2 + st::shareNameTop + st::shareNameFont->height * 2); auto upon = (xupon && yupon) ? (row * _columnCount + column) : -1; if (upon >= displayedChatsCount()) { upon = -1; @@ -621,41 +614,26 @@ void ShareInner::updateUpon(const QPoint &pos) { _upon = upon; } -void ShareInner::mousePressEvent(QMouseEvent *e) { +void ShareBox::Inner::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { updateUpon(e->pos()); changeCheckState(getChatAtIndex(_upon)); } } -void ShareInner::onSelectActive() { +void ShareBox::Inner::onSelectActive() { changeCheckState(getChatAtIndex(_active > 0 ? _active : 0)); } -void ShareInner::resizeEvent(QResizeEvent *e) { - _columnSkip = (width() - _columnCount * st::sharePhotoRadius * 2) / float64(_columnCount + 1); - _rowWidthReal = st::sharePhotoRadius * 2 + _columnSkip; +void ShareBox::Inner::resizeEvent(QResizeEvent *e) { + _columnSkip = (width() - _columnCount * st::sharePhotoCheckbox.imageRadius * 2) / float64(_columnCount + 1); + _rowWidthReal = st::sharePhotoCheckbox.imageRadius * 2 + _columnSkip; _rowsLeft = qFloor(_columnSkip / 2); _rowWidth = qFloor(_rowWidthReal); update(); } -struct AnimBumpy { - AnimBumpy(float64 bump) : bump(bump) - , dt0(bump - sqrt(bump * (bump - 1.))) - , k(1 / (2 * dt0 - 1)) { - } - float64 bump; - float64 dt0; - float64 k; -}; - -float64 anim_bumpy(const float64 &delta, const float64 &dt) { - static AnimBumpy data = { 1.25 }; - return delta * (data.bump - data.k * (dt - data.dt0) * (dt - data.dt0)); -} - -void ShareInner::changeCheckState(Chat *chat) { +void ShareBox::Inner::changeCheckState(Chat *chat) { if (!chat) return; if (!_filter.isEmpty()) { @@ -664,115 +642,44 @@ void ShareInner::changeCheckState(Chat *chat) { row = _chatsIndexed->addToEnd(App::history(chat->peer)).value(0); } chat = getChat(row); - if (!chat->selected) { + if (!chat->checkbox.checked()) { _chatsIndexed->moveToTop(chat->peer); } - emit filterCancel(); } - chat->selected = !chat->selected; - if (chat->selected) { + changePeerCheckState(chat, !chat->checkbox.checked()); +} + +void ShareBox::Inner::peerUnselected(PeerData *peer) { + // If data is nullptr we simply won't do anything. + auto chat = _dataMap.value(peer, nullptr); + changePeerCheckState(chat, false, ChangeStateWay::SkipCallback); +} + +void ShareBox::Inner::setPeerSelectedChangedCallback(base::lambda_unique callback) { + _peerSelectedChangedCallback = std_::move(callback); +} + +void ShareBox::Inner::changePeerCheckState(Chat *chat, bool checked, ChangeStateWay useCallback) { + if (chat) { + chat->checkbox.setChecked(checked); + } + if (checked) { _selected.insert(chat->peer); - chat->icons.push_back(Chat::Icon()); - chat->icons.back().fadeIn.start([this, chat] { - repaintChat(chat->peer); - }, 0, 1, st::shareSelectDuration); + setActive(chatIndex(chat->peer)); } else { _selected.remove(chat->peer); - prepareWideCheckIconCache(&chat->icons.back()); - chat->icons.back().fadeOut.start([this, chat] { - repaintChat(chat->peer); - removeFadeOutedIcons(chat); // this call can destroy current lambda - }, 1, 0, st::shareSelectDuration); } - prepareWideUserpicCache(chat); - chat->selection.start([this, chat] { - repaintChat(chat->peer); - }, chat->selected ? 0 : 1, chat->selected ? 1 : 0, st::shareSelectDuration, anim_bumpy); - if (chat->selected) { - setActive(chatIndex(chat->peer)); - } - emit selectedChanged(); -} - -void ShareInner::removeFadeOutedIcons(Chat *chat) { - while (!chat->icons.empty() && !chat->icons.front().fadeIn.animating() && !chat->icons.front().fadeOut.animating()) { - if (chat->icons.size() > 1 || !chat->selected) { - chat->icons.erase(chat->icons.begin()); - } else { - break; - } + if (useCallback != ChangeStateWay::SkipCallback && _peerSelectedChangedCallback) { + _peerSelectedChangedCallback(chat->peer, checked); } } -void ShareInner::prepareWideUserpicCache(Chat *chat) { - if (chat->wideUserpicCache.isNull()) { - auto size = st::sharePhotoRadius * 2; - auto wideSize = size * WideCacheScale; - QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); - cache.setDevicePixelRatio(cRetinaFactor()); - { - Painter p(&cache); - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(0, 0, wideSize, wideSize, Qt::transparent); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - chat->peer->paintUserpic(p, size, (wideSize - size) / 2, (wideSize - size) / 2); - } - chat->wideUserpicCache = App::pixmapFromImageInPlace(std_::move(cache)); - chat->wideUserpicCache.setDevicePixelRatio(cRetinaFactor()); - } -} - -void ShareInner::prepareWideCheckIconCache(Chat::Icon *icon) { - QImage wideCache(_wideCheckCache.width(), _wideCheckCache.height(), QImage::Format_ARGB32_Premultiplied); - wideCache.setDevicePixelRatio(cRetinaFactor()); - { - Painter p(&wideCache); - p.setCompositionMode(QPainter::CompositionMode_Source); - auto iconRadius = WideCacheScale * st::shareCheckRadius; - auto divider = qRound((WideCacheScale - 2) * st::shareCheckRadius + icon->fadeIn.current(1.) * 3 * st::shareCheckRadius); - p.drawPixmapLeft(QRect(0, 0, divider, iconRadius * 2), width(), _wideCheckIconCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckIconCache.height())); - p.drawPixmapLeft(QRect(divider, 0, iconRadius * 2 - divider, iconRadius * 2), width(), _wideCheckCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckCache.width() - divider * cIntRetinaFactor(), _wideCheckCache.height())); - } - icon->wideCheckCache = App::pixmapFromImageInPlace(std_::move(wideCache)); - icon->wideCheckCache.setDevicePixelRatio(cRetinaFactor()); -} - -void ShareInner::prepareWideCheckIcons() { - auto size = st::shareCheckRadius * 2; - auto wideSize = size * WideCacheScale; - QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); - cache.setDevicePixelRatio(cRetinaFactor()); - { - Painter p(&cache); - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(0, 0, wideSize, wideSize, Qt::transparent); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.setRenderHint(QPainter::HighQualityAntialiasing, true); - auto pen = st::shareCheckBorder->p; - pen.setWidth(st::shareSelectWidth); - p.setPen(pen); - p.setBrush(st::shareCheckBg); - auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size); - p.drawEllipse(ellipse); - } - QImage cacheIcon = cache; - { - Painter p(&cacheIcon); - auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size); - st::shareCheckIcon.paint(p, ellipse.topLeft(), wideSize); - } - _wideCheckCache = App::pixmapFromImageInPlace(std_::move(cache)); - _wideCheckCache.setDevicePixelRatio(cRetinaFactor()); - _wideCheckIconCache = App::pixmapFromImageInPlace(std_::move(cacheIcon)); - _wideCheckIconCache.setDevicePixelRatio(cRetinaFactor()); -} - -bool ShareInner::hasSelected() const { +bool ShareBox::Inner::hasSelected() const { return _selected.size(); } -void ShareInner::updateFilter(QString filter) { +void ShareBox::Inner::updateFilter(QString filter) { _lastQuery = filter.toLower().trimmed(); filter = textSearchKey(filter); @@ -851,7 +758,7 @@ void ShareInner::updateFilter(QString filter) { } } -void ShareInner::peopleReceived(const QString &query, const QVector &people) { +void ShareBox::Inner::peopleReceived(const QString &query, const QVector &people) { _lastQuery = query.toLower().trimmed(); if (_lastQuery.at(0) == '@') _lastQuery = _lastQuery.mid(1); int32 already = _byUsernameFiltered.size(); @@ -867,7 +774,7 @@ void ShareInner::peopleReceived(const QString &query, const QVector &pe auto *peer = App::peer(peerId); if (!peer || !_filterCallback(peer)) continue; - auto chat = new Chat(peer); + auto chat = new Chat(peer, [this, peer] { repaintChat(peer); }); updateChatName(chat, peer); if (auto row = _chatsIndexed->getRow(peer->id)) { continue; @@ -881,7 +788,7 @@ void ShareInner::peopleReceived(const QString &query, const QVector &pe refresh(); } -void ShareInner::refresh() { +void ShareBox::Inner::refresh() { auto count = displayedChatsCount(); if (count) { auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0); @@ -892,25 +799,23 @@ void ShareInner::refresh() { update(); } -ShareInner::~ShareInner() { +ShareBox::Inner::~Inner() { for_const (auto chat, _dataMap) { delete chat; } } -QVector ShareInner::selected() const { +QVector ShareBox::Inner::selected() const { QVector result; result.reserve(_dataMap.size()); for_const (auto chat, _dataMap) { - if (chat->selected) { + if (chat->checkbox.checked()) { result.push_back(chat->peer); } } return result; } -} // namespace internal - QString appendShareGameScoreUrl(const QString &url, const FullMsgId &fullId) { auto shareHashData = QByteArray(0x10, Qt::Uninitialized); auto shareHashDataInts = reinterpret_cast(shareHashData.data()); diff --git a/Telegram/SourceFiles/boxes/sharebox.h b/Telegram/SourceFiles/boxes/sharebox.h index 7f7d8e99d..449c56242 100644 --- a/Telegram/SourceFiles/boxes/sharebox.h +++ b/Telegram/SourceFiles/boxes/sharebox.h @@ -24,20 +24,21 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/lambda_wrap.h" #include "core/observer.h" #include "core/vector_of_moveable.h" +#include "ui/effects/round_image_checkbox.h" namespace Dialogs { class Row; class IndexedList; } // namespace Dialogs -namespace internal { -class ShareInner; -} // namespace internal - namespace Notify { struct PeerUpdate; } // namespace Notify +namespace Ui { +class MultiSelect; +} // namespace Ui + QString appendShareGameScoreUrl(const QString &url, const FullMsgId &fullId); void shareGameScoreByHash(const QString &hash); @@ -51,8 +52,6 @@ public: ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback); private slots: - void onFilterUpdate(); - void onFilterCancel(); void onScroll(); bool onSearchByUsername(bool searchCache = false); @@ -60,7 +59,6 @@ private slots: void onSubmit(); void onCopyLink(); - void onSelectedChanged(); void onMustScrollTo(int top, int bottom); @@ -72,8 +70,15 @@ protected: void doSetInnerFocus() override; private: + void onFilterUpdate(const QString &query); + void onSelectedChanged(); void moveButtons(); void updateButtonsVisibility(); + int getTopScrollSkip() const; + void updateScrollSkips(); + + void addPeerToMultiSelect(PeerData *peer, bool skipAnimation = false); + void onPeerSelectedChanged(PeerData *peer, bool checked); void peopleReceived(const MTPcontacts_Found &result, mtpRequestId requestId); bool peopleFailed(const RPCError &error, mtpRequestId requestId); @@ -81,9 +86,9 @@ private: CopyCallback _copyCallback; SubmitCallback _submitCallback; - ChildWidget _inner; - ChildWidget _filter; - ChildWidget _filterCancel; + class Inner; + ChildWidget _inner; + ChildWidget _select; ChildWidget _copy; ChildWidget _share; @@ -107,13 +112,15 @@ private: }; -namespace internal { - -class ShareInner : public ScrolledWidget, public RPCSender, private base::Subscriber { +// This class is hold in header because it requires Qt preprocessing. +class ShareBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: - ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallback); + Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback); + + void setPeerSelectedChangedCallback(base::lambda_unique callback); + void peerUnselected(PeerData *peer); QVector selected() const; bool hasSelected() const; @@ -126,16 +133,14 @@ public: void setVisibleTopBottom(int visibleTop, int visibleBottom) override; void updateFilter(QString filter = QString()); - ~ShareInner(); + ~Inner(); public slots: void onSelectActive(); signals: void mustScrollTo(int ymin, int ymax); - void filterCancel(); void searchByUsername(); - void selectedChanged(); protected: void paintEvent(QPaintEvent *e) override; @@ -151,36 +156,29 @@ private: int displayedChatsCount() const; - static constexpr int WideCacheScale = 4; struct Chat { - Chat(PeerData *peer); + Chat(PeerData *peer, base::lambda_wrap updateCallback); + PeerData *peer; + Ui::RoundImageCheckbox checkbox; Text name; - bool selected = false; - QPixmap wideUserpicCache; ColorAnimation nameFg; - FloatAnimation selection; - struct Icon { - FloatAnimation fadeIn; - FloatAnimation fadeOut; - QPixmap wideCheckCache; - }; - std_::vector_of_moveable icons; }; - void paintChat(Painter &p, Chat *chat, int index); + void paintChat(Painter &p, uint64 ms, Chat *chat, int index); void updateChat(PeerData *peer); void updateChatName(Chat *chat, PeerData *peer); void repaintChat(PeerData *peer); - void removeFadeOutedIcons(Chat *chat); - void prepareWideUserpicCache(Chat *chat); - void prepareWideCheckIconCache(Chat::Icon *icon); - void prepareWideCheckIcons(); int chatIndex(PeerData *peer) const; void repaintChatAtIndex(int index); Chat *getChatAtIndex(int index); void loadProfilePhotos(int yFrom); void changeCheckState(Chat *chat); + enum class ChangeStateWay { + Default, + SkipCallback, + }; + void changePeerCheckState(Chat *chat, bool checked, ChangeStateWay useCallback = ChangeStateWay::Default); Chat *getChat(Dialogs::Row *row); void setActive(int active); @@ -204,13 +202,13 @@ private: using FilteredDialogs = QVector; FilteredDialogs _filtered; - QPixmap _wideCheckCache, _wideCheckIconCache; - using DataMap = QMap; DataMap _dataMap; using SelectedChats = OrderedSet; SelectedChats _selected; + base::lambda_unique _peerSelectedChangedCallback; + ChatData *data(Dialogs::Row *row); bool _searching = false; @@ -221,5 +219,3 @@ private: ByUsernameDatas d_byUsernameFiltered; }; - -} // namespace internal diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp new file mode 100644 index 000000000..907a5f9d8 --- /dev/null +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -0,0 +1,1254 @@ +/* +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 "stickers_box.h" + +#include "lang.h" +#include "mainwidget.h" +#include "stickers/stickers.h" +#include "boxes/confirmbox.h" +#include "boxes/stickersetbox.h" +#include "apiwrap.h" +#include "localstorage.h" +#include "dialogs/dialogs_layout.h" +#include "styles/style_stickers.h" + +namespace { + +constexpr int kArchivedLimitFirstRequest = 10; +constexpr int kArchivedLimitPerPage = 30; + +} // namespace + +int32 stickerPacksCount(bool includeDisabledOfficial) { + int32 result = 0; + auto &order = Global::StickerSetsOrder(); + auto &sets = Global::StickerSets(); + for (int i = 0, l = order.size(); i < l; ++i) { + auto it = sets.constFind(order.at(i)); + if (it != sets.cend()) { + if (!(it->flags & MTPDstickerSet::Flag::f_archived) || ((it->flags & MTPDstickerSet::Flag::f_official) && includeDisabledOfficial)) { + ++result; + } + } + } + return result; +} + +StickersBox::StickersBox(Section section) : ItemListBox(st::boxScroll) +, _section(section) +, _inner(this, section) +, _aboutWidth(st::boxWideWidth - 2 * st::stickersReorderPadding.top()) +, _about(st::boxTextFont, lang((section == Section::Archived) ? lng_stickers_packs_archived : lng_stickers_reorder), _defaultOptions, _aboutWidth) { + setup(); +} + +StickersBox::StickersBox(const Stickers::Order &archivedIds) : ItemListBox(st::boxScroll) +, _section(Section::ArchivedPart) +, _inner(this, archivedIds) +, _aboutWidth(st::boxWideWidth - 2 * st::stickersReorderPadding.top()) +, _about(st::boxTextFont, lang(lng_stickers_packs_archived), _defaultOptions, _aboutWidth) { + setup(); +} + +void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result) { + _archivedRequestId = 0; + if (result.type() != mtpc_messages_archivedStickers) { + return; + } + + auto &stickers = result.c_messages_archivedStickers(); + auto &archived = Global::RefArchivedStickerSetsOrder(); + if (offsetId) { + auto index = archived.indexOf(offsetId); + if (index >= 0) { + archived = archived.mid(0, index + 1); + } + } else { + archived.clear(); + } + + bool addedSet = false; + auto &v = stickers.vsets.c_vector().v; + for_const (auto &stickerSet, v) { + const MTPDstickerSet *setData = nullptr; + switch (stickerSet.type()) { + case mtpc_stickerSetCovered: { + auto &d = stickerSet.c_stickerSetCovered(); + if (d.vset.type() == mtpc_stickerSet) { + setData = &d.vset.c_stickerSet(); + } + } break; + case mtpc_stickerSetMultiCovered: { + auto &d = stickerSet.c_stickerSetMultiCovered(); + if (d.vset.type() == mtpc_stickerSet) { + setData = &d.vset.c_stickerSet(); + } + } break; + } + if (!setData) continue; + + if (auto set = Stickers::feedSet(*setData)) { + auto index = archived.indexOf(set->id); + if (archived.isEmpty() || index != archived.size() - 1) { + if (index < archived.size() - 1) { + archived.removeAt(index); + } + archived.push_back(set->id); + } + if (_section == Section::Archived) { + if (_inner->appendSet(*set)) { + addedSet = true; + if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { + App::api()->scheduleStickerSetRequest(set->id, set->access); + } + } + } + } + } + if (_section == Section::Installed && !archived.isEmpty()) { + Local::writeArchivedStickers(); + rebuildList(); + } else if (_section == Section::Archived) { + if (addedSet) { + _inner->updateSize(); + setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); + _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + App::api()->requestStickerSets(); + } else { + _allArchivedLoaded = v.isEmpty() || (offsetId != 0); + } + } + checkLoadMoreArchived(); +} + +void StickersBox::setup() { + if (_section == Section::Installed) { + Local::readArchivedStickers(); + if (Global::ArchivedStickerSetsOrder().isEmpty()) { + MTPmessages_GetArchivedStickers::Flags flags = 0; + _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL)); + } + } else if (_section == Section::Archived) { + // Reload the archived list. + MTPmessages_GetArchivedStickers::Flags flags = 0; + _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL)); + + auto &sets = Global::StickerSets(); + for_const (auto setId, Global::ArchivedStickerSetsOrder()) { + auto it = sets.constFind(setId); + if (it != sets.cend()) { + if (it->stickers.isEmpty() && (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { + App::api()->scheduleStickerSetRequest(setId, it->access); + } + } + } + App::api()->requestStickerSets(); + } + + int bottomSkip = st::boxPadding.bottom(); + if (_section == Section::Installed) { + _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); + _topShadow.create(this, st::contactsAboutShadow); + + _save.create(this, lang(lng_settings_save), st::defaultBoxButton); + connect(_save, SIGNAL(clicked()), this, SLOT(onSave())); + + _cancel.create(this, lang(lng_cancel), st::cancelBoxButton); + connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + + _bottomShadow.create(this); + bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom(); + } else if (_section == Section::ArchivedPart) { + _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); + _topShadow.create(this, st::contactsAboutShadow); + + _save.create(this, lang(lng_box_ok), st::defaultBoxButton); + connect(_save, SIGNAL(clicked()), this, SLOT(onClose())); + } else if (_section == Section::Archived) { + _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); + _topShadow.create(this, st::contactsAboutShadow); + } + ItemListBox::init(_inner, bottomSkip, st::boxTitleHeight + _aboutHeight); + setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); + + connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); + App::main()->updateStickers(); + + connect(_inner, SIGNAL(checkDraggingScroll(int)), this, SLOT(onCheckDraggingScroll(int))); + connect(_inner, SIGNAL(noDraggingScroll()), this, SLOT(onNoDraggingScroll())); + connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); + connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); + _scrollTimer.setSingleShot(false); + + rebuildList(); + + prepare(); +} + +void StickersBox::onScroll() { + updateVisibleTopBottom(); + checkLoadMoreArchived(); +} + +void StickersBox::updateVisibleTopBottom() { + auto visibleTop = scrollArea()->scrollTop(); + auto visibleBottom = visibleTop + scrollArea()->height(); + _inner->setVisibleTopBottom(visibleTop, visibleBottom); +} + +void StickersBox::checkLoadMoreArchived() { + if (_section != Section::Archived) return; + + int scrollTop = scrollArea()->scrollTop(), scrollTopMax = scrollArea()->scrollTopMax(); + if (scrollTop + PreloadHeightsCount * scrollArea()->height() >= scrollTopMax) { + if (!_archivedRequestId && !_allArchivedLoaded) { + uint64 lastId = 0; + for (auto setId = Global::ArchivedStickerSetsOrder().cend(), e = Global::ArchivedStickerSetsOrder().cbegin(); setId != e;) { + --setId; + auto it = Global::StickerSets().constFind(*setId); + if (it != Global::StickerSets().cend()) { + if (it->flags & MTPDstickerSet::Flag::f_archived) { + lastId = it->id; + break; + } + } + } + MTPmessages_GetArchivedStickers::Flags flags = 0; + _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(lastId), MTP_int(kArchivedLimitPerPage)), rpcDone(&StickersBox::getArchivedDone, lastId)); + } + } +} + +int32 StickersBox::countHeight() const { + int bottomSkip = st::boxPadding.bottom(); + if (_section == Section::Installed) { + bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom(); + } + return st::boxTitleHeight + _aboutHeight + _inner->height() + bottomSkip; +} + +void StickersBox::disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req) { + _disenableRequests.remove(req); + if (_disenableRequests.isEmpty()) { + saveOrder(); + } +} + +bool StickersBox::disenableFail(const RPCError &error, mtpRequestId req) { + if (MTP::isDefaultHandledError(error)) return false; + _disenableRequests.remove(req); + if (_disenableRequests.isEmpty()) { + saveOrder(); + } + return true; +} + +void StickersBox::saveOrder() { + auto order = _inner->getOrder(); + if (order.size() > 1) { + QVector mtpOrder; + mtpOrder.reserve(order.size()); + for (int i = 0, l = order.size(); i < l; ++i) { + mtpOrder.push_back(MTP_long(order.at(i))); + } + + MTPmessages_ReorderStickerSets::Flags flags = 0; + _reorderRequest = MTP::send(MTPmessages_ReorderStickerSets(MTP_flags(flags), MTP_vector(mtpOrder)), rpcDone(&StickersBox::reorderDone), rpcFail(&StickersBox::reorderFail)); + } else { + reorderDone(MTP_boolTrue()); + } +} + +void StickersBox::reorderDone(const MTPBool &result) { + _reorderRequest = 0; + onClose(); +} + +bool StickersBox::reorderFail(const RPCError &result) { + if (MTP::isDefaultHandledError(result)) return false; + _reorderRequest = 0; + Global::SetLastStickersUpdate(0); + App::main()->updateStickers(); + onClose(); + return true; +} + +void StickersBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + auto title = ([this]() { + if (_section == Section::Installed) { + return lang(lng_stickers_packs); + } else if (_section == Section::Featured) { + return lang(lng_stickers_featured); + } + return lang(lng_stickers_archived); + })(); + paintTitle(p, title); + p.translate(0, st::boxTitleHeight); + + if (_aboutHeight > 0) { + p.fillRect(0, 0, width(), _aboutHeight, st::contactsAboutBg); + p.setPen(st::stickersReorderFg); + _about.draw(p, st::stickersReorderPadding.top(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); + } +} + +void StickersBox::closePressed() { + if (!_disenableRequests.isEmpty()) { + for_const (auto requestId, _disenableRequests) { + MTP::cancel(requestId); + } + _disenableRequests.clear(); + Global::SetLastStickersUpdate(0); + App::main()->updateStickers(); + } else if (_reorderRequest) { + MTP::cancel(_reorderRequest); + _reorderRequest = 0; + Global::SetLastStickersUpdate(0); + App::main()->updateStickers(); + } +} + +StickersBox::~StickersBox() { + if (_section == Section::Archived) { + Local::writeArchivedStickers(); + } +} + +void StickersBox::resizeEvent(QResizeEvent *e) { + ItemListBox::resizeEvent(e); + _inner->resize(width(), _inner->height()); + _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + updateVisibleTopBottom(); + if (_topShadow) { + _topShadow->setGeometry(0, st::boxTitleHeight + _aboutHeight, width(), st::lineWidth); + } + if (_save) { + _save->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save->height()); + } + if (_cancel) { + _cancel->moveToRight(st::boxButtonPadding.right() + _save->width() + st::boxButtonPadding.left(), _save->y()); + _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _save->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); + } +} + +void StickersBox::onStickersUpdated() { + if (_section == Section::Installed || _section == Section::Featured) { + rebuildList(); + } else { + _inner->updateRows(); + } +} + +void StickersBox::rebuildList() { + _inner->rebuild(); + setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); + _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); +} + +void StickersBox::onCheckDraggingScroll(int localY) { + if (localY < scrollArea()->scrollTop()) { + _scrollDelta = localY - scrollArea()->scrollTop(); + } else if (localY >= scrollArea()->scrollTop() + scrollArea()->height()) { + _scrollDelta = localY - scrollArea()->scrollTop() - scrollArea()->height() + 1; + } else { + _scrollDelta = 0; + } + if (_scrollDelta) { + _scrollTimer.start(15); + } else { + _scrollTimer.stop(); + } +} + +void StickersBox::onNoDraggingScroll() { + _scrollTimer.stop(); +} + +void StickersBox::onScrollTimer() { + int32 d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed)); + scrollArea()->scrollToY(scrollArea()->scrollTop() + d); +} + +void StickersBox::onSave() { + if (!_inner->savingStart()) { + return; + } + + bool writeRecent = false, writeArchived = false; + auto &recent = cGetRecentStickers(); + auto &sets = Global::RefStickerSets(); + + auto reorder = _inner->getOrder(), disabled = _inner->getDisabledSets(); + for (int32 i = 0, l = disabled.size(); i < l; ++i) { + auto it = sets.find(disabled.at(i)); + if (it != sets.cend()) { + for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) { + if (it->stickers.indexOf(i->first) >= 0) { + i = recent.erase(i); + writeRecent = true; + } else { + ++i; + } + } + if (!(it->flags & MTPDstickerSet::Flag::f_archived)) { + MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); + if (it->flags & MTPDstickerSet::Flag::f_official) { + _disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolTrue()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5)); + it->flags |= MTPDstickerSet::Flag::f_archived; + auto index = Global::RefArchivedStickerSetsOrder().indexOf(it->id); + if (index < 0) { + Global::RefArchivedStickerSetsOrder().push_front(it->id); + writeArchived = true; + } + } else { + _disenableRequests.insert(MTP::send(MTPmessages_UninstallStickerSet(setId), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5)); + int removeIndex = Global::StickerSetsOrder().indexOf(it->id); + if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); + if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { + sets.erase(it); + } else { + if (it->flags & MTPDstickerSet::Flag::f_archived) { + writeArchived = true; + } + it->flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet::Flag::f_archived); + } + } + } + } + } + + // Clear all installed flags, set only for sets from order. + for (auto &set : sets) { + if (!(set.flags & MTPDstickerSet::Flag::f_archived)) { + set.flags &= ~MTPDstickerSet::Flag::f_installed; + } + } + + auto &order(Global::RefStickerSetsOrder()); + order.clear(); + for (int i = 0, l = reorder.size(); i < l; ++i) { + auto it = sets.find(reorder.at(i)); + if (it != sets.cend()) { + if ((it->flags & MTPDstickerSet::Flag::f_archived) && !disabled.contains(it->id)) { + MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); + _disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolFalse()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5)); + it->flags &= ~MTPDstickerSet::Flag::f_archived; + writeArchived = true; + } + order.push_back(reorder.at(i)); + it->flags |= MTPDstickerSet::Flag::f_installed; + } + } + for (auto it = sets.begin(); it != sets.cend();) { + if ((it->flags & MTPDstickerSet_ClientFlag::f_featured) + || (it->flags & MTPDstickerSet::Flag::f_installed) + || (it->flags & MTPDstickerSet::Flag::f_archived) + || (it->flags & MTPDstickerSet_ClientFlag::f_special)) { + ++it; + } else { + it = sets.erase(it); + } + } + + Local::writeInstalledStickers(); + if (writeRecent) Local::writeUserSettings(); + if (writeArchived) Local::writeArchivedStickers(); + emit App::main()->stickersUpdated(); + + if (_disenableRequests.isEmpty()) { + saveOrder(); + } else { + MTP::sendAnything(); + } +} + +void StickersBox::showAll() { + if (_topShadow) { + _topShadow->show(); + } + if (_save) { + _save->show(); + } + if (_cancel) { + _cancel->show(); + _bottomShadow->show(); + } + ItemListBox::showAll(); +} + +StickersBox::Inner::Inner(QWidget *parent, StickersBox::Section section) : ScrolledWidget(parent) +, _section(section) +, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) +, _a_shifting(animation(this, &Inner::step_shifting)) +, _itemsTop(st::membersPadding.top()) +, _clearWidth(st::normalFont->width(lang(lng_stickers_clear_recent))) +, _removeWidth(st::normalFont->width(lang(lng_stickers_remove))) +, _returnWidth(st::normalFont->width(lang(lng_stickers_return))) +, _restoreWidth(st::normalFont->width(lang(lng_stickers_restore))) +, _aboveShadow(st::boxShadow) { + setup(); +} + +StickersBox::Inner::Inner(QWidget *parent, const Stickers::Order &archivedIds) : ScrolledWidget(parent) +, _section(StickersBox::Section::ArchivedPart) +, _archivedIds(archivedIds) +, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) +, _a_shifting(animation(this, &Inner::step_shifting)) +, _itemsTop(st::membersPadding.top()) +, _clearWidth(st::normalFont->width(lang(lng_stickers_clear_recent))) +, _removeWidth(st::normalFont->width(lang(lng_stickers_remove))) +, _returnWidth(st::normalFont->width(lang(lng_stickers_return))) +, _restoreWidth(st::normalFont->width(lang(lng_stickers_restore))) +, _aboveShadow(st::boxShadow) { + setup(); +} + +void StickersBox::Inner::setup() { + subscribe(FileDownload::ImageLoaded(), [this] { update(); }); + setMouseTracking(true); +} + +void StickersBox::Inner::onImageLoaded() { + update(); + readVisibleSets(); +} + +void StickersBox::Inner::paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const { + if (selected) { + p.fillRect(0, y, width(), _buttonHeight, st::contactsBgOver); + } + p.setFont(st::stickersFeaturedFont); + p.setPen(st::stickersFeaturedPen); + p.drawTextLeft(st::stickersFeaturedPosition.x(), y + st::stickersFeaturedPosition.y(), width(), text); + + if (badgeCounter) { + Dialogs::Layout::UnreadBadgeStyle unreadSt; + unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox; + unreadSt.size = st::stickersFeaturedBadgeSize; + int unreadRight = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x()); + if (rtl()) unreadRight = width() - unreadRight; + int unreadTop = y + (_buttonHeight - st::stickersFeaturedBadgeSize) / 2; + Dialogs::Layout::paintUnreadCount(p, QString::number(badgeCounter), unreadRight, unreadTop, unreadSt); + } +} + +void StickersBox::Inner::paintEvent(QPaintEvent *e) { + QRect r(e->rect()); + Painter p(this); + + _a_shifting.step(); + + p.fillRect(r, st::white); + p.setClipRect(r); + + int y = st::membersPadding.top(); + if (_hasFeaturedButton) { + auto selected = (_selected == -2); + paintButton(p, y, selected, lang(lng_stickers_featured), Global::FeaturedStickerSetsUnreadCount()); + y += _buttonHeight; + } + if (_hasArchivedButton) { + auto selected = (_selected == -1); + paintButton(p, y, selected, lang(lng_stickers_archived), 0); + y += _buttonHeight; + } + + if (_rows.isEmpty()) { + p.setFont(st::noContactsFont); + p.setPen(st::noContactsColor); + p.drawText(QRect(0, y, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); + } else { + p.translate(0, _itemsTop); + + int32 yFrom = r.y() - _itemsTop, yTo = r.y() + r.height() - _itemsTop; + int32 from = floorclamp(yFrom - _rowHeight, _rowHeight, 0, _rows.size()); + int32 to = ceilclamp(yTo + _rowHeight, _rowHeight, 0, _rows.size()); + p.translate(0, from * _rowHeight); + for (int32 i = from; i < to; ++i) { + if (i != _above) { + paintRow(p, i); + } + p.translate(0, _rowHeight); + } + if (from <= _above && _above < to) { + p.translate(0, (_above - to) * _rowHeight); + paintRow(p, _above); + } + } +} + +void StickersBox::Inner::paintRow(Painter &p, int32 index) { + const StickerSetRow *s(_rows.at(index)); + + int32 xadd = 0, yadd = s->yadd.current(); + if (xadd || yadd) p.translate(xadd, yadd); + + if (_section == Section::Installed) { + bool removeSel = (index == _actionSel && (_actionDown < 0 || index == _actionDown)); + bool removeDown = removeSel && (index == _actionDown); + + p.setFont(removeSel ? st::linkOverFont : st::linkFont); + if (removeDown) { + p.setPen(st::btnDefLink.downColor); + } else { + p.setPen(st::btnDefLink.color); + } + int32 remWidth = s->recent ? _clearWidth : (s->disabled ? (s->official ? _restoreWidth : _returnWidth) : _removeWidth); + QString remText = lang(s->recent ? lng_stickers_clear_recent : (s->disabled ? (s->official ? lng_stickers_restore : lng_stickers_return) : lng_stickers_remove)); + p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), remText, remWidth); + + if (index == _above) { + float64 current = _aboveShadowFadeOpacity.current(); + if (_started >= 0) { + float64 o = aboveShadowOpacity(); + if (o > current) { + _aboveShadowFadeOpacity = anim::fvalue(o, o); + current = o; + } + } + p.setOpacity(current); + QRect row(myrtlrect(_aboveShadow.getDimensions(st::boxShadowShift).left(), st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - _aboveShadow.getDimensions(st::boxShadowShift).right(), _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2))); + _aboveShadow.paint(p, row, st::boxShadowShift); + p.fillRect(row, st::white); + p.setOpacity(1); + } + } else if (s->installed && !s->disabled) { + int addw = st::stickersAddSize.width(); + int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (addw + st::stickersFeaturedInstalled.width()) / 2); + int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2; + st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width()); + } else { + int addw = st::stickersAddSize.width(); + int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw; + int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersAddSize.height()) / 2; + QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height())); + + auto textBg = (_actionSel == index) ? st::defaultActiveButton.textBgOver : st::defaultActiveButton.textBg; + App::roundRect(p, add, textBg, ImageRoundRadius::Small); + int iconx = addx + (st::stickersAddSize.width() - st::stickersAddIcon.width()) / 2; + int icony = addy + (st::stickersAddSize.height() - st::stickersAddIcon.height()) / 2; + icony += (_actionSel == index && _actionDown == index) ? (st::defaultActiveButton.downTextTop - st::defaultActiveButton.textTop) : 0; + st::stickersAddIcon.paint(p, QPoint(iconx, icony), width()); + } + + if (s->disabled && _section == Section::Installed) { + p.setOpacity(st::stickersRowDisabledOpacity); + } + if (s->sticker) { + s->sticker->thumb->load(); + QPixmap pix(s->sticker->thumb->pix(s->pixw, s->pixh)); + p.drawPixmapLeft(st::contactsPadding.left() + (st::contactsPhotoSize - s->pixw) / 2, st::contactsPadding.top() + (st::contactsPhotoSize - s->pixh) / 2, width(), pix); + } + + int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); + int namey = st::contactsPadding.top() + st::contactsNameTop; + int statusx = namex; + int statusy = st::contactsPadding.top() + st::contactsStatusTop; + + p.setFont(st::contactsNameFont); + p.setPen(st::black); + p.drawTextLeft(namex, namey, width(), s->title, s->titleWidth); + + if (s->unread) { + p.setPen(Qt::NoPen); + p.setBrush(st::stickersFeaturedUnreadBg); + + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + p.drawEllipse(rtlrect(namex + s->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + } + + p.setFont(st::contactsStatusFont); + p.setPen(st::contactsStatusFg); + p.drawTextLeft(statusx, statusy, width(), lng_stickers_count(lt_count, s->count)); + + p.setOpacity(1); + if (xadd || yadd) p.translate(-xadd, -yadd); +} + +void StickersBox::Inner::mousePressEvent(QMouseEvent *e) { + if (_saving) return; + if (_dragging >= 0) mouseReleaseEvent(e); + _mouse = e->globalPos(); + onUpdateSelected(); + + _pressed = _selected; + if (_actionSel >= 0) { + _actionDown = _actionSel; + update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); + } else if (_selected >= 0 && _section == Section::Installed && !_rows.at(_selected)->recent) { + _above = _dragging = _started = _selected; + _dragStart = mapFromGlobal(_mouse); + } +} + +void StickersBox::Inner::mouseMoveEvent(QMouseEvent *e) { + if (_saving) return; + _mouse = e->globalPos(); + onUpdateSelected(); +} + +void StickersBox::Inner::onUpdateSelected() { + if (_saving) return; + QPoint local(mapFromGlobal(_mouse)); + if (_dragging >= 0) { + int32 shift = 0; + uint64 ms = getms(); + int firstSetIndex = 0; + if (_rows.at(firstSetIndex)->recent) { + ++firstSetIndex; + } + if (_dragStart.y() > local.y() && _dragging > 0) { + shift = -floorclamp(_dragStart.y() - local.y() + (_rowHeight / 2), _rowHeight, 0, _dragging - firstSetIndex); + for (int32 from = _dragging, to = _dragging + shift; from > to; --from) { + qSwap(_rows[from], _rows[from - 1]); + _rows.at(from)->yadd = anim::ivalue(_rows.at(from)->yadd.current() - _rowHeight, 0); + _animStartTimes[from] = ms; + } + } else if (_dragStart.y() < local.y() && _dragging + 1 < _rows.size()) { + shift = floorclamp(local.y() - _dragStart.y() + (_rowHeight / 2), _rowHeight, 0, _rows.size() - _dragging - 1); + for (int32 from = _dragging, to = _dragging + shift; from < to; ++from) { + qSwap(_rows[from], _rows[from + 1]); + _rows.at(from)->yadd = anim::ivalue(_rows.at(from)->yadd.current() + _rowHeight, 0); + _animStartTimes[from] = ms; + } + } + if (shift) { + _dragging += shift; + _above = _dragging; + _dragStart.setY(_dragStart.y() + shift * _rowHeight); + if (!_a_shifting.animating()) { + _a_shifting.start(); + } + } + _rows.at(_dragging)->yadd = anim::ivalue(local.y() - _dragStart.y(), local.y() - _dragStart.y()); + _animStartTimes[_dragging] = 0; + _a_shifting.step(getms(), true); + + emit checkDraggingScroll(local.y()); + } else { + bool in = rect().marginsRemoved(QMargins(0, _itemsTop, 0, st::membersPadding.bottom())).contains(local); + int selected = -2; + int actionSel = -1; + if (in) { + selected = floorclamp(local.y() - _itemsTop, _rowHeight, 0, _rows.size() - 1); + + if (_section == Section::Installed) { + int remw = _rows.at(selected)->recent ? _clearWidth : (_rows.at(selected)->disabled ? (_rows.at(selected)->official ? _restoreWidth : _returnWidth) : _removeWidth); + QRect rem(myrtlrect(width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - remw, st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, remw, st::normalFont->height)); + actionSel = rem.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1; + } else if (_rows.at(selected)->installed && !_rows.at(selected)->disabled) { + actionSel = -1; + } else { + int addw = st::stickersAddSize.width(); + int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw; + int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersAddSize.height()) / 2; + QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height())); + actionSel = add.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1; + } + } else if (_hasFeaturedButton && QRect(0, st::membersPadding.top(), width(), _buttonHeight).contains(local)) { + selected = -2; + } else if (_hasArchivedButton && QRect(0, st::membersPadding.top() + (_hasFeaturedButton ? _buttonHeight : 0), width(), _buttonHeight).contains(local)) { + selected = -1; + } else { + selected = -3; + } + if (_selected != selected) { + if (((_selected == -1) != (selected == -1)) || ((_selected == -2) != (selected == -2))) { + update(); + } + if (_section != Section::Installed && ((_selected >= 0 || _pressed >= 0) != (selected >= 0 || _pressed >= 0))) { + setCursor((selected >= 0 || _pressed >= 0) ? style::cur_pointer : style::cur_default); + } + _selected = selected; + } + setActionSel(actionSel); + emit noDraggingScroll(); + } +} + +void StickersBox::Inner::onClearRecent() { + if (_clearBox) { + _clearBox->onClose(); + } + + auto &sets = Global::RefStickerSets(); + bool removedCloud = (sets.remove(Stickers::CloudRecentSetId) != 0); + bool removedCustom = (sets.remove(Stickers::CustomSetId) != 0); + + auto &recent = cGetRecentStickers(); + if (!recent.isEmpty()) { + recent.clear(); + Local::writeUserSettings(); + } + + if (removedCustom) Local::writeInstalledStickers(); + if (removedCloud) Local::writeRecentStickers(); + emit App::main()->updateStickers(); + rebuild(); + + MTPmessages_ClearRecentStickers::Flags flags = 0; + MTP::send(MTPmessages_ClearRecentStickers(MTP_flags(flags))); +} + +void StickersBox::Inner::onClearBoxDestroyed(QObject *box) { + if (box == _clearBox) { + _clearBox = nullptr; + } +} + +float64 StickersBox::Inner::aboveShadowOpacity() const { + if (_above < 0) return 0; + + int32 dx = 0; + int32 dy = qAbs(_above * _rowHeight + _rows.at(_above)->yadd.current() - _started * _rowHeight); + return qMin((dx + dy) * 2. / _rowHeight, 1.); +} + +void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) { + auto pressed = _pressed; + _pressed = -2; + + if (_section != Section::Installed && _selected < 0 && pressed >= 0) { + setCursor(style::cur_default); + } + + if (_saving) return; + + _mouse = e->globalPos(); + onUpdateSelected(); + if (_actionDown == _actionSel && _actionSel >= 0) { + if (_section == Section::Installed) { + if (_rows[_actionDown]->recent) { + _clearBox = new ConfirmBox(lang(lng_stickers_clear_recent_sure), lang(lng_stickers_clear_recent)); + connect(_clearBox, SIGNAL(confirmed()), this, SLOT(onClearRecent())); + connect(_clearBox, SIGNAL(destroyed(QObject*)), this, SLOT(onClearBoxDestroyed(QObject*))); + Ui::showLayer(_clearBox, KeepOtherLayers); + } else { + _rows[_actionDown]->disabled = !_rows[_actionDown]->disabled; + } + } else { + installSet(_rows[_actionDown]->id); + } + } else if (_dragging >= 0) { + QPoint local(mapFromGlobal(_mouse)); + _rows[_dragging]->yadd.start(0); + _aboveShadowFadeStart = _animStartTimes[_dragging] = getms(); + _aboveShadowFadeOpacity = anim::fvalue(aboveShadowOpacity(), 0); + if (!_a_shifting.animating()) { + _a_shifting.start(); + } + + _dragging = _started = -1; + } else if (pressed == _selected && _actionSel < 0 && _actionDown < 0) { + if (_selected == -2) { + _selected = -3; + Ui::showLayer(new StickersBox(Section::Featured), KeepOtherLayers); + } else if (_selected == -1) { + _selected = -3; + Ui::showLayer(new StickersBox(Section::Archived), KeepOtherLayers); + } else if (_selected >= 0 && _section != Section::Installed) { + auto &sets = Global::RefStickerSets(); + auto it = sets.find(_rows.at(pressed)->id); + if (it != sets.cend()) { + _selected = -3; + Ui::showLayer(new StickerSetBox(Stickers::inputSetId(*it)), KeepOtherLayers); + } + } + } + if (_actionDown >= 0) { + update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight); + _actionDown = -1; + } +} + +void StickersBox::Inner::leaveEvent(QEvent *e) { + _mouse = QPoint(-1, -1); + onUpdateSelected(); +} + +void StickersBox::Inner::installSet(uint64 setId) { + auto &sets = Global::RefStickerSets(); + auto it = sets.find(setId); + if (it == sets.cend()) { + rebuild(); + return; + } + + MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse()), rpcDone(&Inner::installDone), rpcFail(&Inner::installFail, setId)); + + Stickers::installLocally(setId); +} + +void StickersBox::Inner::installDone(const MTPmessages_StickerSetInstallResult &result) { + if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { + Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); + } +} + +bool StickersBox::Inner::installFail(uint64 setId, const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + auto &sets = Global::RefStickerSets(); + auto it = sets.find(setId); + if (it == sets.cend()) { + rebuild(); + return true; + } + + Stickers::undoInstallLocally(setId); + return true; +} + +void StickersBox::Inner::step_shifting(uint64 ms, bool timer) { + bool animating = false; + int32 updateMin = -1, updateMax = 0; + for (int32 i = 0, l = _animStartTimes.size(); i < l; ++i) { + uint64 start = _animStartTimes.at(i); + if (start) { + if (updateMin < 0) updateMin = i; + updateMax = i; + if (start + st::stickersRowDuration > ms && ms >= start) { + _rows.at(i)->yadd.update(float64(ms - start) / st::stickersRowDuration, anim::sineInOut); + animating = true; + } else { + _rows.at(i)->yadd.finish(); + _animStartTimes[i] = 0; + } + } + } + if (_aboveShadowFadeStart) { + if (updateMin < 0 || updateMin > _above) updateMin = _above; + if (updateMax < _above) updateMin = _above; + if (_aboveShadowFadeStart + st::stickersRowDuration > ms && ms > _aboveShadowFadeStart) { + _aboveShadowFadeOpacity.update(float64(ms - _aboveShadowFadeStart) / st::stickersRowDuration, anim::sineInOut); + animating = true; + } else { + _aboveShadowFadeOpacity.finish(); + _aboveShadowFadeStart = 0; + } + } + if (timer) { + if (_dragging >= 0) { + if (updateMin < 0 || updateMin > _dragging) updateMin = _dragging; + if (updateMax < _dragging) updateMax = _dragging; + } + if (updateMin >= 0) { + update(0, _itemsTop + _rowHeight * (updateMin - 1), width(), _rowHeight * (updateMax - updateMin + 3)); + } + } + if (!animating) { + _above = _dragging; + _a_shifting.stop(); + } +} + +void StickersBox::Inner::clear() { + for (int32 i = 0, l = _rows.size(); i < l; ++i) { + delete _rows.at(i); + } + _rows.clear(); + _animStartTimes.clear(); + _aboveShadowFadeStart = 0; + _aboveShadowFadeOpacity = anim::fvalue(0, 0); + _a_shifting.stop(); + _above = _dragging = _started = -1; + _selected = -3; + _pressed = -3; + _actionDown = -1; + setActionSel(-1); + update(); +} + +void StickersBox::Inner::setActionSel(int32 actionSel) { + if (actionSel != _actionSel) { + if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); + _actionSel = actionSel; + if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); + if (_section == Section::Installed) { + setCursor((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel)) ? style::cur_pointer : style::cur_default); + } + } +} + +void StickersBox::Inner::rebuild() { + _hasFeaturedButton = _hasArchivedButton = false; + _itemsTop = st::membersPadding.top(); + _buttonHeight = st::stickersFeaturedHeight; + if (_section == Section::Installed) { + if (!Global::FeaturedStickerSetsOrder().isEmpty()) { + _itemsTop += _buttonHeight; + _hasFeaturedButton = true; + } + if (!Global::ArchivedStickerSetsOrder().isEmpty()) { + _itemsTop += _buttonHeight; + _hasArchivedButton = true; + } + if (_itemsTop > st::membersPadding.top()) { + _itemsTop += st::membersPadding.top(); + } + } + + int maxNameWidth = countMaxNameWidth(); + + clear(); + auto &order = ([this]() -> const Stickers::Order & { + if (_section == Section::Installed) { + return Global::StickerSetsOrder(); + } else if (_section == Section::Featured) { + return Global::FeaturedStickerSetsOrder(); + } else if (_section == Section::Archived) { + return Global::ArchivedStickerSetsOrder(); + } + return _archivedIds; + })(); + _rows.reserve(order.size() + 1); + _animStartTimes.reserve(order.size() + 1); + + auto &sets = Global::StickerSets(); + if (_section == Section::Installed) { + auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); + if (cloudIt != sets.cend() && !cloudIt->stickers.isEmpty()) { + rebuildAppendSet(cloudIt.value(), maxNameWidth); + } + } + for_const (auto setId, order) { + auto it = sets.constFind(setId); + if (it == sets.cend()) { + continue; + } + + rebuildAppendSet(it.value(), maxNameWidth); + + if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { + App::api()->scheduleStickerSetRequest(it->id, it->access); + } + } + App::api()->requestStickerSets(); + updateSize(); +} + +void StickersBox::Inner::updateSize() { + resize(width(), _itemsTop + _rows.size() * _rowHeight + st::membersPadding.bottom()); +} + +void StickersBox::Inner::updateRows() { + int maxNameWidth = countMaxNameWidth(); + auto &sets = Global::StickerSets(); + for_const (auto row, _rows) { + auto it = sets.constFind(row->id); + if (it != sets.cend()) { + auto &set = it.value(); + if (!row->sticker) { + DocumentData *sticker = nullptr; + int pixw = 0, pixh = 0; + fillSetCover(set, &sticker, &pixw, &pixh); + if (sticker) { + row->sticker = sticker; + row->pixw = pixw; + row->pixh = pixh; + } + } + fillSetFlags(set, &row->recent, &row->installed, &row->official, &row->unread, &row->disabled); + if (_section == Section::Installed) { + row->disabled = false; + } + row->title = fillSetTitle(set, maxNameWidth, &row->titleWidth); + row->count = fillSetCount(set); + } + } + update(); +} + +bool StickersBox::Inner::appendSet(const Stickers::Set &set) { + for_const (auto row, _rows) { + if (row->id == set.id) { + return false; + } + } + rebuildAppendSet(set, countMaxNameWidth()); + return true; +} + +int StickersBox::Inner::countMaxNameWidth() const { + int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); + int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x(); + if (_section == Section::Installed) { + namew -= qMax(qMax(qMax(_returnWidth, _removeWidth), _restoreWidth), _clearWidth); + } else { + namew -= st::stickersAddIcon.width() - st::defaultActiveButton.width; + namew -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; + } + return namew; +} + +void StickersBox::Inner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) { + bool recent = false, installed = false, official = false, unread = false, disabled = false; + fillSetFlags(set, &recent, &installed, &official, &unread, &disabled); + if (_section == Section::Installed && disabled) { + return; + } + + DocumentData *sticker = nullptr; + int pixw = 0, pixh = 0; + fillSetCover(set, &sticker, &pixw, &pixh); + + int titleWidth = 0; + QString title = fillSetTitle(set, maxNameWidth, &titleWidth); + int count = fillSetCount(set); + + _rows.push_back(new StickerSetRow(set.id, sticker, count, title, titleWidth, installed, official, unread, disabled, recent, pixw, pixh)); + _animStartTimes.push_back(0); +} + +void StickersBox::Inner::fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const { + if (set.stickers.isEmpty()) { + *outSticker = nullptr; + *outWidth = *outHeight = 0; + return; + } + auto sticker = *outSticker = set.stickers.front(); + + auto pixw = sticker->thumb->width(); + auto pixh = sticker->thumb->height(); + if (pixw > st::contactsPhotoSize) { + if (pixw > pixh) { + pixh = (pixh * st::contactsPhotoSize) / pixw; + pixw = st::contactsPhotoSize; + } else { + pixw = (pixw * st::contactsPhotoSize) / pixh; + pixh = st::contactsPhotoSize; + } + } else if (pixh > st::contactsPhotoSize) { + pixw = (pixw * st::contactsPhotoSize) / pixh; + pixh = st::contactsPhotoSize; + } + *outWidth = pixw; + *outHeight = pixh; +} + +int StickersBox::Inner::fillSetCount(const Stickers::Set &set) const { + int result = set.stickers.isEmpty() ? set.count : set.stickers.size(), added = 0; + if (set.id == Stickers::CloudRecentSetId) { + auto customIt = Global::StickerSets().constFind(Stickers::CustomSetId); + if (customIt != Global::StickerSets().cend()) { + added = customIt->stickers.size(); + for_const (auto &sticker, cGetRecentStickers()) { + if (customIt->stickers.indexOf(sticker.first) < 0) { + ++added; + } + } + } else { + added = cGetRecentStickers().size(); + } + } + return result + added; +} + +QString StickersBox::Inner::fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const { + auto result = set.title; + int titleWidth = st::contactsNameFont->width(result); + if (titleWidth > maxNameWidth) { + result = st::contactsNameFont->elided(result, maxNameWidth); + titleWidth = st::contactsNameFont->width(result); + } + if (outTitleWidth) { + *outTitleWidth = titleWidth; + } + return result; +} + +void StickersBox::Inner::fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled) { + *outRecent = (set.id == Stickers::CloudRecentSetId); + *outInstalled = true; + *outOfficial = true; + *outUnread = false; + *outDisabled = false; + if (!*outRecent) { + *outInstalled = (set.flags & MTPDstickerSet::Flag::f_installed); + *outOfficial = (set.flags & MTPDstickerSet::Flag::f_official); + *outDisabled = (set.flags & MTPDstickerSet::Flag::f_archived); + if (_section == Section::Featured) { + *outUnread = (set.flags & MTPDstickerSet_ClientFlag::f_unread); + } + } +} + +Stickers::Order StickersBox::Inner::getOrder() const { + Stickers::Order result; + result.reserve(_rows.size()); + for (int32 i = 0, l = _rows.size(); i < l; ++i) { + if (_rows.at(i)->disabled || _rows.at(i)->recent) { + continue; + } + result.push_back(_rows.at(i)->id); + } + return result; +} + +Stickers::Order StickersBox::Inner::getDisabledSets() const { + Stickers::Order result; + result.reserve(_rows.size()); + for (int32 i = 0, l = _rows.size(); i < l; ++i) { + if (_rows.at(i)->disabled) { + result.push_back(_rows.at(i)->id); + } + } + return result; +} + +void StickersBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + if (_section == Section::Featured) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; + readVisibleSets(); + } +} + +void StickersBox::Inner::readVisibleSets() { + auto itemsVisibleTop = _visibleTop - _itemsTop; + auto itemsVisibleBottom = _visibleBottom - _itemsTop; + int rowFrom = floorclamp(itemsVisibleTop, _rowHeight, 0, _rows.size()); + int rowTo = ceilclamp(itemsVisibleBottom, _rowHeight, 0, _rows.size()); + for (int i = rowFrom; i < rowTo; ++i) { + if (!_rows[i]->unread) { + continue; + } + if (i * _rowHeight < itemsVisibleTop || (i + 1) * _rowHeight > itemsVisibleBottom) { + continue; + } + if (!_rows[i]->sticker || _rows[i]->sticker->thumb->loaded() || _rows[i]->sticker->loaded()) { + Stickers::markFeaturedAsRead(_rows[i]->id); + } + } +} + +void StickersBox::Inner::setVisibleScrollbar(int32 width) { + _scrollbar = width; +} + +StickersBox::Inner::~Inner() { + clear(); +} diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h new file mode 100644 index 000000000..3524a7db8 --- /dev/null +++ b/Telegram/SourceFiles/boxes/stickers_box.h @@ -0,0 +1,239 @@ +/* +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 "abstractbox.h" + +class ConfirmBox; + +namespace Ui { +class PlainShadow; +} // namespace Ui + +class StickersBox : public ItemListBox, public RPCSender { + Q_OBJECT + +public: + enum class Section { + Installed, + Featured, + Archived, + ArchivedPart, + }; + StickersBox(Section section = Section::Installed); + StickersBox(const Stickers::Order &archivedIds); + + ~StickersBox(); + +public slots: + void onStickersUpdated(); + + void onCheckDraggingScroll(int localY); + void onNoDraggingScroll(); + void onScrollTimer(); + + void onSave(); + +private slots: + void onScroll(); + +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + + void closePressed() override; + void showAll() override; + +private: + void setup(); + int32 countHeight() const; + void rebuildList(); + + void disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req); + bool disenableFail(const RPCError &error, mtpRequestId req); + void reorderDone(const MTPBool &result); + bool reorderFail(const RPCError &result); + void saveOrder(); + + void updateVisibleTopBottom(); + void checkLoadMoreArchived(); + void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result); + + Section _section; + + class Inner; + ChildWidget _inner; + ChildWidget _save = { nullptr }; + ChildWidget _cancel = { nullptr }; + OrderedSet _disenableRequests; + mtpRequestId _reorderRequest = 0; + ChildWidget _topShadow = { nullptr }; + ChildWidget _bottomShadow = { nullptr }; + + QTimer _scrollTimer; + int32 _scrollDelta = 0; + + int _aboutWidth = 0; + Text _about; + int _aboutHeight = 0; + + mtpRequestId _archivedRequestId = 0; + bool _allArchivedLoaded = false; + +}; + +int32 stickerPacksCount(bool includeDisabledOfficial = false); + +// This class is hold in header because it requires Qt preprocessing. +class StickersBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { + Q_OBJECT + +public: + using Section = StickersBox::Section; + Inner(QWidget *parent, Section section); + Inner(QWidget *parent, const Stickers::Order &archivedIds); + + void rebuild(); + void updateSize(); + void updateRows(); // refresh only pack cover stickers + bool appendSet(const Stickers::Set &set); + bool savingStart() { + if (_saving) return false; + _saving = true; + return true; + } + + Stickers::Order getOrder() const; + Stickers::Order getDisabledSets() const; + + void setVisibleScrollbar(int32 width); + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + + ~Inner(); + +protected: + void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void leaveEvent(QEvent *e) override; + +signals: + void checkDraggingScroll(int localY); + void noDraggingScroll(); + +public slots: + void onUpdateSelected(); + void onClearRecent(); + void onClearBoxDestroyed(QObject *box); + +private slots: + void onImageLoaded(); + +private: + void setup(); + void paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const; + + void step_shifting(uint64 ms, bool timer); + void paintRow(Painter &p, int32 index); + void clear(); + void setActionSel(int32 actionSel); + float64 aboveShadowOpacity() const; + + void readVisibleSets(); + + void installSet(uint64 setId); + void installDone(const MTPmessages_StickerSetInstallResult &result); + bool installFail(uint64 setId, const RPCError &error); + + Section _section; + Stickers::Order _archivedIds; + + int32 _rowHeight; + struct StickerSetRow { + StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, int titleWidth, bool installed, bool official, bool unread, bool disabled, bool recent, int32 pixw, int32 pixh) : id(id) + , sticker(sticker) + , count(count) + , title(title) + , titleWidth(titleWidth) + , installed(installed) + , official(official) + , unread(unread) + , disabled(disabled) + , recent(recent) + , pixw(pixw) + , pixh(pixh) + , yadd(0, 0) { + } + uint64 id; + DocumentData *sticker; + int32 count; + QString title; + int titleWidth; + bool installed, official, unread, disabled, recent; + int32 pixw, pixh; + anim::ivalue yadd; + }; + using StickerSetRows = QList; + + void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth); + void fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const; + int fillSetCount(const Stickers::Set &set) const; + QString fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const; + void fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled); + + int countMaxNameWidth() const; + + StickerSetRows _rows; + QList _animStartTimes; + uint64 _aboveShadowFadeStart = 0; + anim::fvalue _aboveShadowFadeOpacity = { 0., 0. }; + Animation _a_shifting; + + int _visibleTop = 0; + int _visibleBottom = 0; + int _itemsTop = 0; + + bool _saving = false; + + int _actionSel = -1; + int _actionDown = -1; + + int _clearWidth, _removeWidth, _returnWidth, _restoreWidth; + + ConfirmBox *_clearBox = nullptr; + + int _buttonHeight = 0; + bool _hasFeaturedButton = false; + bool _hasArchivedButton = false; + + QPoint _mouse; + int _selected = -3; // -2 - featured stickers button, -1 - archived stickers button + int _pressed = -2; + QPoint _dragStart; + int _started = -1; + int _dragging = -1; + int _above = -1; + + Ui::RectShadow _aboveShadow; + + int32 _scrollbar = 0; +}; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index bbd97414c..3bf665a98 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -19,9 +19,9 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "lang.h" +#include "boxes/stickersetbox.h" -#include "stickersetbox.h" +#include "lang.h" #include "mainwidget.h" #include "mainwindow.h" #include "stickers/stickers.h" @@ -32,20 +32,128 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_boxes.h" #include "styles/style_stickers.h" -namespace { +StickerSetBox::StickerSetBox(const MTPInputStickerSet &set) : ScrollableBox(st::stickersScroll) +, _inner(this, set) +, _shadow(this) +, _add(this, lang(lng_stickers_add_pack), st::defaultBoxButton) +, _share(this, lang(lng_stickers_share_pack), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _done(this, lang(lng_about_done), st::defaultBoxButton) { + setMaxHeight(st::stickersMaxHeight); + connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); -constexpr int kArchivedLimitFirstRequest = 10; -constexpr int kArchivedLimitPerPage = 30; + init(_inner, st::boxButtonPadding.bottom() + _cancel.height() + st::boxButtonPadding.top()); -} // namespace + connect(&_add, SIGNAL(clicked()), this, SLOT(onAddStickers())); + connect(&_share, SIGNAL(clicked()), this, SLOT(onShareStickers())); + connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + connect(&_done, SIGNAL(clicked()), this, SLOT(onClose())); -StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : ScrolledWidget() + connect(_inner, SIGNAL(updateButtons()), this, SLOT(onUpdateButtons())); + connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); + + connect(_inner, SIGNAL(installed(uint64)), this, SLOT(onInstalled(uint64))); + + onStickersUpdated(); + + onScroll(); + + prepare(); +} + +void StickerSetBox::onInstalled(uint64 setId) { + emit installed(setId); + onClose(); +} + +void StickerSetBox::onStickersUpdated() { + showAll(); +} + +void StickerSetBox::onAddStickers() { + _inner->install(); +} + +void StickerSetBox::onShareStickers() { + QString url = qsl("https://telegram.me/addstickers/") + _inner->shortName(); + QApplication::clipboard()->setText(url); + Ui::showLayer(new InformBox(lang(lng_stickers_copied))); +} + +void StickerSetBox::onUpdateButtons() { + if (!_cancel.isHidden() || !_done.isHidden()) { + showAll(); + } +} + +void StickerSetBox::onScroll() { + auto scroll = scrollArea(); + auto scrollTop = scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + scroll->height()); +} + +void StickerSetBox::showAll() { + ScrollableBox::showAll(); + int32 cnt = _inner->notInstalled(); + if (_inner->loaded()) { + _shadow.show(); + if (_inner->notInstalled()) { + _add.show(); + _cancel.show(); + _share.hide(); + _done.hide(); + } else if (_inner->official()) { + _add.hide(); + _share.hide(); + _cancel.hide(); + _done.show(); + } else { + _share.show(); + _cancel.show(); + _add.hide(); + _done.hide(); + } + } else { + _shadow.hide(); + _add.hide(); + _share.hide(); + _cancel.show(); + _done.hide(); + } + resizeEvent(0); + update(); +} + +void StickerSetBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + paintTitle(p, _inner->title()); +} + +void StickerSetBox::resizeEvent(QResizeEvent *e) { + ScrollableBox::resizeEvent(e); + _inner->resize(width(), _inner->height()); + _shadow.setGeometry(0, height() - st::boxButtonPadding.bottom() - _cancel.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); + _add.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _add.height()); + _share.moveToRight(st::boxButtonPadding.right(), _add.y()); + _done.moveToRight(st::boxButtonPadding.right(), _add.y()); + if (_add.isHidden() && _share.isHidden()) { + _cancel.moveToRight(st::boxButtonPadding.right(), _add.y()); + } else if (_add.isHidden()) { + _cancel.moveToRight(st::boxButtonPadding.right() + _share.width() + st::boxButtonPadding.left(), _add.y()); + } else { + _cancel.moveToRight(st::boxButtonPadding.right() + _add.width() + st::boxButtonPadding.left(), _add.y()); + } +} + +StickerSetBox::Inner::Inner(QWidget *parent, const MTPInputStickerSet &set) : ScrolledWidget(parent) , _input(set) { switch (set.type()) { case mtpc_inputStickerSetID: _setId = set.c_inputStickerSetID().vid.v; _setAccess = set.c_inputStickerSetID().vaccess_hash.v; break; case mtpc_inputStickerSetShortName: _setShortName = qs(set.c_inputStickerSetShortName().vshort_name); break; } - MTP::send(MTPmessages_GetStickerSet(_input), rpcDone(&StickerSetInner::gotSet), rpcFail(&StickerSetInner::failedSet)); + MTP::send(MTPmessages_GetStickerSet(_input), rpcDone(&Inner::gotSet), rpcFail(&Inner::failedSet)); App::main()->updateStickers(); subscribe(FileDownload::ImageLoaded(), [this] { update(); }); @@ -56,7 +164,7 @@ StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : ScrolledWidget connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); } -void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { +void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { _pack.clear(); _emoji.clear(); _packOvers.clear(); @@ -126,7 +234,7 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { emit updateButtons(); } -bool StickerSetInner::failedSet(const RPCError &error) { +bool StickerSetBox::Inner::failedSet(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _loaded = true; @@ -136,7 +244,7 @@ bool StickerSetInner::failedSet(const RPCError &error) { return true; } -void StickerSetInner::installDone(const MTPmessages_StickerSetInstallResult &result) { +void StickerSetBox::Inner::installDone(const MTPmessages_StickerSetInstallResult &result) { auto &sets = Global::RefStickerSets(); bool wasArchived = (_setFlags & MTPDstickerSet::Flag::f_archived); @@ -189,7 +297,7 @@ void StickerSetInner::installDone(const MTPmessages_StickerSetInstallResult &res emit installed(_setId); } -bool StickerSetInner::installFail(const RPCError &error) { +bool StickerSetBox::Inner::installFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; Ui::showLayer(new InformBox(lang(lng_stickers_not_found))); @@ -197,14 +305,14 @@ bool StickerSetInner::installFail(const RPCError &error) { return true; } -void StickerSetInner::mousePressEvent(QMouseEvent *e) { +void StickerSetBox::Inner::mousePressEvent(QMouseEvent *e) { int index = stickerFromGlobalPos(e->globalPos()); if (index >= 0 && index < _pack.size()) { _previewTimer.start(QApplication::startDragTime()); } } -void StickerSetInner::mouseMoveEvent(QMouseEvent *e) { +void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) { updateSelected(); if (_previewShown >= 0) { int index = stickerFromGlobalPos(e->globalPos()); @@ -215,7 +323,7 @@ void StickerSetInner::mouseMoveEvent(QMouseEvent *e) { } } -void StickerSetInner::mouseReleaseEvent(QMouseEvent *e) { +void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) { if (_previewShown >= 0) { _previewShown = -1; return; @@ -233,7 +341,7 @@ void StickerSetInner::mouseReleaseEvent(QMouseEvent *e) { } } -void StickerSetInner::updateSelected() { +void StickerSetBox::Inner::updateSelected() { auto index = stickerFromGlobalPos(QCursor::pos()); if (isMasksSet()) { index = -1; @@ -246,7 +354,7 @@ void StickerSetInner::updateSelected() { } } -void StickerSetInner::startOverAnimation(int index, float64 from, float64 to) { +void StickerSetBox::Inner::startOverAnimation(int index, float64 from, float64 to) { if (index >= 0 && index < _packOvers.size()) { _packOvers[index].start([this, index] { int row = index / StickerPanPerRow; @@ -258,7 +366,7 @@ void StickerSetInner::startOverAnimation(int index, float64 from, float64 to) { } } -void StickerSetInner::onPreview() { +void StickerSetBox::Inner::onPreview() { int index = stickerFromGlobalPos(QCursor::pos()); if (index >= 0 && index < _pack.size()) { _previewShown = index; @@ -266,7 +374,7 @@ void StickerSetInner::onPreview() { } } -int32 StickerSetInner::stickerFromGlobalPos(const QPoint &p) const { +int32 StickerSetBox::Inner::stickerFromGlobalPos(const QPoint &p) const { QPoint l(mapFromGlobal(p)); if (rtl()) l.setX(width() - l.x()); int32 row = (l.y() >= st::stickersPadding.top()) ? qFloor((l.y() - st::stickersPadding.top()) / st::stickersSize.height()) : -1; @@ -278,7 +386,7 @@ int32 StickerSetInner::stickerFromGlobalPos(const QPoint &p) const { return -1; } -void StickerSetInner::paintEvent(QPaintEvent *e) { +void StickerSetBox::Inner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); @@ -331,1376 +439,42 @@ void StickerSetInner::paintEvent(QPaintEvent *e) { } } -void StickerSetInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { +void StickerSetBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) { _visibleTop = visibleTop; _visibleBottom = visibleBottom; } -bool StickerSetInner::loaded() const { +bool StickerSetBox::Inner::loaded() const { return _loaded && !_pack.isEmpty(); } -int32 StickerSetInner::notInstalled() const { +int32 StickerSetBox::Inner::notInstalled() const { if (!_loaded) return 0; auto it = Global::StickerSets().constFind(_setId); if (it == Global::StickerSets().cend() || !(it->flags & MTPDstickerSet::Flag::f_installed) || (it->flags & MTPDstickerSet::Flag::f_archived)) return _pack.size(); return 0; } -bool StickerSetInner::official() const { +bool StickerSetBox::Inner::official() const { return _loaded && _setShortName.isEmpty(); } -QString StickerSetInner::title() const { +QString StickerSetBox::Inner::title() const { return _loaded ? (_pack.isEmpty() ? lang(lng_attach_failed) : _title) : lang(lng_contacts_loading); } -QString StickerSetInner::shortName() const { +QString StickerSetBox::Inner::shortName() const { return _setShortName; } -void StickerSetInner::install() { +void StickerSetBox::Inner::install() { if (isMasksSet()) { Ui::showLayer(new InformBox(lang(lng_stickers_masks_pack)), KeepOtherLayers); return; } if (_installRequest) return; - _installRequest = MTP::send(MTPmessages_InstallStickerSet(_input, MTP_bool(false)), rpcDone(&StickerSetInner::installDone), rpcFail(&StickerSetInner::installFail)); + _installRequest = MTP::send(MTPmessages_InstallStickerSet(_input, MTP_bool(false)), rpcDone(&Inner::installDone), rpcFail(&Inner::installFail)); } -StickerSetInner::~StickerSetInner() { -} - -StickerSetBox::StickerSetBox(const MTPInputStickerSet &set) : ScrollableBox(st::stickersScroll) -, _inner(set) -, _shadow(this) -, _add(this, lang(lng_stickers_add_pack), st::defaultBoxButton) -, _share(this, lang(lng_stickers_share_pack), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _done(this, lang(lng_about_done), st::defaultBoxButton) { - setMaxHeight(st::stickersMaxHeight); - connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); - - init(&_inner, st::boxButtonPadding.bottom() + _cancel.height() + st::boxButtonPadding.top()); - - connect(&_add, SIGNAL(clicked()), this, SLOT(onAddStickers())); - connect(&_share, SIGNAL(clicked()), this, SLOT(onShareStickers())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - connect(&_done, SIGNAL(clicked()), this, SLOT(onClose())); - - connect(&_inner, SIGNAL(updateButtons()), this, SLOT(onUpdateButtons())); - connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); - - connect(&_inner, SIGNAL(installed(uint64)), this, SLOT(onInstalled(uint64))); - - onStickersUpdated(); - - onScroll(); - - prepare(); -} - -void StickerSetBox::onInstalled(uint64 setId) { - emit installed(setId); - onClose(); -} - -void StickerSetBox::onStickersUpdated() { - showAll(); -} - -void StickerSetBox::onAddStickers() { - _inner.install(); -} - -void StickerSetBox::onShareStickers() { - QString url = qsl("https://telegram.me/addstickers/") + _inner.shortName(); - QApplication::clipboard()->setText(url); - Ui::showLayer(new InformBox(lang(lng_stickers_copied))); -} - -void StickerSetBox::onUpdateButtons() { - if (!_cancel.isHidden() || !_done.isHidden()) { - showAll(); - } -} - -void StickerSetBox::onScroll() { - auto scroll = scrollArea(); - auto scrollTop = scroll->scrollTop(); - _inner.setVisibleTopBottom(scrollTop, scrollTop + scroll->height()); -} - -void StickerSetBox::showAll() { - ScrollableBox::showAll(); - int32 cnt = _inner.notInstalled(); - if (_inner.loaded()) { - _shadow.show(); - if (_inner.notInstalled()) { - _add.show(); - _cancel.show(); - _share.hide(); - _done.hide(); - } else if (_inner.official()) { - _add.hide(); - _share.hide(); - _cancel.hide(); - _done.show(); - } else { - _share.show(); - _cancel.show(); - _add.hide(); - _done.hide(); - } - } else { - _shadow.hide(); - _add.hide(); - _share.hide(); - _cancel.show(); - _done.hide(); - } - resizeEvent(0); - update(); -} - -void StickerSetBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, _inner.title()); -} - -void StickerSetBox::resizeEvent(QResizeEvent *e) { - ScrollableBox::resizeEvent(e); - _inner.resize(width(), _inner.height()); - _shadow.setGeometry(0, height() - st::boxButtonPadding.bottom() - _cancel.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); - _add.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _add.height()); - _share.moveToRight(st::boxButtonPadding.right(), _add.y()); - _done.moveToRight(st::boxButtonPadding.right(), _add.y()); - if (_add.isHidden() && _share.isHidden()) { - _cancel.moveToRight(st::boxButtonPadding.right(), _add.y()); - } else if (_add.isHidden()) { - _cancel.moveToRight(st::boxButtonPadding.right() + _share.width() + st::boxButtonPadding.left(), _add.y()); - } else { - _cancel.moveToRight(st::boxButtonPadding.right() + _add.width() + st::boxButtonPadding.left(), _add.y()); - } -} - -namespace internal { - -StickersInner::StickersInner(StickersBox::Section section) : ScrolledWidget() -, _section(section) -, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) -, _a_shifting(animation(this, &StickersInner::step_shifting)) -, _itemsTop(st::membersPadding.top()) -, _clearWidth(st::normalFont->width(lang(lng_stickers_clear_recent))) -, _removeWidth(st::normalFont->width(lang(lng_stickers_remove))) -, _returnWidth(st::normalFont->width(lang(lng_stickers_return))) -, _restoreWidth(st::normalFont->width(lang(lng_stickers_restore))) -, _aboveShadow(st::boxShadow) { - setup(); -} - -StickersInner::StickersInner(const Stickers::Order &archivedIds) : ScrolledWidget() -, _section(StickersBox::Section::ArchivedPart) -, _archivedIds(archivedIds) -, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) -, _a_shifting(animation(this, &StickersInner::step_shifting)) -, _itemsTop(st::membersPadding.top()) -, _clearWidth(st::normalFont->width(lang(lng_stickers_clear_recent))) -, _removeWidth(st::normalFont->width(lang(lng_stickers_remove))) -, _returnWidth(st::normalFont->width(lang(lng_stickers_return))) -, _restoreWidth(st::normalFont->width(lang(lng_stickers_restore))) -, _aboveShadow(st::boxShadow) { - setup(); -} - -void StickersInner::setup() { - subscribe(FileDownload::ImageLoaded(), [this] { update(); }); - setMouseTracking(true); -} - -void StickersInner::onImageLoaded() { - update(); - readVisibleSets(); -} - -void StickersInner::paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const { - if (selected) { - p.fillRect(0, y, width(), _buttonHeight, st::contactsBgOver); - } - p.setFont(st::stickersFeaturedFont); - p.setPen(st::stickersFeaturedPen); - p.drawTextLeft(st::stickersFeaturedPosition.x(), y + st::stickersFeaturedPosition.y(), width(), text); - - if (badgeCounter) { - Dialogs::Layout::UnreadBadgeStyle unreadSt; - unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox; - unreadSt.size = st::stickersFeaturedBadgeSize; - int unreadRight = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x()); - if (rtl()) unreadRight = width() - unreadRight; - int unreadTop = y + (_buttonHeight - st::stickersFeaturedBadgeSize) / 2; - Dialogs::Layout::paintUnreadCount(p, QString::number(badgeCounter), unreadRight, unreadTop, unreadSt); - } -} - -void StickersInner::paintEvent(QPaintEvent *e) { - QRect r(e->rect()); - Painter p(this); - - _a_shifting.step(); - - p.fillRect(r, st::white); - p.setClipRect(r); - - int y = st::membersPadding.top(); - if (_hasFeaturedButton) { - auto selected = (_selected == -2); - paintButton(p, y, selected, lang(lng_stickers_featured), Global::FeaturedStickerSetsUnreadCount()); - y += _buttonHeight; - } - if (_hasArchivedButton) { - auto selected = (_selected == -1); - paintButton(p, y, selected, lang(lng_stickers_archived), 0); - y += _buttonHeight; - } - - if (_rows.isEmpty()) { - p.setFont(st::noContactsFont); - p.setPen(st::noContactsColor); - p.drawText(QRect(0, y, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); - } else { - p.translate(0, _itemsTop); - - int32 yFrom = r.y() - _itemsTop, yTo = r.y() + r.height() - _itemsTop; - int32 from = floorclamp(yFrom - _rowHeight, _rowHeight, 0, _rows.size()); - int32 to = ceilclamp(yTo + _rowHeight, _rowHeight, 0, _rows.size()); - p.translate(0, from * _rowHeight); - for (int32 i = from; i < to; ++i) { - if (i != _above) { - paintRow(p, i); - } - p.translate(0, _rowHeight); - } - if (from <= _above && _above < to) { - p.translate(0, (_above - to) * _rowHeight); - paintRow(p, _above); - } - } -} - -void StickersInner::paintRow(Painter &p, int32 index) { - const StickerSetRow *s(_rows.at(index)); - - int32 xadd = 0, yadd = s->yadd.current(); - if (xadd || yadd) p.translate(xadd, yadd); - - if (_section == Section::Installed) { - bool removeSel = (index == _actionSel && (_actionDown < 0 || index == _actionDown)); - bool removeDown = removeSel && (index == _actionDown); - - p.setFont(removeSel ? st::linkOverFont : st::linkFont); - if (removeDown) { - p.setPen(st::btnDefLink.downColor); - } else { - p.setPen(st::btnDefLink.color); - } - int32 remWidth = s->recent ? _clearWidth : (s->disabled ? (s->official ? _restoreWidth : _returnWidth) : _removeWidth); - QString remText = lang(s->recent ? lng_stickers_clear_recent : (s->disabled ? (s->official ? lng_stickers_restore : lng_stickers_return) : lng_stickers_remove)); - p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), remText, remWidth); - - if (index == _above) { - float64 current = _aboveShadowFadeOpacity.current(); - if (_started >= 0) { - float64 o = aboveShadowOpacity(); - if (o > current) { - _aboveShadowFadeOpacity = anim::fvalue(o, o); - current = o; - } - } - p.setOpacity(current); - QRect row(myrtlrect(_aboveShadow.getDimensions(st::boxShadowShift).left(), st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - _aboveShadow.getDimensions(st::boxShadowShift).right(), _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2))); - _aboveShadow.paint(p, row, st::boxShadowShift); - p.fillRect(row, st::white); - p.setOpacity(1); - } - } else if (s->installed && !s->disabled) { - int addw = st::stickersAddSize.width(); - int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (addw + st::stickersFeaturedInstalled.width()) / 2); - int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2; - st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width()); - } else { - int addw = st::stickersAddSize.width(); - int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw; - int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersAddSize.height()) / 2; - QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height())); - - auto textBg = (_actionSel == index) ? st::defaultActiveButton.textBgOver : st::defaultActiveButton.textBg; - App::roundRect(p, add, textBg, ImageRoundRadius::Small); - int iconx = addx + (st::stickersAddSize.width() - st::stickersAddIcon.width()) / 2; - int icony = addy + (st::stickersAddSize.height() - st::stickersAddIcon.height()) / 2; - icony += (_actionSel == index && _actionDown == index) ? (st::defaultActiveButton.downTextTop - st::defaultActiveButton.textTop) : 0; - st::stickersAddIcon.paint(p, QPoint(iconx, icony), width()); - } - - if (s->disabled && _section == Section::Installed) { - p.setOpacity(st::stickersRowDisabledOpacity); - } - if (s->sticker) { - s->sticker->thumb->load(); - QPixmap pix(s->sticker->thumb->pix(s->pixw, s->pixh)); - p.drawPixmapLeft(st::contactsPadding.left() + (st::contactsPhotoSize - s->pixw) / 2, st::contactsPadding.top() + (st::contactsPhotoSize - s->pixh) / 2, width(), pix); - } - - int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); - int namey = st::contactsPadding.top() + st::contactsNameTop; - int statusx = namex; - int statusy = st::contactsPadding.top() + st::contactsStatusTop; - - p.setFont(st::contactsNameFont); - p.setPen(st::black); - p.drawTextLeft(namex, namey, width(), s->title, s->titleWidth); - - if (s->unread) { - p.setPen(Qt::NoPen); - p.setBrush(st::stickersFeaturedUnreadBg); - - p.setRenderHint(QPainter::HighQualityAntialiasing, true); - p.drawEllipse(rtlrect(namex + s->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - } - - p.setFont(st::contactsStatusFont); - p.setPen(st::contactsStatusFg); - p.drawTextLeft(statusx, statusy, width(), lng_stickers_count(lt_count, s->count)); - - p.setOpacity(1); - if (xadd || yadd) p.translate(-xadd, -yadd); -} - -void StickersInner::mousePressEvent(QMouseEvent *e) { - if (_saving) return; - if (_dragging >= 0) mouseReleaseEvent(e); - _mouse = e->globalPos(); - onUpdateSelected(); - - _pressed = _selected; - if (_actionSel >= 0) { - _actionDown = _actionSel; - update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); - } else if (_selected >= 0 && _section == Section::Installed && !_rows.at(_selected)->recent) { - _above = _dragging = _started = _selected; - _dragStart = mapFromGlobal(_mouse); - } -} - -void StickersInner::mouseMoveEvent(QMouseEvent *e) { - if (_saving) return; - _mouse = e->globalPos(); - onUpdateSelected(); -} - -void StickersInner::onUpdateSelected() { - if (_saving) return; - QPoint local(mapFromGlobal(_mouse)); - if (_dragging >= 0) { - int32 shift = 0; - uint64 ms = getms(); - int firstSetIndex = 0; - if (_rows.at(firstSetIndex)->recent) { - ++firstSetIndex; - } - if (_dragStart.y() > local.y() && _dragging > 0) { - shift = -floorclamp(_dragStart.y() - local.y() + (_rowHeight / 2), _rowHeight, 0, _dragging - firstSetIndex); - for (int32 from = _dragging, to = _dragging + shift; from > to; --from) { - qSwap(_rows[from], _rows[from - 1]); - _rows.at(from)->yadd = anim::ivalue(_rows.at(from)->yadd.current() - _rowHeight, 0); - _animStartTimes[from] = ms; - } - } else if (_dragStart.y() < local.y() && _dragging + 1 < _rows.size()) { - shift = floorclamp(local.y() - _dragStart.y() + (_rowHeight / 2), _rowHeight, 0, _rows.size() - _dragging - 1); - for (int32 from = _dragging, to = _dragging + shift; from < to; ++from) { - qSwap(_rows[from], _rows[from + 1]); - _rows.at(from)->yadd = anim::ivalue(_rows.at(from)->yadd.current() + _rowHeight, 0); - _animStartTimes[from] = ms; - } - } - if (shift) { - _dragging += shift; - _above = _dragging; - _dragStart.setY(_dragStart.y() + shift * _rowHeight); - if (!_a_shifting.animating()) { - _a_shifting.start(); - } - } - _rows.at(_dragging)->yadd = anim::ivalue(local.y() - _dragStart.y(), local.y() - _dragStart.y()); - _animStartTimes[_dragging] = 0; - _a_shifting.step(getms(), true); - - emit checkDraggingScroll(local.y()); - } else { - bool in = rect().marginsRemoved(QMargins(0, _itemsTop, 0, st::membersPadding.bottom())).contains(local); - int selected = -2; - int actionSel = -1; - if (in) { - selected = floorclamp(local.y() - _itemsTop, _rowHeight, 0, _rows.size() - 1); - - if (_section == Section::Installed) { - int remw = _rows.at(selected)->recent ? _clearWidth : (_rows.at(selected)->disabled ? (_rows.at(selected)->official ? _restoreWidth : _returnWidth) : _removeWidth); - QRect rem(myrtlrect(width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - remw, st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, remw, st::normalFont->height)); - actionSel = rem.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1; - } else if (_rows.at(selected)->installed && !_rows.at(selected)->disabled) { - actionSel = -1; - } else { - int addw = st::stickersAddSize.width(); - int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw; - int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersAddSize.height()) / 2; - QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height())); - actionSel = add.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1; - } - } else if (_hasFeaturedButton && QRect(0, st::membersPadding.top(), width(), _buttonHeight).contains(local)) { - selected = -2; - } else if (_hasArchivedButton && QRect(0, st::membersPadding.top() + (_hasFeaturedButton ? _buttonHeight : 0), width(), _buttonHeight).contains(local)) { - selected = -1; - } else { - selected = -3; - } - if (_selected != selected) { - if (((_selected == -1) != (selected == -1)) || ((_selected == -2) != (selected == -2))) { - update(); - } - if (_section != Section::Installed && ((_selected >= 0 || _pressed >= 0) != (selected >= 0 || _pressed >= 0))) { - setCursor((selected >= 0 || _pressed >= 0) ? style::cur_pointer : style::cur_default); - } - _selected = selected; - } - setActionSel(actionSel); - emit noDraggingScroll(); - } -} - -void StickersInner::onClearRecent() { - if (_clearBox) { - _clearBox->onClose(); - } - - auto &sets = Global::RefStickerSets(); - bool removedCloud = (sets.remove(Stickers::CloudRecentSetId) != 0); - bool removedCustom = (sets.remove(Stickers::CustomSetId) != 0); - - auto &recent = cGetRecentStickers(); - if (!recent.isEmpty()) { - recent.clear(); - Local::writeUserSettings(); - } - - if (removedCustom) Local::writeInstalledStickers(); - if (removedCloud) Local::writeRecentStickers(); - emit App::main()->updateStickers(); - rebuild(); - - MTPmessages_ClearRecentStickers::Flags flags = 0; - MTP::send(MTPmessages_ClearRecentStickers(MTP_flags(flags))); -} - -void StickersInner::onClearBoxDestroyed(QObject *box) { - if (box == _clearBox) { - _clearBox = nullptr; - } -} - -float64 StickersInner::aboveShadowOpacity() const { - if (_above < 0) return 0; - - int32 dx = 0; - int32 dy = qAbs(_above * _rowHeight + _rows.at(_above)->yadd.current() - _started * _rowHeight); - return qMin((dx + dy) * 2. / _rowHeight, 1.); -} - -void StickersInner::mouseReleaseEvent(QMouseEvent *e) { - auto pressed = _pressed; - _pressed = -2; - - if (_section != Section::Installed && _selected < 0 && pressed >= 0) { - setCursor(style::cur_default); - } - - if (_saving) return; - - _mouse = e->globalPos(); - onUpdateSelected(); - if (_actionDown == _actionSel && _actionSel >= 0) { - if (_section == Section::Installed) { - if (_rows[_actionDown]->recent) { - _clearBox = new ConfirmBox(lang(lng_stickers_clear_recent_sure), lang(lng_stickers_clear_recent)); - connect(_clearBox, SIGNAL(confirmed()), this, SLOT(onClearRecent())); - connect(_clearBox, SIGNAL(destroyed(QObject*)), this, SLOT(onClearBoxDestroyed(QObject*))); - Ui::showLayer(_clearBox, KeepOtherLayers); - } else { - _rows[_actionDown]->disabled = !_rows[_actionDown]->disabled; - } - } else { - installSet(_rows[_actionDown]->id); - } - } else if (_dragging >= 0) { - QPoint local(mapFromGlobal(_mouse)); - _rows[_dragging]->yadd.start(0); - _aboveShadowFadeStart = _animStartTimes[_dragging] = getms(); - _aboveShadowFadeOpacity = anim::fvalue(aboveShadowOpacity(), 0); - if (!_a_shifting.animating()) { - _a_shifting.start(); - } - - _dragging = _started = -1; - } else if (pressed == _selected && _actionSel < 0 && _actionDown < 0) { - if (_selected == -2) { - _selected = -3; - Ui::showLayer(new StickersBox(Section::Featured), KeepOtherLayers); - } else if (_selected == -1) { - _selected = -3; - Ui::showLayer(new StickersBox(Section::Archived), KeepOtherLayers); - } else if (_selected >= 0 && _section != Section::Installed) { - auto &sets = Global::RefStickerSets(); - auto it = sets.find(_rows.at(pressed)->id); - if (it != sets.cend()) { - _selected = -3; - Ui::showLayer(new StickerSetBox(Stickers::inputSetId(*it)), KeepOtherLayers); - } - } - } - if (_actionDown >= 0) { - update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight); - _actionDown = -1; - } -} - -void StickersInner::leaveEvent(QEvent *e) { - _mouse = QPoint(-1, -1); - onUpdateSelected(); -} - -void StickersInner::installSet(uint64 setId) { - auto &sets = Global::RefStickerSets(); - auto it = sets.find(setId); - if (it == sets.cend()) { - rebuild(); - return; - } - - MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse()), rpcDone(&StickersInner::installDone), rpcFail(&StickersInner::installFail, setId)); - - Stickers::installLocally(setId); -} - -void StickersInner::installDone(const MTPmessages_StickerSetInstallResult &result) { - if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { - Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); - } -} - -bool StickersInner::installFail(uint64 setId, const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - - auto &sets = Global::RefStickerSets(); - auto it = sets.find(setId); - if (it == sets.cend()) { - rebuild(); - return true; - } - - Stickers::undoInstallLocally(setId); - return true; -} - -void StickersInner::step_shifting(uint64 ms, bool timer) { - bool animating = false; - int32 updateMin = -1, updateMax = 0; - for (int32 i = 0, l = _animStartTimes.size(); i < l; ++i) { - uint64 start = _animStartTimes.at(i); - if (start) { - if (updateMin < 0) updateMin = i; - updateMax = i; - if (start + st::stickersRowDuration > ms && ms >= start) { - _rows.at(i)->yadd.update(float64(ms - start) / st::stickersRowDuration, anim::sineInOut); - animating = true; - } else { - _rows.at(i)->yadd.finish(); - _animStartTimes[i] = 0; - } - } - } - if (_aboveShadowFadeStart) { - if (updateMin < 0 || updateMin > _above) updateMin = _above; - if (updateMax < _above) updateMin = _above; - if (_aboveShadowFadeStart + st::stickersRowDuration > ms && ms > _aboveShadowFadeStart) { - _aboveShadowFadeOpacity.update(float64(ms - _aboveShadowFadeStart) / st::stickersRowDuration, anim::sineInOut); - animating = true; - } else { - _aboveShadowFadeOpacity.finish(); - _aboveShadowFadeStart = 0; - } - } - if (timer) { - if (_dragging >= 0) { - if (updateMin < 0 || updateMin > _dragging) updateMin = _dragging; - if (updateMax < _dragging) updateMax = _dragging; - } - if (updateMin >= 0) { - update(0, _itemsTop + _rowHeight * (updateMin - 1), width(), _rowHeight * (updateMax - updateMin + 3)); - } - } - if (!animating) { - _above = _dragging; - _a_shifting.stop(); - } -} - -void StickersInner::clear() { - for (int32 i = 0, l = _rows.size(); i < l; ++i) { - delete _rows.at(i); - } - _rows.clear(); - _animStartTimes.clear(); - _aboveShadowFadeStart = 0; - _aboveShadowFadeOpacity = anim::fvalue(0, 0); - _a_shifting.stop(); - _above = _dragging = _started = -1; - _selected = -3; - _pressed = -3; - _actionDown = -1; - setActionSel(-1); - update(); -} - -void StickersInner::setActionSel(int32 actionSel) { - if (actionSel != _actionSel) { - if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); - _actionSel = actionSel; - if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); - if (_section == Section::Installed) { - setCursor((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel)) ? style::cur_pointer : style::cur_default); - } - } -} - -void StickersInner::rebuild() { - _hasFeaturedButton = _hasArchivedButton = false; - _itemsTop = st::membersPadding.top(); - _buttonHeight = st::stickersFeaturedHeight; - if (_section == Section::Installed) { - if (!Global::FeaturedStickerSetsOrder().isEmpty()) { - _itemsTop += _buttonHeight; - _hasFeaturedButton = true; - } - if (!Global::ArchivedStickerSetsOrder().isEmpty()) { - _itemsTop += _buttonHeight; - _hasArchivedButton = true; - } - if (_itemsTop > st::membersPadding.top()) { - _itemsTop += st::membersPadding.top(); - } - } - - int maxNameWidth = countMaxNameWidth(); - - clear(); - auto &order = ([this]() -> const Stickers::Order & { - if (_section == Section::Installed) { - return Global::StickerSetsOrder(); - } else if (_section == Section::Featured) { - return Global::FeaturedStickerSetsOrder(); - } else if (_section == Section::Archived) { - return Global::ArchivedStickerSetsOrder(); - } - return _archivedIds; - })(); - _rows.reserve(order.size() + 1); - _animStartTimes.reserve(order.size() + 1); - - auto &sets = Global::StickerSets(); - if (_section == Section::Installed) { - auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); - if (cloudIt != sets.cend() && !cloudIt->stickers.isEmpty()) { - rebuildAppendSet(cloudIt.value(), maxNameWidth); - } - } - for_const (auto setId, order) { - auto it = sets.constFind(setId); - if (it == sets.cend()) { - continue; - } - - rebuildAppendSet(it.value(), maxNameWidth); - - if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { - App::api()->scheduleStickerSetRequest(it->id, it->access); - } - } - App::api()->requestStickerSets(); - updateSize(); -} - -void StickersInner::updateSize() { - resize(width(), _itemsTop + _rows.size() * _rowHeight + st::membersPadding.bottom()); -} - -void StickersInner::updateRows() { - int maxNameWidth = countMaxNameWidth(); - auto &sets = Global::StickerSets(); - for_const (auto row, _rows) { - auto it = sets.constFind(row->id); - if (it != sets.cend()) { - auto &set = it.value(); - if (!row->sticker) { - DocumentData *sticker = nullptr; - int pixw = 0, pixh = 0; - fillSetCover(set, &sticker, &pixw, &pixh); - if (sticker) { - row->sticker = sticker; - row->pixw = pixw; - row->pixh = pixh; - } - } - fillSetFlags(set, &row->recent, &row->installed, &row->official, &row->unread, &row->disabled); - if (_section == Section::Installed) { - row->disabled = false; - } - row->title = fillSetTitle(set, maxNameWidth, &row->titleWidth); - row->count = fillSetCount(set); - } - } - update(); -} - -bool StickersInner::appendSet(const Stickers::Set &set) { - for_const (auto row, _rows) { - if (row->id == set.id) { - return false; - } - } - rebuildAppendSet(set, countMaxNameWidth()); - return true; -} - -int StickersInner::countMaxNameWidth() const { - int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); - int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x(); - if (_section == Section::Installed) { - namew -= qMax(qMax(qMax(_returnWidth, _removeWidth), _restoreWidth), _clearWidth); - } else { - namew -= st::stickersAddIcon.width() - st::defaultActiveButton.width; - namew -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; - } - return namew; -} - -void StickersInner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) { - bool recent = false, installed = false, official = false, unread = false, disabled = false; - fillSetFlags(set, &recent, &installed, &official, &unread, &disabled); - if (_section == Section::Installed && disabled) { - return; - } - - DocumentData *sticker = nullptr; - int pixw = 0, pixh = 0; - fillSetCover(set, &sticker, &pixw, &pixh); - - int titleWidth = 0; - QString title = fillSetTitle(set, maxNameWidth, &titleWidth); - int count = fillSetCount(set); - - _rows.push_back(new StickerSetRow(set.id, sticker, count, title, titleWidth, installed, official, unread, disabled, recent, pixw, pixh)); - _animStartTimes.push_back(0); -} - -void StickersInner::fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const { - if (set.stickers.isEmpty()) { - *outSticker = nullptr; - *outWidth = *outHeight = 0; - return; - } - auto sticker = *outSticker = set.stickers.front(); - - auto pixw = sticker->thumb->width(); - auto pixh = sticker->thumb->height(); - if (pixw > st::contactsPhotoSize) { - if (pixw > pixh) { - pixh = (pixh * st::contactsPhotoSize) / pixw; - pixw = st::contactsPhotoSize; - } else { - pixw = (pixw * st::contactsPhotoSize) / pixh; - pixh = st::contactsPhotoSize; - } - } else if (pixh > st::contactsPhotoSize) { - pixw = (pixw * st::contactsPhotoSize) / pixh; - pixh = st::contactsPhotoSize; - } - *outWidth = pixw; - *outHeight = pixh; -} - -int StickersInner::fillSetCount(const Stickers::Set &set) const { - int result = set.stickers.isEmpty() ? set.count : set.stickers.size(), added = 0; - if (set.id == Stickers::CloudRecentSetId) { - auto customIt = Global::StickerSets().constFind(Stickers::CustomSetId); - if (customIt != Global::StickerSets().cend()) { - added = customIt->stickers.size(); - for_const (auto &sticker, cGetRecentStickers()) { - if (customIt->stickers.indexOf(sticker.first) < 0) { - ++added; - } - } - } else { - added = cGetRecentStickers().size(); - } - } - return result + added; -} - -QString StickersInner::fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const { - auto result = set.title; - int titleWidth = st::contactsNameFont->width(result); - if (titleWidth > maxNameWidth) { - result = st::contactsNameFont->elided(result, maxNameWidth); - titleWidth = st::contactsNameFont->width(result); - } - if (outTitleWidth) { - *outTitleWidth = titleWidth; - } - return result; -} - -void StickersInner::fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled) { - *outRecent = (set.id == Stickers::CloudRecentSetId); - *outInstalled = true; - *outOfficial = true; - *outUnread = false; - *outDisabled = false; - if (!*outRecent) { - *outInstalled = (set.flags & MTPDstickerSet::Flag::f_installed); - *outOfficial = (set.flags & MTPDstickerSet::Flag::f_official); - *outDisabled = (set.flags & MTPDstickerSet::Flag::f_archived); - if (_section == Section::Featured) { - *outUnread = (set.flags & MTPDstickerSet_ClientFlag::f_unread); - } - } -} - -Stickers::Order StickersInner::getOrder() const { - Stickers::Order result; - result.reserve(_rows.size()); - for (int32 i = 0, l = _rows.size(); i < l; ++i) { - if (_rows.at(i)->disabled || _rows.at(i)->recent) { - continue; - } - result.push_back(_rows.at(i)->id); - } - return result; -} - -Stickers::Order StickersInner::getDisabledSets() const { - Stickers::Order result; - result.reserve(_rows.size()); - for (int32 i = 0, l = _rows.size(); i < l; ++i) { - if (_rows.at(i)->disabled) { - result.push_back(_rows.at(i)->id); - } - } - return result; -} - -void StickersInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { - if (_section == Section::Featured) { - _visibleTop = visibleTop; - _visibleBottom = visibleBottom; - readVisibleSets(); - } -} - -void StickersInner::readVisibleSets() { - auto itemsVisibleTop = _visibleTop - _itemsTop; - auto itemsVisibleBottom = _visibleBottom - _itemsTop; - int rowFrom = floorclamp(itemsVisibleTop, _rowHeight, 0, _rows.size()); - int rowTo = ceilclamp(itemsVisibleBottom, _rowHeight, 0, _rows.size()); - for (int i = rowFrom; i < rowTo; ++i) { - if (!_rows[i]->unread) { - continue; - } - if (i * _rowHeight < itemsVisibleTop || (i + 1) * _rowHeight > itemsVisibleBottom) { - continue; - } - if (!_rows[i]->sticker || _rows[i]->sticker->thumb->loaded() || _rows[i]->sticker->loaded()) { - Stickers::markFeaturedAsRead(_rows[i]->id); - } - } -} - -void StickersInner::setVisibleScrollbar(int32 width) { - _scrollbar = width; -} - -StickersInner::~StickersInner() { - clear(); -} - -} // namespace internal - -StickersBox::StickersBox(Section section) : ItemListBox(st::boxScroll) -, _section(section) -, _inner(section) -, _aboutWidth(st::boxWideWidth - 2 * st::stickersReorderPadding.top()) -, _about(st::boxTextFont, lang((section == Section::Archived) ? lng_stickers_packs_archived : lng_stickers_reorder), _defaultOptions, _aboutWidth) { - setup(); -} - -StickersBox::StickersBox(const Stickers::Order &archivedIds) : ItemListBox(st::boxScroll) -, _section(Section::ArchivedPart) -, _inner(archivedIds) -, _aboutWidth(st::boxWideWidth - 2 * st::stickersReorderPadding.top()) -, _about(st::boxTextFont, lang(lng_stickers_packs_archived), _defaultOptions, _aboutWidth) { - setup(); -} - -void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result) { - _archivedRequestId = 0; - if (result.type() != mtpc_messages_archivedStickers) { - return; - } - - auto &stickers = result.c_messages_archivedStickers(); - auto &archived = Global::RefArchivedStickerSetsOrder(); - if (offsetId) { - auto index = archived.indexOf(offsetId); - if (index >= 0) { - archived = archived.mid(0, index + 1); - } - } else { - archived.clear(); - } - - bool addedSet = false; - auto &v = stickers.vsets.c_vector().v; - for_const (auto &stickerSet, v) { - const MTPDstickerSet *setData = nullptr; - switch (stickerSet.type()) { - case mtpc_stickerSetCovered: { - auto &d = stickerSet.c_stickerSetCovered(); - if (d.vset.type() == mtpc_stickerSet) { - setData = &d.vset.c_stickerSet(); - } - } break; - case mtpc_stickerSetMultiCovered: { - auto &d = stickerSet.c_stickerSetMultiCovered(); - if (d.vset.type() == mtpc_stickerSet) { - setData = &d.vset.c_stickerSet(); - } - } break; - } - if (!setData) continue; - - if (auto set = Stickers::feedSet(*setData)) { - auto index = archived.indexOf(set->id); - if (archived.isEmpty() || index != archived.size() - 1) { - if (index < archived.size() - 1) { - archived.removeAt(index); - } - archived.push_back(set->id); - } - if (_section == Section::Archived) { - if (_inner->appendSet(*set)) { - addedSet = true; - if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { - App::api()->scheduleStickerSetRequest(set->id, set->access); - } - } - } - } - } - if (_section == Section::Installed && !archived.isEmpty()) { - Local::writeArchivedStickers(); - rebuildList(); - } else if (_section == Section::Archived) { - if (addedSet) { - _inner->updateSize(); - setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); - _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); - App::api()->requestStickerSets(); - } else { - _allArchivedLoaded = v.isEmpty() || (offsetId != 0); - } - } - checkLoadMoreArchived(); -} - -void StickersBox::setup() { - if (_section == Section::Installed) { - Local::readArchivedStickers(); - if (Global::ArchivedStickerSetsOrder().isEmpty()) { - MTPmessages_GetArchivedStickers::Flags flags = 0; - _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL)); - } - } else if (_section == Section::Archived) { - // Reload the archived list. - MTPmessages_GetArchivedStickers::Flags flags = 0; - _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL)); - - auto &sets = Global::StickerSets(); - for_const (auto setId, Global::ArchivedStickerSetsOrder()) { - auto it = sets.constFind(setId); - if (it != sets.cend()) { - if (it->stickers.isEmpty() && (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { - App::api()->scheduleStickerSetRequest(setId, it->access); - } - } - } - App::api()->requestStickerSets(); - } - - int bottomSkip = st::boxPadding.bottom(); - if (_section == Section::Installed) { - _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); - _topShadow.create(this, st::contactsAboutShadow); - - _save.create(this, lang(lng_settings_save), st::defaultBoxButton); - connect(_save, SIGNAL(clicked()), this, SLOT(onSave())); - - _cancel.create(this, lang(lng_cancel), st::cancelBoxButton); - connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - - _bottomShadow.create(this); - bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom(); - } else if (_section == Section::ArchivedPart) { - _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); - _topShadow.create(this, st::contactsAboutShadow); - - _save.create(this, lang(lng_box_ok), st::defaultBoxButton); - connect(_save, SIGNAL(clicked()), this, SLOT(onClose())); - } else if (_section == Section::Archived) { - _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); - _topShadow.create(this, st::contactsAboutShadow); - } - ItemListBox::init(_inner, bottomSkip, st::boxTitleHeight + _aboutHeight); - setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); - - connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); - App::main()->updateStickers(); - - connect(_inner, SIGNAL(checkDraggingScroll(int)), this, SLOT(onCheckDraggingScroll(int))); - connect(_inner, SIGNAL(noDraggingScroll()), this, SLOT(onNoDraggingScroll())); - connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); - connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); - _scrollTimer.setSingleShot(false); - - rebuildList(); - - prepare(); -} - -void StickersBox::onScroll() { - updateVisibleTopBottom(); - checkLoadMoreArchived(); -} - -void StickersBox::updateVisibleTopBottom() { - auto visibleTop = scrollArea()->scrollTop(); - auto visibleBottom = visibleTop + scrollArea()->height(); - _inner->setVisibleTopBottom(visibleTop, visibleBottom); -} - -void StickersBox::checkLoadMoreArchived() { - if (_section != Section::Archived) return; - - int scrollTop = scrollArea()->scrollTop(), scrollTopMax = scrollArea()->scrollTopMax(); - if (scrollTop + PreloadHeightsCount * scrollArea()->height() >= scrollTopMax) { - if (!_archivedRequestId && !_allArchivedLoaded) { - uint64 lastId = 0; - for (auto setId = Global::ArchivedStickerSetsOrder().cend(), e = Global::ArchivedStickerSetsOrder().cbegin(); setId != e;) { - --setId; - auto it = Global::StickerSets().constFind(*setId); - if (it != Global::StickerSets().cend()) { - if (it->flags & MTPDstickerSet::Flag::f_archived) { - lastId = it->id; - break; - } - } - } - MTPmessages_GetArchivedStickers::Flags flags = 0; - _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(lastId), MTP_int(kArchivedLimitPerPage)), rpcDone(&StickersBox::getArchivedDone, lastId)); - } - } -} - -int32 StickersBox::countHeight() const { - int bottomSkip = st::boxPadding.bottom(); - if (_section == Section::Installed) { - bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom(); - } - return st::boxTitleHeight + _aboutHeight + _inner->height() + bottomSkip; -} - -void StickersBox::disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req) { - _disenableRequests.remove(req); - if (_disenableRequests.isEmpty()) { - saveOrder(); - } -} - -bool StickersBox::disenableFail(const RPCError &error, mtpRequestId req) { - if (MTP::isDefaultHandledError(error)) return false; - _disenableRequests.remove(req); - if (_disenableRequests.isEmpty()) { - saveOrder(); - } - return true; -} - -void StickersBox::saveOrder() { - auto order = _inner->getOrder(); - if (order.size() > 1) { - QVector mtpOrder; - mtpOrder.reserve(order.size()); - for (int i = 0, l = order.size(); i < l; ++i) { - mtpOrder.push_back(MTP_long(order.at(i))); - } - - MTPmessages_ReorderStickerSets::Flags flags = 0; - _reorderRequest = MTP::send(MTPmessages_ReorderStickerSets(MTP_flags(flags), MTP_vector(mtpOrder)), rpcDone(&StickersBox::reorderDone), rpcFail(&StickersBox::reorderFail)); - } else { - reorderDone(MTP_boolTrue()); - } -} - -void StickersBox::reorderDone(const MTPBool &result) { - _reorderRequest = 0; - onClose(); -} - -bool StickersBox::reorderFail(const RPCError &result) { - if (MTP::isDefaultHandledError(result)) return false; - _reorderRequest = 0; - Global::SetLastStickersUpdate(0); - App::main()->updateStickers(); - onClose(); - return true; -} - -void StickersBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - auto title = ([this]() { - if (_section == Section::Installed) { - return lang(lng_stickers_packs); - } else if (_section == Section::Featured) { - return lang(lng_stickers_featured); - } - return lang(lng_stickers_archived); - })(); - paintTitle(p, title); - p.translate(0, st::boxTitleHeight); - - if (_aboutHeight > 0) { - p.fillRect(0, 0, width(), _aboutHeight, st::contactsAboutBg); - p.setPen(st::stickersReorderFg); - _about.draw(p, st::stickersReorderPadding.top(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); - } -} - -void StickersBox::closePressed() { - if (!_disenableRequests.isEmpty()) { - for_const (auto requestId, _disenableRequests) { - MTP::cancel(requestId); - } - _disenableRequests.clear(); - Global::SetLastStickersUpdate(0); - App::main()->updateStickers(); - } else if (_reorderRequest) { - MTP::cancel(_reorderRequest); - _reorderRequest = 0; - Global::SetLastStickersUpdate(0); - App::main()->updateStickers(); - } -} - -StickersBox::~StickersBox() { - if (_section == Section::Archived) { - Local::writeArchivedStickers(); - } -} - -void StickersBox::resizeEvent(QResizeEvent *e) { - ItemListBox::resizeEvent(e); - _inner->resize(width(), _inner->height()); - _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); - updateVisibleTopBottom(); - if (_topShadow) { - _topShadow->setGeometry(0, st::boxTitleHeight + _aboutHeight, width(), st::lineWidth); - } - if (_save) { - _save->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save->height()); - } - if (_cancel) { - _cancel->moveToRight(st::boxButtonPadding.right() + _save->width() + st::boxButtonPadding.left(), _save->y()); - _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _save->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); - } -} - -void StickersBox::onStickersUpdated() { - if (_section == Section::Installed || _section == Section::Featured) { - rebuildList(); - } else { - _inner->updateRows(); - } -} - -void StickersBox::rebuildList() { - _inner->rebuild(); - setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); - _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); -} - -void StickersBox::onCheckDraggingScroll(int localY) { - if (localY < scrollArea()->scrollTop()) { - _scrollDelta = localY - scrollArea()->scrollTop(); - } else if (localY >= scrollArea()->scrollTop() + scrollArea()->height()) { - _scrollDelta = localY - scrollArea()->scrollTop() - scrollArea()->height() + 1; - } else { - _scrollDelta = 0; - } - if (_scrollDelta) { - _scrollTimer.start(15); - } else { - _scrollTimer.stop(); - } -} - -void StickersBox::onNoDraggingScroll() { - _scrollTimer.stop(); -} - -void StickersBox::onScrollTimer() { - int32 d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed)); - scrollArea()->scrollToY(scrollArea()->scrollTop() + d); -} - -void StickersBox::onSave() { - if (!_inner->savingStart()) { - return; - } - - bool writeRecent = false, writeArchived = false; - auto &recent = cGetRecentStickers(); - auto &sets = Global::RefStickerSets(); - - auto reorder = _inner->getOrder(), disabled = _inner->getDisabledSets(); - for (int32 i = 0, l = disabled.size(); i < l; ++i) { - auto it = sets.find(disabled.at(i)); - if (it != sets.cend()) { - for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) { - if (it->stickers.indexOf(i->first) >= 0) { - i = recent.erase(i); - writeRecent = true; - } else { - ++i; - } - } - if (!(it->flags & MTPDstickerSet::Flag::f_archived)) { - MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); - if (it->flags & MTPDstickerSet::Flag::f_official) { - _disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolTrue()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5)); - it->flags |= MTPDstickerSet::Flag::f_archived; - auto index = Global::RefArchivedStickerSetsOrder().indexOf(it->id); - if (index < 0) { - Global::RefArchivedStickerSetsOrder().push_front(it->id); - writeArchived = true; - } - } else { - _disenableRequests.insert(MTP::send(MTPmessages_UninstallStickerSet(setId), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5)); - int removeIndex = Global::StickerSetsOrder().indexOf(it->id); - if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); - if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { - sets.erase(it); - } else { - if (it->flags & MTPDstickerSet::Flag::f_archived) { - writeArchived = true; - } - it->flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet::Flag::f_archived); - } - } - } - } - } - - // Clear all installed flags, set only for sets from order. - for (auto &set : sets) { - if (!(set.flags & MTPDstickerSet::Flag::f_archived)) { - set.flags &= ~MTPDstickerSet::Flag::f_installed; - } - } - - auto &order(Global::RefStickerSetsOrder()); - order.clear(); - for (int i = 0, l = reorder.size(); i < l; ++i) { - auto it = sets.find(reorder.at(i)); - if (it != sets.cend()) { - if ((it->flags & MTPDstickerSet::Flag::f_archived) && !disabled.contains(it->id)) { - MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); - _disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolFalse()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5)); - it->flags &= ~MTPDstickerSet::Flag::f_archived; - writeArchived = true; - } - order.push_back(reorder.at(i)); - it->flags |= MTPDstickerSet::Flag::f_installed; - } - } - for (auto it = sets.begin(); it != sets.cend();) { - if ((it->flags & MTPDstickerSet_ClientFlag::f_featured) - || (it->flags & MTPDstickerSet::Flag::f_installed) - || (it->flags & MTPDstickerSet::Flag::f_archived) - || (it->flags & MTPDstickerSet_ClientFlag::f_special)) { - ++it; - } else { - it = sets.erase(it); - } - } - - Local::writeInstalledStickers(); - if (writeRecent) Local::writeUserSettings(); - if (writeArchived) Local::writeArchivedStickers(); - emit App::main()->stickersUpdated(); - - if (_disenableRequests.isEmpty()) { - saveOrder(); - } else { - MTP::sendAnything(); - } -} - -void StickersBox::showAll() { - if (_topShadow) { - _topShadow->show(); - } - if (_save) { - _save->show(); - } - if (_cancel) { - _cancel->show(); - _bottomShadow->show(); - } - ItemListBox::showAll(); -} - -int32 stickerPacksCount(bool includeDisabledOfficial) { - int32 result = 0; - auto &order = Global::StickerSetsOrder(); - auto &sets = Global::StickerSets(); - for (int i = 0, l = order.size(); i < l; ++i) { - auto it = sets.constFind(order.at(i)); - if (it != sets.cend()) { - if (!(it->flags & MTPDstickerSet::Flag::f_archived) || ((it->flags & MTPDstickerSet::Flag::f_official) && includeDisabledOfficial)) { - ++result; - } - } - } - return result; +StickerSetBox::Inner::~Inner() { } diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 1f9cc7561..8252ab4ab 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -24,15 +24,52 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/vector_of_moveable.h" class ConfirmBox; + namespace Ui { class PlainShadow; } // namespace Ui -class StickerSetInner : public ScrolledWidget, public RPCSender, private base::Subscriber { +class StickerSetBox : public ScrollableBox, public RPCSender { Q_OBJECT public: - StickerSetInner(const MTPInputStickerSet &set); + StickerSetBox(const MTPInputStickerSet &set); + +public slots: + void onStickersUpdated(); + void onAddStickers(); + void onShareStickers(); + void onUpdateButtons(); + + void onScroll(); + +private slots: + void onInstalled(uint64 id); + +signals: + void installed(uint64 id); + +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + + void showAll() override; + +private: + class Inner; + ChildWidget _inner; + ScrollableBoxShadow _shadow; + BoxButton _add, _share, _cancel, _done; + QString _title; + +}; + +// This class is hold in header because it requires Qt preprocessing. +class StickerSetBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { + Q_OBJECT + +public: + Inner(QWidget *parent, const MTPInputStickerSet &set); bool loaded() const; int32 notInstalled() const; @@ -43,7 +80,7 @@ public: void setVisibleTopBottom(int visibleTop, int visibleBottom) override; void install(); - ~StickerSetInner(); + ~Inner(); protected: void mousePressEvent(QMouseEvent *e) override; @@ -96,253 +133,3 @@ private: int _previewShown = -1; }; - -class StickerSetBox : public ScrollableBox, public RPCSender { - Q_OBJECT - -public: - StickerSetBox(const MTPInputStickerSet &set); - -public slots: - void onStickersUpdated(); - void onAddStickers(); - void onShareStickers(); - void onUpdateButtons(); - - void onScroll(); - -private slots: - void onInstalled(uint64 id); - -signals: - void installed(uint64 id); - -protected: - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void showAll() override; - -private: - StickerSetInner _inner; - ScrollableBoxShadow _shadow; - BoxButton _add, _share, _cancel, _done; - QString _title; - -}; - -namespace internal { -class StickersInner; -} // namespace internal - -class StickersBox : public ItemListBox, public RPCSender { - Q_OBJECT - -public: - enum class Section { - Installed, - Featured, - Archived, - ArchivedPart, - }; - StickersBox(Section section = Section::Installed); - StickersBox(const Stickers::Order &archivedIds); - - ~StickersBox(); - -public slots: - void onStickersUpdated(); - - void onCheckDraggingScroll(int localY); - void onNoDraggingScroll(); - void onScrollTimer(); - - void onSave(); - -private slots: - void onScroll(); - -protected: - void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; - - void closePressed() override; - void showAll() override; - -private: - void setup(); - int32 countHeight() const; - void rebuildList(); - - void disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req); - bool disenableFail(const RPCError &error, mtpRequestId req); - void reorderDone(const MTPBool &result); - bool reorderFail(const RPCError &result); - void saveOrder(); - - void updateVisibleTopBottom(); - void checkLoadMoreArchived(); - void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result); - - Section _section; - - ChildWidget _inner; - ChildWidget _save = { nullptr }; - ChildWidget _cancel = { nullptr }; - OrderedSet _disenableRequests; - mtpRequestId _reorderRequest = 0; - ChildWidget _topShadow = { nullptr }; - ChildWidget _bottomShadow = { nullptr }; - - QTimer _scrollTimer; - int32 _scrollDelta = 0; - - int _aboutWidth = 0; - Text _about; - int _aboutHeight = 0; - - mtpRequestId _archivedRequestId = 0; - bool _allArchivedLoaded = false; - -}; - -int32 stickerPacksCount(bool includeDisabledOfficial = false); - -namespace internal { - -class StickersInner : public ScrolledWidget, public RPCSender, private base::Subscriber { - Q_OBJECT - -public: - using Section = StickersBox::Section; - StickersInner(Section section); - StickersInner(const Stickers::Order &archivedIds); - - void rebuild(); - void updateSize(); - void updateRows(); // refresh only pack cover stickers - bool appendSet(const Stickers::Set &set); - bool savingStart() { - if (_saving) return false; - _saving = true; - return true; - } - - Stickers::Order getOrder() const; - Stickers::Order getDisabledSets() const; - - void setVisibleScrollbar(int32 width); - void setVisibleTopBottom(int visibleTop, int visibleBottom) override; - - ~StickersInner(); - -protected: - void paintEvent(QPaintEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void leaveEvent(QEvent *e) override; - -signals: - void checkDraggingScroll(int localY); - void noDraggingScroll(); - -public slots: - void onUpdateSelected(); - void onClearRecent(); - void onClearBoxDestroyed(QObject *box); - -private slots: - void onImageLoaded(); - -private: - void setup(); - void paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const; - - void step_shifting(uint64 ms, bool timer); - void paintRow(Painter &p, int32 index); - void clear(); - void setActionSel(int32 actionSel); - float64 aboveShadowOpacity() const; - - void readVisibleSets(); - - void installSet(uint64 setId); - void installDone(const MTPmessages_StickerSetInstallResult &result); - bool installFail(uint64 setId, const RPCError &error); - - Section _section; - Stickers::Order _archivedIds; - - int32 _rowHeight; - struct StickerSetRow { - StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, int titleWidth, bool installed, bool official, bool unread, bool disabled, bool recent, int32 pixw, int32 pixh) : id(id) - , sticker(sticker) - , count(count) - , title(title) - , titleWidth(titleWidth) - , installed(installed) - , official(official) - , unread(unread) - , disabled(disabled) - , recent(recent) - , pixw(pixw) - , pixh(pixh) - , yadd(0, 0) { - } - uint64 id; - DocumentData *sticker; - int32 count; - QString title; - int titleWidth; - bool installed, official, unread, disabled, recent; - int32 pixw, pixh; - anim::ivalue yadd; - }; - using StickerSetRows = QList; - - void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth); - void fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const; - int fillSetCount(const Stickers::Set &set) const; - QString fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const; - void fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled); - - int countMaxNameWidth() const; - - StickerSetRows _rows; - QList _animStartTimes; - uint64 _aboveShadowFadeStart = 0; - anim::fvalue _aboveShadowFadeOpacity = { 0., 0. }; - Animation _a_shifting; - - int _visibleTop = 0; - int _visibleBottom = 0; - int _itemsTop = 0; - - bool _saving = false; - - int _actionSel = -1; - int _actionDown = -1; - - int _clearWidth, _removeWidth, _returnWidth, _restoreWidth; - - ConfirmBox *_clearBox = nullptr; - - int _buttonHeight = 0; - bool _hasFeaturedButton = false; - bool _hasArchivedButton = false; - - QPoint _mouse; - int _selected = -3; // -2 - featured stickers button, -1 - archived stickers button - int _pressed = -2; - QPoint _dragStart; - int _started = -1; - int _dragging = -1; - int _above = -1; - - Ui::RectShadow _aboveShadow; - - int32 _scrollbar = 0; -}; - -} // namespace internal diff --git a/Telegram/SourceFiles/codegen/style/parsed_file.cpp b/Telegram/SourceFiles/codegen/style/parsed_file.cpp index f45ecebad..1f5ab35f9 100644 --- a/Telegram/SourceFiles/codegen/style/parsed_file.cpp +++ b/Telegram/SourceFiles/codegen/style/parsed_file.cpp @@ -243,7 +243,7 @@ structure::Variable ParsedFile::readVariable(const QString &name) { structure::Variable result = { composeFullName(name) }; if (auto value = readValue()) { result.value = value; - if (value.type().tag != structure::TypeTag::Struct) { + if (value.type().tag != structure::TypeTag::Struct || !value.copyOf().empty()) { assertNextToken(BasicType::Semicolon); } } diff --git a/Telegram/SourceFiles/core/lambda_wrap.h b/Telegram/SourceFiles/core/lambda_wrap.h index fc0ce2224..3e211781d 100644 --- a/Telegram/SourceFiles/core/lambda_wrap.h +++ b/Telegram/SourceFiles/core/lambda_wrap.h @@ -60,7 +60,7 @@ struct lambda_wrap_helper_base { protected: static void bad_construct_copy(void *lambda, const void *source) { - throw std::exception(); + t_assert(!"base::lambda bad_construct_copy() called!"); } }; @@ -72,7 +72,8 @@ struct lambda_wrap_empty : public lambda_wrap_helper_base { static void construct_move_other_method(void *lambda, void *source) { } static Return call_method(const void *lambda, Args... args) { - throw std::exception(); + t_assert(!"base::lambda empty call_method() called!"); + return Return(); } static void destruct_method(const void *lambda) { } @@ -359,6 +360,10 @@ public: } } + lambda_wrap clone() const { + return *this; + } + template > lambda_wrap(const Lambda &other) : Parent(&internal::lambda_wrap_helper_copy::instance, typename Parent::Private()) { internal::lambda_wrap_helper_copy::construct_copy_lambda_method(this->storage_, &other); diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index d09ad184b..ae7d0e116 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -35,6 +35,9 @@ historyToDownPaddingTop: 10px; historyToDownBadgeFont: semiboldFont; historyToDownBadgeSize: 22px; +historyEmptyDog: icon {{ "history_empty_dog", #ffffff }}; +historyEmptySize: 128px; + membersInnerScroll: flatScroll(solidScroll) { deltat: 3px; deltab: 3px; diff --git a/Telegram/SourceFiles/history/history_service_layout.cpp b/Telegram/SourceFiles/history/history_service_layout.cpp index fa0db9f69..b69742070 100644 --- a/Telegram/SourceFiles/history/history_service_layout.cpp +++ b/Telegram/SourceFiles/history/history_service_layout.cpp @@ -334,6 +334,16 @@ QVector ServiceMessagePainter::countLineWidths(const Text &text, const QRec return lineWidths; } +void paintEmpty(Painter &p, int width, int height) { + auto position = QPoint((width - st::historyEmptySize) / 2, ((height - st::historyEmptySize) * 4) / 9); + p.setPen(Qt::NoPen); + p.setBrush(App::msgServiceBg()); + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(rtlrect(position.x(), position.y(), st::historyEmptySize, st::historyEmptySize, width)); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + st::historyEmptyDog.paint(p, position.x() + (st::historyEmptySize - st::historyEmptyDog.width()) / 2, position.y() + (st::historyEmptySize - st::historyEmptyDog.height()) / 2, width); +} + void serviceColorsUpdated() { if (serviceMessageStyle) { for (auto &corner : serviceMessageStyle->corners) { diff --git a/Telegram/SourceFiles/history/history_service_layout.h b/Telegram/SourceFiles/history/history_service_layout.h index 4c9beee4d..faae5d450 100644 --- a/Telegram/SourceFiles/history/history_service_layout.h +++ b/Telegram/SourceFiles/history/history_service_layout.h @@ -48,6 +48,8 @@ private: }; +void paintEmpty(Painter &p, int width, int height); + void serviceColorsUpdated(); } // namespace HistoryLayout diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index a8917224e..e9a7beee6 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -376,8 +376,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { textstyleRestore(); } } else if (noHistoryDisplayed) { - QPoint dogPos((width() - st::msgDogImg.pxWidth()) / 2, ((height() - st::msgDogImg.pxHeight()) * 4) / 9); - p.drawPixmap(dogPos, Window::chatBackground()->dog()); + HistoryLayout::paintEmpty(p, width(), height()); } if (!noHistoryDisplayed) { adjustCurrent(r.top()); @@ -8776,8 +8775,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { } if (_scroll.isHidden()) { p.setClipRect(_scroll.geometry()); - QPoint dogPos((width() - st::msgDogImg.pxWidth()) / 2, ((height() - _field.height() - 2 * st::sendPadding - st::msgDogImg.pxHeight()) * 4) / 9); - p.drawPixmap(dogPos, Window::chatBackground()->dog()); + HistoryLayout::paintEmpty(p, width(), height() - _field.height() - 2 * st::sendPadding); } } else { style::font font(st::msgServiceFont); diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp index 4aa94953a..bcdcd1e2e 100644 --- a/Telegram/SourceFiles/layout.cpp +++ b/Telegram/SourceFiles/layout.cpp @@ -230,11 +230,6 @@ style::color documentSelectedColor(int32 colorIndex) { return colors[colorIndex & 3]; } -style::sprite documentCorner(int32 colorIndex) { - static style::sprite corners[] = { st::msgFileBlue, st::msgFileGreen, st::msgFileRed, st::msgFileYellow }; - return corners[colorIndex & 3]; -} - RoundCorners documentCorners(int32 colorIndex) { return RoundCorners(DocBlueCorners + (colorIndex & 3)); } diff --git a/Telegram/SourceFiles/layout.h b/Telegram/SourceFiles/layout.h index 385050401..bb5ff4307 100644 --- a/Telegram/SourceFiles/layout.h +++ b/Telegram/SourceFiles/layout.h @@ -86,7 +86,6 @@ style::color documentColor(int32 colorIndex); style::color documentDarkColor(int32 colorIndex); style::color documentOverColor(int32 colorIndex); style::color documentSelectedColor(int32 colorIndex); -style::sprite documentCorner(int32 colorIndex); RoundCorners documentCorners(int32 colorIndex); bool documentIsValidMediaFile(const QString &filepath); diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index e48dcbaeb..7ce562e00 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -715,7 +715,7 @@ HitTestType MainWindow::hitTest(const QPoint &p) const { } QRect MainWindow::iconRect() const { - return QRect(st::titleIconPos + title->geometry().topLeft(), st::titleIconImg.pxSize()); + return title->iconRect(); } bool MainWindow::eventFilter(QObject *obj, QEvent *e) { diff --git a/Telegram/SourceFiles/media/view/mediaview.style b/Telegram/SourceFiles/media/view/mediaview.style index e2888cc70..7f43918ef 100644 --- a/Telegram/SourceFiles/media/view/mediaview.style +++ b/Telegram/SourceFiles/media/view/mediaview.style @@ -85,19 +85,28 @@ mediaviewClose: icon {{ "mediaview_close", #ffffff }}; mediaviewSave: icon {{ "mediaview_download", #ffffff }}; mediaviewMore: icon {{ "mediaview_more", #ffffff }}; +mediaviewFileRedCornerFg: #d55959; +mediaviewFileYellowCornerFg: #e8a659; +mediaviewFileGreenCornerFg: #49a957; +mediaviewFileBlueCornerFg: #599dcf; + mediaviewFileRed: icon { { size(25px, 25px), #ffffff }, - { "mediaview_file_corner", #d55959 }, + { "mediaview_file_corner", mediaviewFileRedCornerFg }, }; mediaviewFileYellow: icon { { size(25px, 25px), #ffffff }, - { "mediaview_file_corner", #e8a659 }, + { "mediaview_file_corner", mediaviewFileYellowCornerFg }, }; mediaviewFileGreen: icon { { size(25px, 25px), #ffffff }, - { "mediaview_file_corner", #49a957 }, + { "mediaview_file_corner", mediaviewFileGreenCornerFg }, }; mediaviewFileBlue: icon { { size(25px, 25px), #ffffff }, - { "mediaview_file_corner", #599dcf }, + { "mediaview_file_corner", mediaviewFileBlueCornerFg }, }; + +mediaviewTransparentBg: #ffffff; +mediaviewTransparentFg: #cccccc; +mediaviewTransparentSize: 4px; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 82d603b4c..eb3c30909 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -103,7 +103,7 @@ MediaView::MediaView() : TWidget(App::wnd()) } }); - _transparentBrush = QBrush(App::sprite().copy(st::mvTransparentBrush.rect())); + generateTransparentBrush(); setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::Tool | Qt::NoDropShadowWindowHint); moveToScreen(); @@ -2628,6 +2628,19 @@ void MediaView::loadBack() { } } +void MediaView::generateTransparentBrush() { + auto size = st::mediaviewTransparentSize * cIntRetinaFactor(); + auto transparent = QImage(2 * size, 2 * size, QImage::Format_ARGB32_Premultiplied); + transparent.fill(st::mediaviewTransparentBg->c); + { + Painter p(&transparent); + p.fillRect(rtlrect(0, size, size, size, 2 * size), st::mediaviewTransparentFg); + p.fillRect(rtlrect(size, 0, size, size, 2 * size), st::mediaviewTransparentFg); + } + transparent.setDevicePixelRatio(cRetinaFactor()); + _transparentBrush = QBrush(transparent); +} + MediaView::LastChatPhoto MediaView::computeLastOverviewChatPhoto() { LastChatPhoto emptyResult = { nullptr, nullptr }; auto lastPhotoInOverview = [&emptyResult](auto history, auto list) -> LastChatPhoto { diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 320b86fb5..b86383165 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -136,6 +136,8 @@ private: void findCurrent(); void loadBack(); + void generateTransparentBrush(); + void updateCursor(); void setZoomLevel(int newZoom); diff --git a/Telegram/SourceFiles/overview/overview.style b/Telegram/SourceFiles/overview/overview.style index c010d0191..7250e6d15 100644 --- a/Telegram/SourceFiles/overview/overview.style +++ b/Telegram/SourceFiles/overview/overview.style @@ -20,6 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ using "basic.style"; using "history/history.style"; +using "media/view/mediaview.style"; OverviewFileLayout { maxWidth: pixels; diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index b58b9a468..ff5f18a72 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -34,6 +34,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "application.h" #include "overview/overview_layout.h" #include "history/history_media_types.h" +#include "history/history_service_layout.h" #include "media/media_audio.h" // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html @@ -778,8 +779,7 @@ void OverviewInner::paintEvent(QPaintEvent *e) { Overview::Layout::PaintContext context(ms, _selMode); if (_history->overview[_type].isEmpty() && (!_migrated || !_history->overviewLoaded(_type) || _migrated->overview[_type].isEmpty())) { - QPoint dogPos((_width - st::msgDogImg.pxWidth()) / 2, ((height() - st::msgDogImg.pxHeight()) * 4) / 9); - p.drawPixmap(dogPos, Window::chatBackground()->dog()); + HistoryLayout::paintEmpty(p, _width, height()); return; } else if (_inSearch && _searchResults.isEmpty() && _searchFull && (!_migrated || _searchFullMigrated) && !_searchTimer.isActive()) { p.setFont(st::noContactsFont->f); diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index d9ac7434d..96dd3da7a 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -529,14 +529,14 @@ void CoverWidget::onAddMember() { if (_peerChat->count >= Global::ChatSizeMax() && _peerChat->amCreator()) { Ui::showLayer(new ConvertToSupergroupBox(_peerChat)); } else { - Ui::showLayer(new ContactsBox(_peerChat, MembersFilterRecent)); + Ui::showLayer(new ContactsBox(_peerChat, MembersFilter::Recent)); } } else if (_peerChannel && _peerChannel->mgInfo) { MembersAlreadyIn already; for_const (auto user, _peerChannel->mgInfo->lastParticipants) { already.insert(user); } - Ui::showLayer(new ContactsBox(_peerChannel, MembersFilterRecent, already)); + Ui::showLayer(new ContactsBox(_peerChannel, MembersFilter::Recent, already)); } } diff --git a/Telegram/SourceFiles/profile/profile_members_widget.cpp b/Telegram/SourceFiles/profile/profile_members_widget.cpp index 022f96772..2f9b1a6c8 100644 --- a/Telegram/SourceFiles/profile/profile_members_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_members_widget.cpp @@ -700,13 +700,13 @@ int ChannelMembersWidget::resizeGetHeight(int newWidth) { void ChannelMembersWidget::onAdmins() { if (auto channel = peer()->asChannel()) { - Ui::showLayer(new MembersBox(channel, MembersFilterAdmins)); + Ui::showLayer(new MembersBox(channel, MembersFilter::Admins)); } } void ChannelMembersWidget::onMembers() { if (auto channel = peer()->asChannel()) { - Ui::showLayer(new MembersBox(channel, MembersFilterRecent)); + Ui::showLayer(new MembersBox(channel, MembersFilter::Recent)); } } diff --git a/Telegram/SourceFiles/profile/profile_settings_widget.cpp b/Telegram/SourceFiles/profile/profile_settings_widget.cpp index d94ec9868..4c6d254c8 100644 --- a/Telegram/SourceFiles/profile/profile_settings_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_settings_widget.cpp @@ -164,9 +164,9 @@ void SettingsWidget::onNotificationsChange() { void SettingsWidget::onManageAdmins() { if (auto chat = peer()->asChat()) { - Ui::showLayer(new ContactsBox(chat, MembersFilterAdmins)); + Ui::showLayer(new ContactsBox(chat, MembersFilter::Admins)); } else if (auto channel = peer()->asChannel()) { - Ui::showLayer(new MembersBox(channel, MembersFilterAdmins)); + Ui::showLayer(new MembersBox(channel, MembersFilter::Admins)); } } diff --git a/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp b/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp index 53ae1053a..329da42a4 100644 --- a/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp @@ -29,7 +29,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "mainwindow.h" #include "boxes/emojibox.h" -#include "boxes/stickersetbox.h" +#include "boxes/stickers_box.h" #include "boxes/downloadpathbox.h" #include "boxes/connectionbox.h" #include "boxes/confirmbox.h" diff --git a/Telegram/SourceFiles/stickers/emoji_pan.cpp b/Telegram/SourceFiles/stickers/emoji_pan.cpp index a7f0e5563..74dca4181 100644 --- a/Telegram/SourceFiles/stickers/emoji_pan.cpp +++ b/Telegram/SourceFiles/stickers/emoji_pan.cpp @@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_stickers.h" #include "boxes/confirmbox.h" #include "boxes/stickersetbox.h" +#include "boxes/stickers_box.h" #include "inline_bots/inline_bot_result.h" #include "inline_bots/inline_bot_layout_item.h" #include "dialogs/dialogs_layout.h" diff --git a/Telegram/SourceFiles/stickers/stickers.cpp b/Telegram/SourceFiles/stickers/stickers.cpp index 266ede52d..0a47e900d 100644 --- a/Telegram/SourceFiles/stickers/stickers.cpp +++ b/Telegram/SourceFiles/stickers/stickers.cpp @@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "stickers.h" -#include "boxes/stickersetbox.h" +#include "boxes/stickers_box.h" #include "boxes/confirmbox.h" #include "lang.h" #include "apiwrap.h" diff --git a/Telegram/SourceFiles/stickers/stickers.style b/Telegram/SourceFiles/stickers/stickers.style index c215aadcf..c1a9168a1 100644 --- a/Telegram/SourceFiles/stickers/stickers.style +++ b/Telegram/SourceFiles/stickers/stickers.style @@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ using "basic.style"; +using "boxes/boxes.style"; + featuredStickersHeader: 45px; featuredStickersSkip: 15px; diff --git a/Telegram/SourceFiles/title.cpp b/Telegram/SourceFiles/title.cpp index 0a34528a2..9722ac9d4 100644 --- a/Telegram/SourceFiles/title.cpp +++ b/Telegram/SourceFiles/title.cpp @@ -31,6 +31,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "media/player/media_player_title_button.h" #include "media/player/media_player_panel.h" #include "media/player/media_player_instance.h" +#include "styles/style_window.h" class TitleWidget::Hider : public TWidget { public: @@ -126,9 +127,9 @@ void TitleWidget::paintEvent(QPaintEvent *e) { auto chooseText = lang(inlineSwitchChoose ? lng_inline_switch_choose : lng_forward_choose); p.drawText(st::titleMenuOffset - st::titleTextButton.width / 2, st::titleTextButton.textTop + st::titleTextButton.font->ascent, chooseText); } - p.drawSprite(st::titleIconPos, st::titleIconImg); + st::titleIcon.paint(p, st::titleIconPosition, width()); if (Adaptive::OneColumn() && !_counter.isNull() && App::main()) { - p.drawPixmap(st::titleIconPos.x() + st::titleIconImg.pxWidth() - (_counter.width() / cIntRetinaFactor()), st::titleIconPos.y() + st::titleIconImg.pxHeight() - (_counter.height() / cIntRetinaFactor()), _counter); + p.drawPixmap(st::titleCounterPosition, _counter); } } @@ -335,11 +336,11 @@ void TitleWidget::updateCounter() { } _counter = App::pixmapFromImageInPlace(App::wnd()->iconWithCounter(size, counter, bg, false)); _counter.setDevicePixelRatio(cRetinaFactor()); - update(QRect(st::titleIconPos, st::titleIconImg.pxSize())); + update(QRect(st::titleCounterPosition, _counter.size() / cIntRetinaFactor())); } else { if (!_counter.isNull()) { + update(QRect(st::titleCounterPosition, _counter.size() / cIntRetinaFactor())); _counter = QPixmap(); - update(QRect(st::titleIconPos, st::titleIconImg.pxSize())); } } } @@ -395,7 +396,7 @@ HitTestType TitleWidget::hitTest(const QPoint &p) { int x(p.x()), y(p.y()), w(width()), h(height()); if (!Adaptive::OneColumn() && _hider && x >= App::main()->dlgsWidth()) return HitTestType::None; - if (x >= st::titleIconPos.x() && y >= st::titleIconPos.y() && x < st::titleIconPos.x() + st::titleIconImg.pxWidth() && y < st::titleIconPos.y() + st::titleIconImg.pxHeight()) { + if (x >= st::titleIconPosition.x() && y >= st::titleIconPosition.y() && x < st::titleIconPosition.x() + st::titleIcon.width() && y < st::titleIconPosition.y() + st::titleIcon.height()) { return HitTestType::Icon; } else if (false || (_player && _player->geometry().contains(p)) @@ -420,3 +421,7 @@ HitTestType TitleWidget::hitTest(const QPoint &p) { } return HitTestType::None; } + +QRect TitleWidget::iconRect() const { + return myrtlrect(QRect(st::titleIconPosition, st::titleIcon.size())); +} diff --git a/Telegram/SourceFiles/title.h b/Telegram/SourceFiles/title.h index 6cfd8edff..ea278669e 100644 --- a/Telegram/SourceFiles/title.h +++ b/Telegram/SourceFiles/title.h @@ -42,6 +42,7 @@ public: void maximizedChanged(bool maximized, bool force = false); HitTestType hitTest(const QPoint &p); + QRect iconRect() const; void setHideLevel(float64 level); diff --git a/Telegram/SourceFiles/ui/animation.h b/Telegram/SourceFiles/ui/animation.h index 1018053f2..8f2b7f427 100644 --- a/Telegram/SourceFiles/ui/animation.h +++ b/Telegram/SourceFiles/ui/animation.h @@ -102,6 +102,22 @@ namespace anim { float64 easeInQuint(const float64 &delta, const float64 &dt); float64 easeOutQuint(const float64 &delta, const float64 &dt); + template + float64 bumpy(const float64 &delta, const float64 &dt) { + struct Bumpy { + Bumpy() + : bump(BumpRatioNumerator / float64(BumpRatioDenominator)) + , dt0(bump - sqrt(bump * (bump - 1.))) + , k(1 / (2 * dt0 - 1)) { + } + float64 bump; + float64 dt0; + float64 k; + }; + static Bumpy data; + return delta * (data.bump - data.k * (dt - data.dt0) * (dt - data.dt0)); + } + class fvalue { // float animated value public: using ValueType = float64; @@ -468,6 +484,15 @@ public: using ValueType = typename AnimType::ValueType; using Callback = base::lambda_unique; + void step(uint64 ms) { + if (_data) { + _data->a_animation.step(ms); + if (_data && !_data->a_animation.animating()) { + _data.reset(); + } + } + } + bool animating() const { if (_data) { if (_data->a_animation.animating()) { @@ -478,11 +503,8 @@ public: return false; } bool animating(uint64 ms) { - if (animating()) { - _data->a_animation.step(ms); - return animating(); - } - return false; + step(ms); + return animating(); } ValueType current() const { @@ -499,7 +521,7 @@ public: template void start(Lambda &&updateCallback, const ValueType &from, const ValueType &to, float64 duration, anim::transition transition = anim::linear) { if (!_data) { - _data = std_::make_unique(from, std_::move(updateCallback)); + _data = std_::make_unique(from, std_::forward(updateCallback)); } _data->value.start(to); _data->duration = duration; @@ -517,11 +539,17 @@ public: private: struct Data { - Data(const ValueType &from, Callback &&updateCallback) + template ::value>> + Data(const ValueType &from, Lambda &&updateCallback) : value(from, from) , a_animation(animation(this, &Data::step)) , updateCallback(std_::move(updateCallback)) { } + Data(const ValueType &from, const base::lambda_wrap &updateCallback) + : value(from, from) + , a_animation(animation(this, &Data::step)) + , updateCallback(base::lambda_wrap(updateCallback)) { + } void step(float64 ms, bool timer) { auto dt = (ms >= duration) ? 1. : (ms / duration); if (dt >= 1) { diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index 40a2c0f16..c4a390e9c 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -24,8 +24,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "application.h" #include "ui/scrollarea.h" +#include "ui/widgets/multi_select.h" #include "boxes/contactsbox.h" #include "countries.h" +#include "styles/style_boxes.h" namespace { @@ -63,7 +65,8 @@ namespace { countriesFiltered.reserve(countriesCount); countriesNames.resize(countriesCount); } -} + +} // namespace const CountriesByCode &countriesByCode() { initCountries(); @@ -192,7 +195,73 @@ void CountryInput::setText(const QString &newText) { _text = _st.font->elided(newText, width() - _st.textMrg.left() - _st.textMrg.right()); } -CountrySelectInner::CountrySelectInner() : TWidget() +CountrySelectBox::CountrySelectBox() : ItemListBox(st::countriesScroll, st::boxWidth) +, _inner(this) +, _select(this, st::contactsMultiSelect, lang(lng_country_ph)) +, _topShadow(this) { + _select->resizeToWidth(st::boxWidth); + + ItemListBox::init(_inner, st::boxScrollSkip, st::boxTitleHeight + _select->height()); + + _select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); + _select->setSubmittedCallback([this](bool) { onSubmit(); }); + connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); + connect(_inner, SIGNAL(countryChosen(const QString&)), this, SIGNAL(countryChosen(const QString&))); + + prepare(); +} + +void CountrySelectBox::onSubmit() { + _inner->chooseCountry(); +} + +void CountrySelectBox::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Down) { + _inner->selectSkip(1); + } else if (e->key() == Qt::Key_Up) { + _inner->selectSkip(-1); + } else if (e->key() == Qt::Key_PageDown) { + _inner->selectSkipPage(scrollArea()->height(), 1); + } else if (e->key() == Qt::Key_PageUp) { + _inner->selectSkipPage(scrollArea()->height(), -1); + } else { + ItemListBox::keyPressEvent(e); + } +} + +void CountrySelectBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + paintTitle(p, lang(lng_country_select)); +} + +void CountrySelectBox::resizeEvent(QResizeEvent *e) { + ItemListBox::resizeEvent(e); + + _select->resizeToWidth(width()); + _select->moveToLeft(0, st::boxTitleHeight); + + _inner->resizeToWidth(width()); + _topShadow.setGeometry(0, st::boxTitleHeight + _select->height(), width(), st::lineWidth); +} + +void CountrySelectBox::showAll() { + _select->show(); + _topShadow.show(); + ItemListBox::showAll(); +} + +void CountrySelectBox::onFilterUpdate(const QString &query) { + scrollArea()->scrollToY(0); + _inner->updateFilter(query); +} + +void CountrySelectBox::doSetInnerFocus() { + _select->setInnerFocus(); +} + +CountrySelectBox::Inner::Inner(QWidget *parent) : ScrolledWidget(parent) , _rowHeight(st::countryRowHeight) , _sel(0) , _mouseSel(false) { @@ -239,7 +308,7 @@ CountrySelectInner::CountrySelectInner() : TWidget() updateFilter(); } -void CountrySelectInner::paintEvent(QPaintEvent *e) { +void CountrySelectBox::Inner::paintEvent(QPaintEvent *e) { Painter p(this); QRect r(e->rect()); p.setClipRect(r); @@ -283,11 +352,11 @@ void CountrySelectInner::paintEvent(QPaintEvent *e) { } } -void CountrySelectInner::enterEvent(QEvent *e) { +void CountrySelectBox::Inner::enterEvent(QEvent *e) { setMouseTracking(true); } -void CountrySelectInner::leaveEvent(QEvent *e) { +void CountrySelectBox::Inner::leaveEvent(QEvent *e) { _mouseSel = false; setMouseTracking(false); if (_sel >= 0) { @@ -296,13 +365,13 @@ void CountrySelectInner::leaveEvent(QEvent *e) { } } -void CountrySelectInner::mouseMoveEvent(QMouseEvent *e) { +void CountrySelectBox::Inner::mouseMoveEvent(QMouseEvent *e) { _mouseSel = true; _lastMousePos = e->globalPos(); updateSel(); } -void CountrySelectInner::mousePressEvent(QMouseEvent *e) { +void CountrySelectBox::Inner::mousePressEvent(QMouseEvent *e) { _mouseSel = true; _lastMousePos = e->globalPos(); updateSel(); @@ -311,7 +380,7 @@ void CountrySelectInner::mousePressEvent(QMouseEvent *e) { } } -void CountrySelectInner::updateFilter(QString filter) { +void CountrySelectBox::Inner::updateFilter(QString filter) { filter = textSearchKey(filter); QStringList f; @@ -366,7 +435,7 @@ void CountrySelectInner::updateFilter(QString filter) { } } -void CountrySelectInner::selectSkip(int32 dir) { +void CountrySelectBox::Inner::selectSkip(int32 dir) { _mouseSel = false; int cur = (_sel >= 0) ? _sel : -1; @@ -384,13 +453,13 @@ void CountrySelectInner::selectSkip(int32 dir) { update(); } -void CountrySelectInner::selectSkipPage(int32 h, int32 dir) { +void CountrySelectBox::Inner::selectSkipPage(int32 h, int32 dir) { int32 points = h / _rowHeight; if (!points) return; selectSkip(points * dir); } -void CountrySelectInner::chooseCountry() { +void CountrySelectBox::Inner::chooseCountry() { QString result; if (_filter.isEmpty()) { if (_sel >= 0 && _sel < countriesAll.size()) { @@ -404,11 +473,11 @@ void CountrySelectInner::chooseCountry() { emit countryChosen(result); } -void CountrySelectInner::refresh() { +void CountrySelectBox::Inner::refresh() { resize(width(), countriesNow->length() ? (countriesNow->length() * _rowHeight + st::countriesSkip) : st::noContactsHeight); } -void CountrySelectInner::updateSel() { +void CountrySelectBox::Inner::updateSel() { if (!_mouseSel) return; QPoint p(mapFromGlobal(_lastMousePos)); @@ -422,89 +491,8 @@ void CountrySelectInner::updateSel() { } } -void CountrySelectInner::updateSelectedRow() { +void CountrySelectBox::Inner::updateSelectedRow() { if (_sel >= 0) { update(0, st::countriesSkip + _sel * _rowHeight, width(), _rowHeight); } } - -CountrySelectBox::CountrySelectBox() : ItemListBox(st::countriesScroll, st::boxWidth) -, _inner() -, _filter(this, st::boxSearchField, lang(lng_country_ph)) -, _filterCancel(this, st::boxSearchCancel) -, _topShadow(this) { - ItemListBox::init(&_inner, st::boxScrollSkip, st::boxTitleHeight + _filter.height()); - - connect(&_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); - connect(&_filter, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel())); - connect(&_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); - connect(&_inner, SIGNAL(countryChosen(const QString&)), this, SIGNAL(countryChosen(const QString&))); - - _filterCancel.setAttribute(Qt::WA_OpaquePaintEvent); - - prepare(); -} - -void CountrySelectBox::onSubmit() { - _inner.chooseCountry(); -} - -void CountrySelectBox::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Down) { - _inner.selectSkip(1); - } else if (e->key() == Qt::Key_Up) { - _inner.selectSkip(-1); - } else if (e->key() == Qt::Key_PageDown) { - _inner.selectSkipPage(scrollArea()->height(), 1); - } else if (e->key() == Qt::Key_PageUp) { - _inner.selectSkipPage(scrollArea()->height(), -1); - } else { - ItemListBox::keyPressEvent(e); - } -} - -void CountrySelectBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, lang(lng_country_select)); -} - -void CountrySelectBox::resizeEvent(QResizeEvent *e) { - ItemListBox::resizeEvent(e); - _filter.resize(width(), _filter.height()); - _filter.moveToLeft(0, st::boxTitleHeight); - _filterCancel.moveToRight(0, st::boxTitleHeight); - _inner.resize(width(), _inner.height()); - _topShadow.setGeometry(0, st::boxTitleHeight + _filter.height(), width(), st::lineWidth); -} - -void CountrySelectBox::showAll() { - _filter.show(); - if (_filter.getLastText().isEmpty()) { - _filterCancel.hide(); - } else { - _filterCancel.show(); - } - _topShadow.show(); - ItemListBox::showAll(); -} - -void CountrySelectBox::onFilterCancel() { - _filter.setText(QString()); -} - -void CountrySelectBox::onFilterUpdate() { - scrollArea()->scrollToY(0); - if (_filter.getLastText().isEmpty()) { - _filterCancel.hide(); - } else { - _filterCancel.show(); - } - _inner.updateFilter(_filter.getLastText()); -} - -void CountrySelectBox::doSetInnerFocus() { - _filter.setFocus(); -} diff --git a/Telegram/SourceFiles/ui/countryinput.h b/Telegram/SourceFiles/ui/countryinput.h index 8292f0187..fe3b217a9 100644 --- a/Telegram/SourceFiles/ui/countryinput.h +++ b/Telegram/SourceFiles/ui/countryinput.h @@ -30,6 +30,10 @@ QString findValidCode(QString fullCode); class CountrySelect; +namespace Ui { +class MultiSelect; +} // namespace Ui + class CountryInput : public QWidget { Q_OBJECT @@ -63,11 +67,47 @@ private: }; -class CountrySelectInner : public TWidget { +namespace internal { +class CountrySelectInner; +} // namespace internal + +class CountrySelectBox : public ItemListBox { Q_OBJECT public: - CountrySelectInner(); + CountrySelectBox(); + +signals: + void countryChosen(const QString &iso); + +public slots: + void onSubmit(); + +protected: + void keyPressEvent(QKeyEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + + void doSetInnerFocus() override; + void showAll() override; + +private: + void onFilterUpdate(const QString &query); + + class Inner; + ChildWidget _inner; + ChildWidget _select; + + ScrollableBoxShadow _topShadow; + +}; + +// This class is hold in header because it requires Qt preprocessing. +class CountrySelectBox::Inner : public ScrolledWidget { + Q_OBJECT + +public: + Inner(QWidget *parent); void updateFilter(QString filter = QString()); @@ -104,34 +144,3 @@ private: QPoint _lastMousePos; }; - -class CountrySelectBox : public ItemListBox { - Q_OBJECT - -public: - CountrySelectBox(); - -signals: - void countryChosen(const QString &iso); - -public slots: - void onFilterUpdate(); - void onFilterCancel(); - void onSubmit(); - -protected: - void keyPressEvent(QKeyEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void doSetInnerFocus() override; - void showAll() override; - -private: - CountrySelectInner _inner; - InputField _filter; - IconedButton _filterCancel; - - ScrollableBoxShadow _topShadow; - -}; diff --git a/Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp b/Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp new file mode 100644 index 000000000..f0fcb5456 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp @@ -0,0 +1,217 @@ +/* +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/round_image_checkbox.h" + +namespace Ui { +namespace { + +static constexpr int kWideScale = 4; + +void prepareCheckCaches(const style::RoundImageCheckbox *st, QPixmap &checkBgCache, QPixmap &checkFullCache) { + auto size = st->checkRadius * 2; + auto wideSize = size * kWideScale; + auto cache = QImage(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&cache); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, wideSize, wideSize, Qt::transparent); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + auto pen = st->checkBorder->p; + pen.setWidth(st->selectWidth); + p.setPen(pen); + p.setBrush(st->checkBg); + auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size); + p.drawEllipse(ellipse); + } + auto cacheIcon = cache; + { + Painter p(&cacheIcon); + auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size); + st->checkIcon.paint(p, ellipse.topLeft(), wideSize); + } + checkBgCache = App::pixmapFromImageInPlace(std_::move(cache)); + checkBgCache.setDevicePixelRatio(cRetinaFactor()); + checkFullCache = App::pixmapFromImageInPlace(std_::move(cacheIcon)); + checkFullCache.setDevicePixelRatio(cRetinaFactor()); +} + +} // namespace + +RoundImageCheckbox::RoundImageCheckbox(const style::RoundImageCheckbox &st, base::lambda_wrap updateCallback, PaintRoundImage paintRoundImage) +: _st(st) +, _updateCallback(std_::move(updateCallback)) +, _paintRoundImage(std_::move(paintRoundImage)) { + prepareCheckCaches(&_st, _wideCheckBgCache, _wideCheckFullCache); +} + +void RoundImageCheckbox::paint(Painter &p, uint64 ms, int x, int y, int outerWidth) { + _selection.step(ms); + for (auto &icon : _icons) { + icon.fadeIn.step(ms); + icon.fadeOut.step(ms); + } + removeFadeOutedIcons(); + + auto selectionLevel = _selection.current(_checked ? 1. : 0.); + if (_selection.animating()) { + auto userpicRadius = qRound(kWideScale * (_st.imageRadius + (_st.imageSmallRadius - _st.imageRadius) * selectionLevel)); + auto userpicShift = kWideScale * _st.imageRadius - userpicRadius; + auto userpicLeft = x - (kWideScale - 1) * _st.imageRadius + userpicShift; + auto userpicTop = y - (kWideScale - 1) * _st.imageRadius + userpicShift; + auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2); + auto from = QRect(QPoint(0, 0), _wideCache.size()); + + p.setRenderHint(QPainter::SmoothPixmapTransform, true); + p.drawPixmapLeft(to, outerWidth, _wideCache, from); + p.setRenderHint(QPainter::SmoothPixmapTransform, false); + } else { + if (!_wideCache.isNull()) { + _wideCache = QPixmap(); + } + auto userpicRadius = _checked ? _st.imageSmallRadius : _st.imageRadius; + auto userpicShift = _st.imageRadius - userpicRadius; + auto userpicLeft = x + userpicShift; + auto userpicTop = y + userpicShift; + _paintRoundImage(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2); + } + + if (selectionLevel > 0) { + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + p.setOpacity(snap(selectionLevel, 0., 1.)); + p.setBrush(Qt::NoBrush); + auto pen = _st.selectFg->p; + pen.setWidth(_st.selectWidth); + p.setPen(pen); + p.drawEllipse(rtlrect(x, y, _st.imageRadius * 2, _st.imageRadius * 2, outerWidth)); + p.setOpacity(1.); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + } + + p.setRenderHint(QPainter::SmoothPixmapTransform, true); + for (auto &icon : _icons) { + auto fadeIn = icon.fadeIn.current(1.); + auto fadeOut = icon.fadeOut.current(1.); + auto iconRadius = qRound(kWideScale * (_st.checkSmallRadius + fadeOut * (_st.checkRadius - _st.checkSmallRadius))); + auto iconShift = kWideScale * _st.checkRadius - iconRadius; + auto iconLeft = x + 2 * _st.imageRadius + _st.selectWidth - 2 * _st.checkRadius - (kWideScale - 1) * _st.checkRadius + iconShift; + auto iconTop = y + 2 * _st.imageRadius + _st.selectWidth - 2 * _st.checkRadius - (kWideScale - 1) * _st.checkRadius + iconShift; + auto to = QRect(iconLeft, iconTop, iconRadius * 2, iconRadius * 2); + auto from = QRect(QPoint(0, 0), _wideCheckFullCache.size()); + auto opacity = fadeIn * fadeOut; + p.setOpacity(opacity); + if (fadeOut < 1.) { + p.drawPixmapLeft(to, outerWidth, icon.wideCheckCache, from); + } else { + auto divider = qRound((kWideScale - 2) * _st.checkRadius + fadeIn * 3 * _st.checkRadius); + p.drawPixmapLeft(QRect(iconLeft, iconTop, divider, iconRadius * 2), outerWidth, _wideCheckFullCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckFullCache.height())); + p.drawPixmapLeft(QRect(iconLeft + divider, iconTop, iconRadius * 2 - divider, iconRadius * 2), outerWidth, _wideCheckBgCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckBgCache.width() - divider * cIntRetinaFactor(), _wideCheckBgCache.height())); + } + } + p.setRenderHint(QPainter::SmoothPixmapTransform, false); + p.setOpacity(1.); +} + +float64 RoundImageCheckbox::checkedAnimationRatio() const { + return snap(_selection.current(_checked ? 1. : 0.), 0., 1.); +} + +void RoundImageCheckbox::setChecked(bool checked, SetStyle speed) { + if (_checked == checked) { + if (speed != SetStyle::Animated) { + if (!_icons.isEmpty()) { + _icons.back().fadeIn.finish(); + _icons.back().fadeOut.finish(); + } + _selection.finish(); + } + return; + } + _checked = checked; + if (_checked) { + _icons.push_back(Icon()); + _icons.back().fadeIn.start(_updateCallback, 0, 1, _st.selectDuration); + if (speed != SetStyle::Animated) { + _icons.back().fadeIn.finish(); + } + } else { + _icons.back().fadeOut.start(_updateCallback, 1, 0, _st.selectDuration); + if (speed == SetStyle::Animated) { + prepareWideCheckIconCache(&_icons.back()); + } else { + _icons.back().fadeOut.finish(); + } + } + if (speed == SetStyle::Animated) { + prepareWideCache(); + _selection.start(_updateCallback, _checked ? 0 : 1, _checked ? 1 : 0, _st.selectDuration, anim::bumpy<125, 100>); + } else { + _selection.finish(); + } +} + +void RoundImageCheckbox::removeFadeOutedIcons() { + while (!_icons.empty() && !_icons.front().fadeIn.animating() && !_icons.front().fadeOut.animating()) { + if (_icons.size() > 1 || !_checked) { + _icons.erase(_icons.begin()); + } else { + break; + } + } +} + +void RoundImageCheckbox::prepareWideCache() { + if (_wideCache.isNull()) { + auto size = _st.imageRadius * 2; + auto wideSize = size * kWideScale; + QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&cache); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, wideSize, wideSize, Qt::transparent); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + _paintRoundImage(p, (wideSize - size) / 2, (wideSize - size) / 2, wideSize, size); + } + _wideCache = App::pixmapFromImageInPlace(std_::move(cache)); + } +} + +void RoundImageCheckbox::prepareWideCheckIconCache(Icon *icon) { + auto cacheWidth = _wideCheckBgCache.width() / _wideCheckBgCache.devicePixelRatio(); + auto cacheHeight = _wideCheckBgCache.height() / _wideCheckBgCache.devicePixelRatio(); + auto wideCache = QImage(cacheWidth * cIntRetinaFactor(), cacheHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + wideCache.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&wideCache); + p.setCompositionMode(QPainter::CompositionMode_Source); + auto iconRadius = kWideScale * _st.checkRadius; + auto divider = qRound((kWideScale - 2) * _st.checkRadius + icon->fadeIn.current(1.) * (kWideScale - 1) * _st.checkRadius); + p.drawPixmapLeft(QRect(0, 0, divider, iconRadius * 2), cacheWidth, _wideCheckFullCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckFullCache.height())); + p.drawPixmapLeft(QRect(divider, 0, iconRadius * 2 - divider, iconRadius * 2), cacheWidth, _wideCheckBgCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckBgCache.width() - divider * cIntRetinaFactor(), _wideCheckBgCache.height())); + } + icon->wideCheckCache = App::pixmapFromImageInPlace(std_::move(wideCache)); + icon->wideCheckCache.setDevicePixelRatio(cRetinaFactor()); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/round_image_checkbox.h b/Telegram/SourceFiles/ui/effects/round_image_checkbox.h new file mode 100644 index 000000000..3b9d0f79a --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/round_image_checkbox.h @@ -0,0 +1,68 @@ +/* +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 RoundImageCheckbox { +public: + using PaintRoundImage = base::lambda_unique; + RoundImageCheckbox(const style::RoundImageCheckbox &st, base::lambda_wrap updateCallback, PaintRoundImage paintRoundImage); + + void paint(Painter &p, uint64 ms, int x, int y, int outerWidth); + float64 checkedAnimationRatio() const; + + bool checked() const { + return _checked; + } + enum class SetStyle { + Animated, + Fast, + }; + void setChecked(bool checked, SetStyle speed = SetStyle::Animated); + +private: + struct Icon { + FloatAnimation fadeIn; + FloatAnimation fadeOut; + QPixmap wideCheckCache; + }; + void removeFadeOutedIcons(); + void prepareWideCache(); + void prepareWideCheckIconCache(Icon *icon); + + const style::RoundImageCheckbox &_st; + base::lambda_wrap _updateCallback; + PaintRoundImage _paintRoundImage; + + bool _checked = false; + QPixmap _wideCache; + FloatAnimation _selection; + std_::vector_of_moveable _icons; + + // Those pixmaps are shared among all checkboxes that have the same style. + QPixmap _wideCheckBgCache, _wideCheckFullCache; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/flatinput.cpp b/Telegram/SourceFiles/ui/flatinput.cpp index cc63eb4fe..5f835f9a8 100644 --- a/Telegram/SourceFiles/ui/flatinput.cpp +++ b/Telegram/SourceFiles/ui/flatinput.cpp @@ -184,8 +184,8 @@ void FlatInput::paintEvent(QPaintEvent *e) { p.drawRoundedRect(QRectF(0, 0, width(), height()).marginsRemoved(QMarginsF(_st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2.)), st::buttonRadius - (_st.borderWidth / 2.), st::buttonRadius - (_st.borderWidth / 2.)); p.setRenderHint(QPainter::HighQualityAntialiasing, false); - if (_st.imgRect.pxWidth()) { - p.drawSprite(_st.imgPos, _st.imgRect); + if (!_st.icon.empty()) { + _st.icon.paint(p, 0, 0, width()); } bool phDraw = _phVisible; @@ -683,10 +683,10 @@ void InputArea::checkContentHeight() { } } -InputArea::InputAreaInner::InputAreaInner(InputArea *parent) : QTextEdit(parent) { +InputArea::Inner::Inner(InputArea *parent) : QTextEdit(parent) { } -bool InputArea::InputAreaInner::viewportEvent(QEvent *e) { +bool InputArea::Inner::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) { @@ -790,7 +790,7 @@ void InputArea::contextMenuEvent(QContextMenuEvent *e) { _inner.contextMenuEvent(e); } -void InputArea::InputAreaInner::focusInEvent(QFocusEvent *e) { +void InputArea::Inner::focusInEvent(QFocusEvent *e) { f()->focusInInner(); QTextEdit::focusInEvent(e); emit f()->focused(); @@ -807,7 +807,7 @@ void InputArea::focusInInner() { } } -void InputArea::InputAreaInner::focusOutEvent(QFocusEvent *e) { +void InputArea::Inner::focusOutEvent(QFocusEvent *e) { f()->focusOutInner(); QTextEdit::focusOutEvent(e); emit f()->blurred(); @@ -943,7 +943,7 @@ void InputArea::insertEmoji(EmojiPtr emoji, QTextCursor c) { c.insertText(objectReplacement, imageFormat); } -QVariant InputArea::InputAreaInner::loadResource(int type, const QUrl &name) { +QVariant InputArea::Inner::loadResource(int type, const QUrl &name) { QString imageName = name.toDisplayString(); if (imageName.startsWith(qstr("emoji://e."))) { if (EmojiPtr emoji = emojiFromUrl(imageName)) { @@ -1193,7 +1193,7 @@ void InputArea::updatePlaceholder() { } } -QMimeData *InputArea::InputAreaInner::createMimeDataFromSelection() const { +QMimeData *InputArea::Inner::createMimeDataFromSelection() const { QMimeData *result = new QMimeData(); QTextCursor c(textCursor()); int32 start = c.selectionStart(), end = c.selectionEnd(); @@ -1211,7 +1211,7 @@ void InputArea::setCtrlEnterSubmit(CtrlEnterSubmit ctrlEnterSubmit) { _ctrlEnterSubmit = ctrlEnterSubmit; } -void InputArea::InputAreaInner::keyPressEvent(QKeyEvent *e) { +void InputArea::Inner::keyPressEvent(QKeyEvent *e) { bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier); 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); @@ -1276,11 +1276,11 @@ void InputArea::InputAreaInner::keyPressEvent(QKeyEvent *e) { } } -void InputArea::InputAreaInner::paintEvent(QPaintEvent *e) { +void InputArea::Inner::paintEvent(QPaintEvent *e) { return QTextEdit::paintEvent(e); } -void InputArea::InputAreaInner::contextMenuEvent(QContextMenuEvent *e) { +void InputArea::Inner::contextMenuEvent(QContextMenuEvent *e) { if (QMenu *menu = createStandardContextMenu()) { (new PopupMenu(menu))->popup(e->globalPos()); } @@ -1338,7 +1338,9 @@ InputField::InputField(QWidget *parent, const style::InputField &st, const QStri _inner.setWordWrapMode(QTextOption::NoWrap); - setAttribute(Qt::WA_OpaquePaintEvent); + if (_st.textBg->c.alphaF() >= 1.) { + setAttribute(Qt::WA_OpaquePaintEvent); + } _inner.setFont(_st.font->f); _inner.setAlignment(_st.textAlign); @@ -1380,10 +1382,10 @@ void InputField::onTouchTimer() { _touchRightButton = true; } -InputField::InputFieldInner::InputFieldInner(InputField *parent) : QTextEdit(parent) { +InputField::Inner::Inner(InputField *parent) : QTextEdit(parent) { } -bool InputField::InputFieldInner::viewportEvent(QEvent *e) { +bool InputField::Inner::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) { @@ -1436,8 +1438,18 @@ void InputField::touchEvent(QTouchEvent *e) { void InputField::paintEvent(QPaintEvent *e) { Painter p(this); + auto ms = getms(); + if (_a_placeholderShift.animating()) { + _a_placeholderShift.step(ms); + } + if (_a_placeholderFg.animating()) { + _a_placeholderFg.step(ms); + } + QRect r(rect().intersected(e->rect())); - p.fillRect(r, st::white->b); + if (_st.textBg->c.alphaF() > 0.) { + p.fillRect(r, _st.textBg); + } if (_st.border) { p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b); } @@ -1446,9 +1458,6 @@ void InputField::paintEvent(QPaintEvent *e) { p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, a_borderFg.current()); p.setOpacity(1); } - if (_st.iconSprite.pxWidth()) { - p.drawSpriteLeft(_st.iconPosition, width(), _st.iconSprite); - } bool drawPlaceholder = _placeholderVisible; if (_a_placeholderShift.animating()) { @@ -1490,7 +1499,7 @@ void InputField::contextMenuEvent(QContextMenuEvent *e) { _inner.contextMenuEvent(e); } -void InputField::InputFieldInner::focusInEvent(QFocusEvent *e) { +void InputField::Inner::focusInEvent(QFocusEvent *e) { f()->focusInInner(); QTextEdit::focusInEvent(e); emit f()->focused(); @@ -1507,7 +1516,7 @@ void InputField::focusInInner() { } } -void InputField::InputFieldInner::focusOutEvent(QFocusEvent *e) { +void InputField::Inner::focusOutEvent(QFocusEvent *e) { f()->focusOutInner(); QTextEdit::focusOutEvent(e); emit f()->blurred(); @@ -1643,7 +1652,7 @@ void InputField::insertEmoji(EmojiPtr emoji, QTextCursor c) { c.insertText(objectReplacement, imageFormat); } -QVariant InputField::InputFieldInner::loadResource(int type, const QUrl &name) { +QVariant InputField::Inner::loadResource(int type, const QUrl &name) { QString imageName = name.toDisplayString(); if (imageName.startsWith(qstr("emoji://e."))) { if (EmojiPtr emoji = emojiFromUrl(imageName)) { @@ -1889,9 +1898,7 @@ void InputField::step_placeholderFg(float64 ms, bool timer) { void InputField::step_placeholderShift(float64 ms, bool timer) { float64 dt = ms / _st.duration; if (dt >= 1) { - _a_placeholderShift.stop(); - a_placeholderLeft.finish(); - a_placeholderOpacity.finish(); + finishPlaceholderAnimation(); } else { a_placeholderLeft.update(dt, anim::linear); a_placeholderOpacity.update(dt, anim::linear); @@ -1899,6 +1906,13 @@ void InputField::step_placeholderShift(float64 ms, bool timer) { if (timer) update(); } +void InputField::finishPlaceholderAnimation() { + _a_placeholderShift.stop(); + a_placeholderLeft.finish(); + a_placeholderOpacity.finish(); + update(); +} + void InputField::step_border(float64 ms, bool timer) { float64 dt = ms / _st.duration; if (dt >= 1) { @@ -1928,7 +1942,7 @@ void InputField::setPlaceholderHidden(bool forcePlaceholderHidden) { updatePlaceholder(); } -QMimeData *InputField::InputFieldInner::createMimeDataFromSelection() const { +QMimeData *InputField::Inner::createMimeDataFromSelection() const { QMimeData *result = new QMimeData(); QTextCursor c(textCursor()); int32 start = c.selectionStart(), end = c.selectionEnd(); @@ -1942,7 +1956,7 @@ void InputField::customUpDown(bool custom) { _customUpDown = custom; } -void InputField::InputFieldInner::keyPressEvent(QKeyEvent *e) { +void InputField::Inner::keyPressEvent(QKeyEvent *e) { bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier); 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), ctrlGood = true; @@ -1979,36 +1993,41 @@ void InputField::InputFieldInner::keyPressEvent(QKeyEvent *e) { } #endif // Q_OS_MAC } else { - QTextCursor tc(textCursor()); + auto oldCursorPosition = textCursor().position(); if (enter && ctrl) { e->setModifiers(e->modifiers() & ~Qt::ControlModifier); } QTextEdit::keyPressEvent(e); - if (tc == textCursor()) { + auto currentCursor = textCursor(); + if (textCursor().position() == oldCursorPosition) { 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); + oldCursorPosition = currentCursor.position(); + currentCursor.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); + oldCursorPosition = currentCursor.position(); + currentCursor.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); check = true; + } else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right || e->key() == Qt::Key_Backspace) { + e->ignore(); } if (check) { - if (tc == textCursor()) { + if (oldCursorPosition == currentCursor.position()) { e->ignore(); } else { - setTextCursor(tc); + setTextCursor(currentCursor); } } } } } -void InputField::InputFieldInner::paintEvent(QPaintEvent *e) { +void InputField::Inner::paintEvent(QPaintEvent *e) { return QTextEdit::paintEvent(e); } -void InputField::InputFieldInner::contextMenuEvent(QContextMenuEvent *e) { +void InputField::Inner::contextMenuEvent(QContextMenuEvent *e) { if (QMenu *menu = createStandardContextMenu()) { (new PopupMenu(menu))->popup(e->globalPos()); } @@ -2167,9 +2186,6 @@ void MaskedInputField::paintEvent(QPaintEvent *e) { p.fillRect(0, height() - _st.borderActive, width(), _st.borderActive, a_borderFg.current()); p.setOpacity(1); } - if (_st.iconSprite.pxWidth()) { - p.drawSpriteLeft(_st.iconPosition, width(), _st.iconSprite); - } p.setClipRect(r); paintPlaceholder(p); diff --git a/Telegram/SourceFiles/ui/flatinput.h b/Telegram/SourceFiles/ui/flatinput.h index 2535a019d..f730be8f8 100644 --- a/Telegram/SourceFiles/ui/flatinput.h +++ b/Telegram/SourceFiles/ui/flatinput.h @@ -262,10 +262,9 @@ private: bool heightAutoupdated(); void checkContentHeight(); - friend class InputAreaInner; - class InputAreaInner : public QTextEdit { + class Inner : public QTextEdit { public: - InputAreaInner(InputArea *parent); + Inner(InputArea *parent); QVariant loadResource(int type, const QUrl &name) override; @@ -286,6 +285,7 @@ private: friend class InputArea; }; + friend class Inner; void focusInInner(); void focusOutInner(); @@ -294,7 +294,7 @@ private: void startBorderAnimation(); - InputAreaInner _inner; + Inner _inner; QString _oldtext; @@ -343,6 +343,7 @@ public: } void updatePlaceholder(); void setPlaceholderHidden(bool forcePlaceholderHidden); + void finishPlaceholderAnimation(); void step_placeholderFg(float64 ms, bool timer); void step_placeholderShift(float64 ms, bool timer); @@ -431,10 +432,9 @@ private: int32 _maxLength; bool _forcePlaceholderHidden = false; - friend class InputFieldInner; - class InputFieldInner : public QTextEdit { + class Inner : public QTextEdit { public: - InputFieldInner(InputField *parent); + Inner(InputField *parent); QVariant loadResource(int type, const QUrl &name) override; @@ -455,6 +455,7 @@ private: friend class InputField; }; + friend class Inner; void focusInInner(); void focusOutInner(); @@ -463,7 +464,7 @@ private: void startBorderAnimation(); - InputFieldInner _inner; + Inner _inner; QString _oldtext; diff --git a/Telegram/SourceFiles/ui/style/style_core_icon.cpp b/Telegram/SourceFiles/ui/style/style_core_icon.cpp index acad1c618..2df173ae9 100644 --- a/Telegram/SourceFiles/ui/style/style_core_icon.cpp +++ b/Telegram/SourceFiles/ui/style/style_core_icon.cpp @@ -134,7 +134,7 @@ void MonoIcon::ensureLoaded() const { if (size > sizeTag.size() && !memcmp(data, sizeTag.data(), sizeTag.size())) { size -= sizeTag.size(); data += sizeTag.size(); - QByteArray baForStream(reinterpret_cast(data), size); + auto baForStream = QByteArray::fromRawData(reinterpret_cast(data), size); QBuffer buffer(&baForStream); buffer.open(QIODevice::ReadOnly); @@ -207,7 +207,7 @@ int Icon::width() const { int Icon::height() const { if (_height < 0) { _height = 0; - for_const (const auto &part, _parts) { + for_const (auto &part, _parts) { accumulate_max(_height, part.offset().x() + part.height()); } } diff --git a/Telegram/SourceFiles/ui/style/style_core_icon.h b/Telegram/SourceFiles/ui/style/style_core_icon.h index 822a491a9..c4e6b73a7 100644 --- a/Telegram/SourceFiles/ui/style/style_core_icon.h +++ b/Telegram/SourceFiles/ui/style/style_core_icon.h @@ -105,6 +105,9 @@ public: return std_::make_unique(ColoredCopy { *this, colors }); } + bool empty() const { + return _parts.empty(); + } void paint(QPainter &p, const QPoint &pos, int outerw) const; void paint(QPainter &p, int x, int y, int outerw) const { paint(p, QPoint(x, y), outerw); @@ -115,6 +118,9 @@ public: void fill(QPainter &p, const QRect &rect) const; int width() const; int height() const; + QSize size() const { + return QSize(width(), height()); + } private: struct ColoredCopy { diff --git a/Telegram/SourceFiles/ui/widgets/filled_slider.h b/Telegram/SourceFiles/ui/widgets/filled_slider.h index 7037b9544..87bb6e7ed 100644 --- a/Telegram/SourceFiles/ui/widgets/filled_slider.h +++ b/Telegram/SourceFiles/ui/widgets/filled_slider.h @@ -21,10 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "ui/widgets/continuous_slider.h" - -namespace style { -struct FilledSlider; -} // namespace style +#include "styles/style_widgets.h" namespace Ui { diff --git a/Telegram/SourceFiles/ui/widgets/media_slider.h b/Telegram/SourceFiles/ui/widgets/media_slider.h index 4bf1967d3..e01e29af5 100644 --- a/Telegram/SourceFiles/ui/widgets/media_slider.h +++ b/Telegram/SourceFiles/ui/widgets/media_slider.h @@ -21,10 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "ui/widgets/continuous_slider.h" - -namespace style { -struct MediaSlider; -} // namespace style +#include "styles/style_widgets.h" namespace Ui { diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.cpp b/Telegram/SourceFiles/ui/widgets/multi_select.cpp new file mode 100644 index 000000000..bef2de71b --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/multi_select.cpp @@ -0,0 +1,856 @@ +/* +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/widgets/multi_select.h" + +#include "styles/style_widgets.h" +#include "ui/buttons/icon_button.h" +#include "lang.h" + +namespace Ui { +namespace { + +constexpr int kWideScale = 3; + +} // namespace + +class MultiSelect::Inner::Item { +public: + Item(const style::MultiSelectItem &st, uint64 id, const QString &text, const style::color &color, PaintRoundImage paintRoundImage); + + uint64 id() const { + return _id; + } + int getWidth() const { + return _width; + } + QRect rect() const { + return QRect(_x, _y, _width, _st.height); + } + bool isOverDelete() const { + return _overDelete; + } + void setActive(bool active) { + _active = active; + } + void setPosition(int x, int y, int outerWidth, int maxVisiblePadding); + QRect paintArea(int outerWidth) const; + + void setUpdateCallback(base::lambda_wrap updateCallback) { + _updateCallback = std_::move(updateCallback); + } + void setText(const QString &text); + void paint(Painter &p, int outerWidth, uint64 ms); + + void mouseMoveEvent(QPoint point); + void leaveEvent(); + + void showAnimated() { + setVisibleAnimated(true); + } + void hideAnimated() { + setVisibleAnimated(false); + } + bool hideFinished() const { + return (_hiding && !_visibility.animating()); + } + + +private: + void setOver(bool over); + void paintOnce(Painter &p, int x, int y, int outerWidth, uint64 ms); + void paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity); + bool paintCached(Painter &p, int x, int y, int outerWidth); + void prepareCache(); + void setVisibleAnimated(bool visible); + + const style::MultiSelectItem &_st; + + uint64 _id; + struct SlideAnimation { + SlideAnimation(base::lambda_wrap updateCallback, int fromX, int toX, int y, float64 duration) + : fromX(fromX) + , toX(toX) + , y(y) { + x.start(std_::move(updateCallback), fromX, toX, duration); + } + IntAnimation x; + int fromX, toX; + int y; + }; + std_::vector_of_moveable _copies; + int _x = -1; + int _y = -1; + int _width = 0; + Text _text; + const style::color &_color; + bool _over = false; + QPixmap _cache; + FloatAnimation _visibility; + FloatAnimation _overOpacity; + bool _overDelete = false; + bool _active = false; + PaintRoundImage _paintRoundImage; + base::lambda_wrap _updateCallback; + bool _hiding = false; + +}; + +MultiSelect::Inner::Item::Item(const style::MultiSelectItem &st, uint64 id, const QString &text, const style::color &color, PaintRoundImage paintRoundImage) +: _st(st) +, _id(id) +, _color(color) +, _paintRoundImage(std_::move(paintRoundImage)) { + setText(text); +} + +void MultiSelect::Inner::Item::setText(const QString &text) { + _text.setText(_st.font, text, _textNameOptions); + _width = _st.height + _st.padding.left() + _text.maxWidth() + _st.padding.right(); + accumulate_min(_width, _st.maxWidth); +} + +void MultiSelect::Inner::Item::paint(Painter &p, int outerWidth, uint64 ms) { + if (!_cache.isNull() && !_visibility.animating(ms)) { + if (_hiding) { + return; + } else { + _cache = QPixmap(); + } + } + if (_copies.empty()) { + paintOnce(p, _x, _y, outerWidth, ms); + } else { + for (auto i = _copies.begin(), e = _copies.end(); i != e;) { + auto x = i->x.current(getms(), _x); + auto y = i->y; + auto animating = i->x.animating(); + if (animating || (y == _y)) { + paintOnce(p, x, y, outerWidth, ms); + } + if (animating) { + ++i; + } else { + i = _copies.erase(i); + e = _copies.end(); + } + } + } +} + +void MultiSelect::Inner::Item::paintOnce(Painter &p, int x, int y, int outerWidth, uint64 ms) { + if (!_cache.isNull()) { + paintCached(p, x, y, outerWidth); + return; + } + + auto radius = _st.height / 2; + auto inner = rtlrect(x + radius, y, _width - radius, _st.height, outerWidth); + + auto clipEnabled = p.hasClipping(); + auto clip = clipEnabled ? p.clipRegion() : QRegion(); + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.setClipRect(inner); + + p.setPen(Qt::NoPen); + p.setBrush(_active ? _st.textActiveBg : _st.textBg); + p.drawRoundedRect(rtlrect(x, y, _width, _st.height, outerWidth), radius, radius); + + if (clipEnabled) { + p.setClipRegion(clip); + } else { + p.setClipping(false); + } + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + auto overOpacity = _overOpacity.current(ms, _over ? 1. : 0.); + if (overOpacity < 1.) { + _paintRoundImage(p, x, y, outerWidth, _st.height); + } + if (overOpacity > 0.) { + paintDeleteButton(p, x, y, outerWidth, overOpacity); + } + + auto textLeft = _st.height + _st.padding.left(); + auto textWidth = _width - textLeft - _st.padding.right(); + p.setPen(_active ? _st.textActiveFg : _st.textFg); + _text.drawLeftElided(p, x + textLeft, y + _st.padding.top(), textWidth, outerWidth); +} + +void MultiSelect::Inner::Item::paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity) { + p.setOpacity(overOpacity); + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.setPen(Qt::NoPen); + p.setBrush(_color); + p.drawEllipse(rtlrect(x, y, _st.height, _st.height, outerWidth)); + + auto deleteScale = overOpacity + _st.minScale * (1. - overOpacity); + auto deleteSkip = deleteScale * _st.deleteLeft + (1. - deleteScale) * (_st.height / 2); + auto sqrt2 = sqrt(2.); + auto deleteLeft = rtlpoint(x + deleteSkip, 0, outerWidth).x() + 0.; + auto deleteTop = y + deleteSkip + 0.; + auto deleteWidth = _st.height - 2 * deleteSkip; + auto deleteHeight = _st.height - 2 * deleteSkip; + auto deleteStroke = _st.deleteStroke / sqrt2; + QPointF pathDelete[] = { + { deleteLeft, deleteTop + deleteStroke }, + { deleteLeft + deleteStroke, deleteTop }, + { deleteLeft + (deleteWidth / 2.), deleteTop + (deleteHeight / 2.) - deleteStroke }, + { deleteLeft + deleteWidth - deleteStroke, deleteTop }, + { deleteLeft + deleteWidth, deleteTop + deleteStroke }, + { deleteLeft + (deleteWidth / 2.) + deleteStroke, deleteTop + (deleteHeight / 2.) }, + { deleteLeft + deleteWidth, deleteTop + deleteHeight - deleteStroke }, + { deleteLeft + deleteWidth - deleteStroke, deleteTop + deleteHeight }, + { deleteLeft + (deleteWidth / 2.), deleteTop + (deleteHeight / 2.) + deleteStroke }, + { deleteLeft + deleteStroke, deleteTop + deleteHeight }, + { deleteLeft, deleteTop + deleteHeight - deleteStroke }, + { deleteLeft + (deleteWidth / 2.) - deleteStroke, deleteTop + (deleteHeight / 2.) }, + }; + if (overOpacity < 1.) { + auto alpha = -(overOpacity - 1.) * M_PI_2; + auto cosalpha = cos(alpha); + auto sinalpha = sin(alpha); + auto shiftx = deleteLeft + (deleteWidth / 2.); + auto shifty = deleteTop + (deleteHeight / 2.); + for (auto &point : pathDelete) { + auto x = point.x() - shiftx; + auto y = point.y() - shifty; + point.setX(shiftx + x * cosalpha - y * sinalpha); + point.setY(shifty + y * cosalpha + x * sinalpha); + } + } + QPainterPath path; + path.moveTo(pathDelete[0]); + for (int i = 1; i != base::array_size(pathDelete); ++i) { + path.lineTo(pathDelete[i]); + } + p.fillPath(path, _st.deleteFg); + + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + p.setOpacity(1.); +} + +bool MultiSelect::Inner::Item::paintCached(Painter &p, int x, int y, int outerWidth) { + auto opacity = _visibility.current(_hiding ? 0. : 1.); + auto scale = opacity + _st.minScale * (1. - opacity); + auto height = opacity * _cache.height() / _cache.devicePixelRatio(); + auto width = opacity * _cache.width() / _cache.devicePixelRatio(); + + p.setOpacity(opacity); + p.setRenderHint(QPainter::SmoothPixmapTransform, true); + p.drawPixmap(rtlrect(x + (_width - width) / 2., y + (_st.height - height) / 2., width, height, outerWidth), _cache); + p.setRenderHint(QPainter::SmoothPixmapTransform, false); + p.setOpacity(1.); + return true; +} + +void MultiSelect::Inner::Item::mouseMoveEvent(QPoint point) { + if (!_cache.isNull()) return; + _overDelete = QRect(0, 0, _st.height, _st.height).contains(point); + setOver(true); +} + +void MultiSelect::Inner::Item::leaveEvent() { + _overDelete = false; + setOver(false); +} + +void MultiSelect::Inner::Item::setPosition(int x, int y, int outerWidth, int maxVisiblePadding) { + if (_x >= 0 && _y >= 0 && (_x != x || _y != y)) { + // Make an animation if it is not the first setPosition(). + auto found = false; + auto leftHidden = -_width - maxVisiblePadding; + auto rightHidden = outerWidth + maxVisiblePadding; + for (auto i = _copies.begin(), e = _copies.end(); i != e;) { + if (i->x.animating()) { + if (i->y == y) { + i->x.start(_updateCallback, i->toX, x, _st.duration); + found = true; + } else { + i->x.start(_updateCallback, i->fromX, (i->toX > i->fromX) ? rightHidden : leftHidden, _st.duration); + } + ++i; + } else { + i = _copies.erase(i); + e = _copies.end(); + } + } + if (_copies.empty()) { + if (_y == y) { + auto copy = SlideAnimation(_updateCallback, _x, x, _y, _st.duration); + _copies.push_back(std_::move(copy)); + } else { + auto copyHiding = SlideAnimation(_updateCallback, _x, (y > _y) ? rightHidden : leftHidden, _y, _st.duration); + _copies.push_back(std_::move(copyHiding)); + auto copyShowing = SlideAnimation(_updateCallback, (y > _y) ? leftHidden : rightHidden, x, y, _st.duration); + _copies.push_back(std_::move(copyShowing)); + } + } else if (!found) { + auto copy = SlideAnimation(_updateCallback, (y > _y) ? leftHidden : rightHidden, x, y, _st.duration); + _copies.push_back(std_::move(copy)); + } + } + _x = x; + _y = y; +} + +QRect MultiSelect::Inner::Item::paintArea(int outerWidth) const { + if (_copies.empty()) { + return rect(); + } + auto yMin = 0, yMax = 0; + for_const (auto ©, _copies) { + accumulate_max(yMax, copy.y); + if (yMin) { + accumulate_min(yMin, copy.y); + } else { + yMin = copy.y; + } + } + return QRect(0, yMin, outerWidth, yMax - yMin + _st.height); +} + +void MultiSelect::Inner::Item::prepareCache() { + if (!_cache.isNull()) return; + + t_assert(!_visibility.animating()); + auto cacheWidth = _width * kWideScale * cIntRetinaFactor(); + auto cacheHeight = _st.height * kWideScale * cIntRetinaFactor(); + auto data = QImage(cacheWidth, cacheHeight, QImage::Format_ARGB32_Premultiplied); + data.fill(Qt::transparent); + data.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&data); + paintOnce(p, _width * (kWideScale - 1) / 2, _st.height * (kWideScale - 1) / 2, cacheWidth, getms()); + } + _cache = App::pixmapFromImageInPlace(std_::move(data)); +} + +void MultiSelect::Inner::Item::setVisibleAnimated(bool visible) { + _hiding = !visible; + prepareCache(); + auto from = visible ? 0. : 1.; + auto to = visible ? 1. : 0.; + auto transition = visible ? anim::bumpy<1125, 1000> : anim::linear; + _visibility.start(_updateCallback, from, to, _st.duration, transition); +} + +void MultiSelect::Inner::Item::setOver(bool over) { + if (over != _over) { + _over = over; + _overOpacity.start(_updateCallback, _over ? 0. : 1., _over ? 1. : 0., _st.duration); + } +} + +MultiSelect::MultiSelect(QWidget *parent, const style::MultiSelect &st, const QString &placeholder) : TWidget(parent) +, _st(st) +, _scroll(this, _st.scroll) +, _inner(this, st, placeholder, [this](int activeTop, int activeBottom) { scrollTo(activeTop, activeBottom); }) { + _scroll->setOwnedWidget(_inner); + _scroll->installEventFilter(this); + _inner->setResizedCallback([this](int innerHeightDelta) { + auto newHeight = resizeGetHeight(width()); + if (innerHeightDelta > 0) { + _scroll->scrollToY(_scroll->scrollTop() + innerHeightDelta); + } + if (newHeight != height()) { + resize(width(), newHeight); + if (_resizedCallback) { + _resizedCallback(); + } + } + }); + _inner->setQueryChangedCallback([this](const QString &query) { + _scroll->scrollToY(_scroll->scrollTopMax()); + if (_queryChangedCallback) { + _queryChangedCallback(query); + } + }); + + setAttribute(Qt::WA_OpaquePaintEvent); +} + +bool MultiSelect::eventFilter(QObject *o, QEvent *e) { + if (o == _scroll && e->type() == QEvent::KeyPress) { + e->ignore(); + return true; + } + return false; +} + +void MultiSelect::scrollTo(int activeTop, int activeBottom) { + auto scrollTop = _scroll->scrollTop(); + auto scrollHeight = _scroll->height(); + auto scrollBottom = scrollTop + scrollHeight; + if (scrollTop > activeTop) { + _scroll->scrollToY(activeTop); + } else if (scrollBottom < activeBottom) { + _scroll->scrollToY(activeBottom - scrollHeight); + } +} + +void MultiSelect::setQueryChangedCallback(base::lambda_unique callback) { + _queryChangedCallback = std_::move(callback); +} + +void MultiSelect::setSubmittedCallback(base::lambda_unique callback) { + _inner->setSubmittedCallback(std_::move(callback)); +} + +void MultiSelect::setResizedCallback(base::lambda_unique callback) { + _resizedCallback = std_::move(callback); +} + +void MultiSelect::setInnerFocus() { + if (_inner->setInnerFocus()) { + _scroll->scrollToY(_scroll->scrollTopMax()); + } +} + +void MultiSelect::clearQuery() { + _inner->clearQuery(); +} + +QString MultiSelect::getQuery() const { + return _inner->getQuery(); +} + +void MultiSelect::addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage, AddItemWay way) { + _inner->addItem(std_::make_unique(_st.item, itemId, text, color, std_::move(paintRoundImage)), way); +} + +void MultiSelect::setItemRemovedCallback(base::lambda_unique callback) { + _inner->setItemRemovedCallback(std_::move(callback)); +} + +void MultiSelect::removeItem(uint64 itemId) { + _inner->removeItem(itemId); +} + +int MultiSelect::resizeGetHeight(int newWidth) { + if (newWidth != _inner->width()) { + _inner->resizeToWidth(newWidth); + } + auto newHeight = qMin(_inner->height(), _st.maxHeight); + _scroll->setGeometryToLeft(0, 0, newWidth, newHeight); + return newHeight; +} + +MultiSelect::Inner::Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder, ScrollCallback callback) : ScrolledWidget(parent) +, _st(st) +, _scrollCallback(std_::move(callback)) +, _field(this, _st.field, placeholder) +, _cancel(this, _st.fieldCancel) { + _field->customUpDown(true); + connect(_field, SIGNAL(focused()), this, SLOT(onFieldFocused())); + connect(_field, SIGNAL(changed()), this, SLOT(onQueryChanged())); + connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSubmitted(bool))); + _cancel->hide(); + _cancel->setClickedCallback([this] { + clearQuery(); + _field->setFocus(); + }); + setMouseTracking(true); +} + +void MultiSelect::Inner::onQueryChanged() { + auto query = getQuery(); + _cancel->setVisible(!query.isEmpty()); + updateFieldGeometry(); + if (_queryChangedCallback) { + _queryChangedCallback(query); + } +} + +QString MultiSelect::Inner::getQuery() const { + return _field->getLastText().trimmed(); +} + +bool MultiSelect::Inner::setInnerFocus() { + if (_active >= 0) { + setFocus(); + } else if (!_field->hasFocus()) { + _field->setFocus(); + return true; + } + return false; +} + +void MultiSelect::Inner::clearQuery() { + _field->setText(QString()); +} + +void MultiSelect::Inner::setQueryChangedCallback(base::lambda_unique callback) { + _queryChangedCallback = std_::move(callback); +} + +void MultiSelect::Inner::setSubmittedCallback(base::lambda_unique callback) { + _submittedCallback = std_::move(callback); +} + +void MultiSelect::Inner::updateFieldGeometry() { + auto fieldFinalWidth = _fieldWidth; + if (!_cancel->isHidden()) { + fieldFinalWidth -= _st.fieldCancelSkip; + } + _field->resizeToWidth(fieldFinalWidth); + _field->moveToLeft(_st.padding.left() + _fieldLeft, _st.padding.top() + _fieldTop); +} + +void MultiSelect::Inner::updateHasAnyItems(bool hasAnyItems) { + _field->setPlaceholderHidden(hasAnyItems); + updateCursor(); + _iconOpacity.start([this] { + rtlupdate(_st.padding.left(), _st.padding.top(), _st.fieldIcon.width(), _st.fieldIcon.height()); + }, hasAnyItems ? 1. : 0., hasAnyItems ? 0. : 1., _st.item.duration); +} + +void MultiSelect::Inner::updateCursor() { + setCursor(_items.empty() ? style::cur_text : (_overDelete ? style::cur_pointer : style::cur_default)); +} + +void MultiSelect::Inner::setActiveItem(int active, ChangeActiveWay skipSetFocus) { + if (_active == active) return; + + if (_active >= 0) { + t_assert(_active < _items.size()); + _items[_active]->setActive(false); + } + _active = active; + if (_active >= 0) { + t_assert(_active < _items.size()); + _items[_active]->setActive(true); + } + if (skipSetFocus != ChangeActiveWay::SkipSetFocus) { + setInnerFocus(); + } + if (_scrollCallback) { + auto rect = (_active >= 0) ? _items[_active]->rect() : _field->geometry().translated(-_st.padding.left(), -_st.padding.top()); + _scrollCallback(rect.y(), rect.y() + rect.height() + _st.padding.top() + _st.padding.bottom()); + } + update(); +} + +void MultiSelect::Inner::setActiveItemPrevious() { + if (_active > 0) { + setActiveItem(_active - 1); + } else if (_active < 0 && !_items.empty()) { + setActiveItem(_items.size() - 1); + } +} + +void MultiSelect::Inner::setActiveItemNext() { + if (_active >= 0 && _active + 1 < _items.size()) { + setActiveItem(_active + 1); + } else { + setActiveItem(-1); + } +} + +int MultiSelect::Inner::resizeGetHeight(int newWidth) { + computeItemsGeometry(newWidth); + updateFieldGeometry(); + + auto cancelLeft = _fieldLeft + _fieldWidth + _st.padding.right() - _cancel->width(); + auto cancelTop = _fieldTop - _st.padding.top(); + _cancel->moveToLeft(_st.padding.left() + cancelLeft, _st.padding.top() + cancelTop); + + return _field->y() + _field->height() + _st.padding.bottom(); +} + +void MultiSelect::Inner::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto ms = getms(); + _height.step(ms); + _iconOpacity.step(ms); + + auto paintRect = e->rect(); + p.fillRect(paintRect, st::windowBg); + + auto offset = QPoint(rtl() ? _st.padding.right() : _st.padding.left(), _st.padding.top()); + p.translate(offset); + paintRect.translate(-offset); + + auto outerWidth = width() - _st.padding.left() - _st.padding.right(); + auto iconOpacity = _iconOpacity.current(_items.empty() ? 1. : 0.); + if (iconOpacity > 0.) { + p.setOpacity(iconOpacity); + _st.fieldIcon.paint(p, 0, 0, outerWidth); + p.setOpacity(1.); + } + + auto checkRect = myrtlrect(paintRect); + auto paintMargins = itemPaintMargins(); + for (auto i = _removingItems.begin(), e = _removingItems.end(); i != e;) { + auto item = *i; + auto itemRect = item->paintArea(outerWidth); + itemRect = itemRect.marginsAdded(paintMargins); + if (checkRect.intersects(itemRect)) { + item->paint(p, outerWidth, ms); + } + if (item->hideFinished()) { + i = _removingItems.erase(i); + e = _removingItems.end(); + } else { + ++i; + } + } + for_const (auto item, _items) { + auto itemRect = item->paintArea(outerWidth); + itemRect = itemRect.marginsAdded(paintMargins); + if (checkRect.y() + checkRect.height() <= itemRect.y()) { + break; + } else if (checkRect.intersects(itemRect)) { + item->paint(p, outerWidth, ms); + } + } +} + +QMargins MultiSelect::Inner::itemPaintMargins() const { + return { + qMax(_st.itemSkip, _st.padding.left()), + _st.itemSkip, + qMax(_st.itemSkip, _st.padding.right()), + _st.itemSkip, + }; +} + +void MultiSelect::Inner::leaveEvent(QEvent *e) { + clearSelection(); +} + +void MultiSelect::Inner::mouseMoveEvent(QMouseEvent *e) { + updateSelection(e->pos()); +} + +void MultiSelect::Inner::keyPressEvent(QKeyEvent *e) { + if (_active >= 0) { + t_assert(_active < _items.size()); + if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) { + auto itemId = _items[_active]->id(); + setActiveItemNext(); + removeItem(itemId); + } else if (e->key() == Qt::Key_Left) { + setActiveItemPrevious(); + } else if (e->key() == Qt::Key_Right) { + setActiveItemNext(); + } else if (e->key() == Qt::Key_Escape) { + setActiveItem(-1); + } else { + e->ignore(); + } + } else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Backspace) { + setActiveItemPrevious(); + } else { + e->ignore(); + } +} + +void MultiSelect::Inner::onFieldFocused() { + setActiveItem(-1, ChangeActiveWay::SkipSetFocus); +} + +void MultiSelect::Inner::updateSelection(QPoint mousePosition) { + auto point = myrtlpoint(mousePosition) - QPoint(_st.padding.left(), _st.padding.right()); + auto selected = -1; + for (auto i = 0, size = _items.size(); i != size; ++i) { + auto itemRect = _items[i]->rect(); + if (itemRect.y() > point.y()) { + break; + } else if (itemRect.contains(point)) { + point -= itemRect.topLeft(); + selected = i; + break; + } + } + if (_selected != selected) { + if (_selected >= 0) { + t_assert(_selected < _items.size()); + _items[_selected]->leaveEvent(); + } + _selected = selected; + update(); + } + auto overDelete = false; + if (_selected >= 0) { + _items[_selected]->mouseMoveEvent(point); + overDelete = _items[_selected]->isOverDelete(); + } + if (_overDelete != overDelete) { + _overDelete = overDelete; + updateCursor(); + } +} + +void MultiSelect::Inner::mousePressEvent(QMouseEvent *e) { + if (_overDelete) { + t_assert(_selected >= 0); + t_assert(_selected < _items.size()); + removeItem(_items[_selected]->id()); + } else if (_selected >= 0) { + setActiveItem(_selected); + } else { + setInnerFocus(); + } +} + +void MultiSelect::Inner::addItem(std_::unique_ptr item, AddItemWay way) { + auto wasEmpty = _items.empty(); + item->setUpdateCallback([this, item = item.get()] { + auto itemRect = item->paintArea(width() - _st.padding.left() - _st.padding.top()); + itemRect = itemRect.translated(_st.padding.left(), _st.padding.top()); + itemRect = itemRect.marginsAdded(itemPaintMargins()); + rtlupdate(itemRect); + }); + _items.push_back(item.release()); + updateItemsGeometry(); + if (wasEmpty) { + updateHasAnyItems(true); + } + if (way != AddItemWay::SkipAnimation) { + _items.back()->showAnimated(); + } else { + _field->finishPlaceholderAnimation(); + finishHeightAnimation(); + } +} + +void MultiSelect::Inner::computeItemsGeometry(int newWidth) { + newWidth -= _st.padding.left() + _st.padding.right(); + + auto itemLeft = 0; + auto itemTop = 0; + auto widthLeft = newWidth; + auto maxVisiblePadding = qMax(_st.padding.left(), _st.padding.right()); + for_const (auto item, _items) { + auto itemWidth = item->getWidth(); + t_assert(itemWidth <= newWidth); + if (itemWidth > widthLeft) { + itemLeft = 0; + itemTop += _st.item.height + _st.itemSkip; + widthLeft = newWidth; + } + item->setPosition(itemLeft, itemTop, newWidth, maxVisiblePadding); + itemLeft += itemWidth + _st.itemSkip; + widthLeft -= itemWidth + _st.itemSkip; + } + + auto fieldMinWidth = _st.fieldMinWidth + _st.fieldCancelSkip; + t_assert(fieldMinWidth <= newWidth); + if (fieldMinWidth > widthLeft) { + _fieldLeft = 0; + _fieldTop = itemTop + _st.item.height + _st.itemSkip; + } else { + _fieldLeft = itemLeft + (_items.empty() ? _st.fieldIconSkip : 0); + _fieldTop = itemTop; + } + _fieldWidth = newWidth - _fieldLeft; +} + +void MultiSelect::Inner::updateItemsGeometry() { + computeItemsGeometry(width()); + updateFieldGeometry(); + auto newHeight = resizeGetHeight(width()); + if (newHeight == _newHeight) return; + + _newHeight = newHeight; + _height.start([this] { updateHeightStep(); }, height(), _newHeight, _st.item.duration); +} + +void MultiSelect::Inner::updateHeightStep() { + auto newHeight = _height.current(_newHeight); + if (auto heightDelta = newHeight - height()) { + resize(width(), newHeight); + if (_resizedCallback) { + _resizedCallback(heightDelta); + } + update(); + } +} + +void MultiSelect::Inner::finishHeightAnimation() { + _height.finish(); + updateHeightStep(); +} + +void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) { + for (int i = 0, count = _items.size(); i != count; ++i) { + auto item = _items[i]; + if (item->id() == itemId) { + item->setText(text); + updateItemsGeometry(); + return; + } + } +} + +void MultiSelect::Inner::setItemRemovedCallback(base::lambda_unique callback) { + _itemRemovedCallback = std_::move(callback); +} + +void MultiSelect::Inner::setResizedCallback(base::lambda_unique callback) { + _resizedCallback = std_::move(callback); +} + +void MultiSelect::Inner::removeItem(uint64 itemId) { + for (int i = 0, count = _items.size(); i != count; ++i) { + auto item = _items[i]; + if (item->id() == itemId) { + clearSelection(); + _items.removeAt(i); + if (_active == i) { + _active = -1; + } else if (_active > i) { + --_active; + } + _removingItems.insert(item); + item->hideAnimated(); + + updateItemsGeometry(); + if (_items.empty()) { + updateHasAnyItems(false); + } + auto point = QCursor::pos(); + if (auto parent = parentWidget()) { + if (parent->rect().contains(parent->mapFromGlobal(point))) { + updateSelection(mapFromGlobal(point)); + } + } + break; + } + } + if (_itemRemovedCallback) { + _itemRemovedCallback(itemId); + } + setInnerFocus(); +} + +MultiSelect::Inner::~Inner() { + for (auto item : base::take(_items)) { + delete item; + } + for (auto item : base::take(_removingItems)) { + delete item; + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.h b/Telegram/SourceFiles/ui/widgets/multi_select.h new file mode 100644 index 000000000..01854db33 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/multi_select.h @@ -0,0 +1,169 @@ +/* +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" + +class InputField; + +namespace Ui { + +class IconButton; + +class MultiSelect : public TWidget { +public: + MultiSelect(QWidget *parent, const style::MultiSelect &st, const QString &placeholder = QString()); + + QString getQuery() const; + void setInnerFocus(); + void clearQuery(); + + void setQueryChangedCallback(base::lambda_unique callback); + void setSubmittedCallback(base::lambda_unique callback); + void setResizedCallback(base::lambda_unique callback); + + enum class AddItemWay { + Default, + SkipAnimation, + }; + using PaintRoundImage = base::lambda_unique; + void addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage, AddItemWay way = AddItemWay::Default); + void setItemText(uint64 itemId, const QString &text); + + void setItemRemovedCallback(base::lambda_unique callback); + void removeItem(uint64 itemId); + +protected: + int resizeGetHeight(int newWidth) override; + bool eventFilter(QObject *o, QEvent *e) override; + +private: + void scrollTo(int activeTop, int activeBottom); + + const style::MultiSelect &_st; + + ChildWidget _scroll; + + class Inner; + ChildWidget _inner; + + base::lambda_unique _resizedCallback; + base::lambda_unique _queryChangedCallback; + +}; + +// This class is hold in header because it requires Qt preprocessing. +class MultiSelect::Inner : public ScrolledWidget { + Q_OBJECT + +public: + using ScrollCallback = base::lambda_unique; + Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder, ScrollCallback callback); + + QString getQuery() const; + bool setInnerFocus(); + void clearQuery(); + + void setQueryChangedCallback(base::lambda_unique callback); + void setSubmittedCallback(base::lambda_unique callback); + + class Item; + void addItem(std_::unique_ptr item, AddItemWay way); + void setItemText(uint64 itemId, const QString &text); + + void setItemRemovedCallback(base::lambda_unique callback); + void removeItem(uint64 itemId); + + void setResizedCallback(base::lambda_unique callback); + + ~Inner(); + +protected: + int resizeGetHeight(int newWidth) override; + + void paintEvent(QPaintEvent *e) override; + void leaveEvent(QEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + +private slots: + void onQueryChanged(); + void onSubmitted(bool ctrlShiftEnter) { + if (_submittedCallback) { + _submittedCallback(ctrlShiftEnter); + } + } + void onFieldFocused(); + +private: + void computeItemsGeometry(int newWidth); + void updateItemsGeometry(); + void updateFieldGeometry(); + void updateHasAnyItems(bool hasAnyItems); + void updateSelection(QPoint mousePosition); + void clearSelection() { + updateSelection(QPoint(-1, -1)); + } + void updateCursor(); + void updateHeightStep(); + void finishHeightAnimation(); + enum class ChangeActiveWay { + Default, + SkipSetFocus, + }; + void setActiveItem(int active, ChangeActiveWay skipSetFocus = ChangeActiveWay::Default); + void setActiveItemPrevious(); + void setActiveItemNext(); + + QMargins itemPaintMargins() const; + + const style::MultiSelect &_st; + FloatAnimation _iconOpacity; + + ScrollCallback _scrollCallback; + + using Items = QList; + Items _items; + using RemovingItems = OrderedSet; + RemovingItems _removingItems; + + int _selected = -1; + int _active = -1; + bool _overDelete = false; + + int _fieldLeft = 0; + int _fieldTop = 0; + int _fieldWidth = 0; + ChildWidget _field; + ChildWidget _cancel; + + int _newHeight = 0; + IntAnimation _height; + + base::lambda_unique _queryChangedCallback; + base::lambda_unique _submittedCallback; + base::lambda_unique _itemRemovedCallback; + base::lambda_unique _resizedCallback; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h b/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h index 380e65209..4c8ebc0ec 100644 --- a/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h +++ b/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h @@ -30,7 +30,7 @@ public: using UpdateCallback = base::lambda_unique; WidgetSlideWrap(QWidget *parent, Widget *entity , style::margins entityPadding - , UpdateCallback &&updateCallback + , UpdateCallback updateCallback , int duration = st::widgetSlideDuration) : TWidget(parent) , _entity(entity) , _padding(entityPadding) diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 3128f0521..d4ab8f7a1 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -54,6 +54,51 @@ FilledSlider { duration: int; } +RoundImageCheckbox { + imageRadius: pixels; + imageSmallRadius: pixels; + selectWidth: pixels; + selectFg: color; + selectDuration: int; + checkBorder: color; + checkBg: color; + checkRadius: pixels; + checkSmallRadius: pixels; + checkIcon: icon; +} + +MultiSelectItem { + padding: margins; + maxWidth: pixels; + height: pixels; + font: font; + textBg: color; + textFg: color; + textActiveBg: color; + textActiveFg: color; + deleteFg: color; + deleteLeft: pixels; + deleteStroke: pixels; + duration: int; + minScale: double; +} + +MultiSelect { + padding: margins; + maxHeight: pixels; + scroll: flatScroll; + + item: MultiSelectItem; + itemSkip: pixels; + + field: InputField; + fieldMinWidth: pixels; + fieldIcon: icon; + fieldIconSkip: pixels; + fieldCancel: IconButton; + fieldCancelSkip: pixels; +} + widgetSlideDuration: 200; discreteSliderHeight: 39px; diff --git a/Telegram/SourceFiles/window/chat_background.cpp b/Telegram/SourceFiles/window/chat_background.cpp index 294e53471..315ac129f 100644 --- a/Telegram/SourceFiles/window/chat_background.cpp +++ b/Telegram/SourceFiles/window/chat_background.cpp @@ -41,10 +41,9 @@ void ChatBackground::initIfEmpty() { } } -void ChatBackground::init(int32 id, QPixmap &&image, QPixmap &&dog) { +void ChatBackground::init(int32 id, QPixmap &&image) { _id = id; _image = std_::move(image); - _dog = std_::move(dog); notify(ChatBackgroundUpdate(ChatBackgroundUpdate::Type::New, _tile)); } @@ -52,7 +51,6 @@ void ChatBackground::init(int32 id, QPixmap &&image, QPixmap &&dog) { void ChatBackground::reset() { _id = 0; _image = QPixmap(); - _dog = QPixmap(); _tile = false; notify(ChatBackgroundUpdate(ChatBackgroundUpdate::Type::New, _tile)); @@ -66,10 +64,6 @@ const QPixmap &ChatBackground::image() const { return _image; } -const QPixmap &ChatBackground::dog() const { - return _dog; -} - bool ChatBackground::tile() const { return _tile; } diff --git a/Telegram/SourceFiles/window/chat_background.h b/Telegram/SourceFiles/window/chat_background.h index 67a2bf72d..6840dbc2f 100644 --- a/Telegram/SourceFiles/window/chat_background.h +++ b/Telegram/SourceFiles/window/chat_background.h @@ -39,19 +39,17 @@ class ChatBackground : public base::Observable { public: bool empty() const; void initIfEmpty(); - void init(int32 id, QPixmap &&image, QPixmap &&dog); + void init(int32 id, QPixmap &&image); void reset(); int32 id() const; const QPixmap &image() const; - const QPixmap &dog() const; bool tile() const; void setTile(bool tile); private: int32 _id = 0; QPixmap _image; - QPixmap _dog; bool _tile = false; }; diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 721d756c9..8722c4d3b 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -21,6 +21,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org using "basic.style"; +titleIconPosition: point(9px, 9px); +titleIcon: icon { + { "title_icon_bg", #49708f }, + { "title_icon", #ffffff, point(4px, 4px) } +}; +titleCounterPosition: point(17px, 17px); + notifyBg: white; notifyBorder: #f1f1f1; notifyBorderWidth: 1px; diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 5a49cd29b..f6609c10f 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -183,6 +183,8 @@ '<(src_loc)/boxes/languagebox.h', '<(src_loc)/boxes/localstoragebox.cpp', '<(src_loc)/boxes/localstoragebox.h', + '<(src_loc)/boxes/members_box.cpp', + '<(src_loc)/boxes/members_box.h', '<(src_loc)/boxes/notifications_box.cpp', '<(src_loc)/boxes/notifications_box.h', '<(src_loc)/boxes/passcodebox.cpp', @@ -199,6 +201,8 @@ '<(src_loc)/boxes/sharebox.h', '<(src_loc)/boxes/stickersetbox.cpp', '<(src_loc)/boxes/stickersetbox.h', + '<(src_loc)/boxes/stickers_box.cpp', + '<(src_loc)/boxes/stickers_box.h', '<(src_loc)/boxes/usernamebox.cpp', '<(src_loc)/boxes/usernamebox.h', '<(src_loc)/core/basic_types.h', @@ -455,6 +459,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/round_image_checkbox.cpp', + '<(src_loc)/ui/effects/round_image_checkbox.h', '<(src_loc)/ui/style/style_core.cpp', '<(src_loc)/ui/style/style_core.h', '<(src_loc)/ui/style/style_core_color.cpp', @@ -487,6 +493,8 @@ '<(src_loc)/ui/widgets/label_simple.h', '<(src_loc)/ui/widgets/media_slider.cpp', '<(src_loc)/ui/widgets/media_slider.h', + '<(src_loc)/ui/widgets/multi_select.cpp', + '<(src_loc)/ui/widgets/multi_select.h', '<(src_loc)/ui/widgets/shadow.cpp', '<(src_loc)/ui/widgets/shadow.h', '<(src_loc)/ui/widgets/widget_slide_wrap.h',