Merge branch 'dev'
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 66 KiB |
|
@ -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.;
|
||||
|
|
|
@ -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 {
|
||||
|
|
BIN
Telegram/Resources/icons/add_contact_phone.png
Normal file
After Width: | Height: | Size: 380 B |
BIN
Telegram/Resources/icons/add_contact_phone@2x.png
Normal file
After Width: | Height: | Size: 708 B |
BIN
Telegram/Resources/icons/add_contact_user.png
Normal file
After Width: | Height: | Size: 316 B |
BIN
Telegram/Resources/icons/add_contact_user@2x.png
Normal file
After Width: | Height: | Size: 579 B |
BIN
Telegram/Resources/icons/box_button_close.png
Normal file
After Width: | Height: | Size: 151 B |
BIN
Telegram/Resources/icons/box_button_close@2x.png
Normal file
After Width: | Height: | Size: 283 B |
BIN
Telegram/Resources/icons/box_search_cancel.png
Normal file
After Width: | Height: | Size: 187 B |
BIN
Telegram/Resources/icons/box_search_cancel@2x.png
Normal file
After Width: | Height: | Size: 296 B |
BIN
Telegram/Resources/icons/box_search_icon.png
Normal file
After Width: | Height: | Size: 350 B |
BIN
Telegram/Resources/icons/box_search_icon@2x.png
Normal file
After Width: | Height: | Size: 707 B |
BIN
Telegram/Resources/icons/box_title_shadow.png
Normal file
After Width: | Height: | Size: 85 B |
BIN
Telegram/Resources/icons/box_title_shadow@2x.png
Normal file
After Width: | Height: | Size: 105 B |
BIN
Telegram/Resources/icons/contacts_add.png
Normal file
After Width: | Height: | Size: 316 B |
BIN
Telegram/Resources/icons/contacts_add@2x.png
Normal file
After Width: | Height: | Size: 590 B |
BIN
Telegram/Resources/icons/history_empty_dog.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
Telegram/Resources/icons/history_empty_dog@2x.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
Telegram/Resources/icons/title_icon.png
Normal file
After Width: | Height: | Size: 265 B |
BIN
Telegram/Resources/icons/title_icon@2x.png
Normal file
After Width: | Height: | Size: 401 B |
BIN
Telegram/Resources/icons/title_icon_bg.png
Normal file
After Width: | Height: | Size: 363 B |
BIN
Telegram/Resources/icons/title_icon_bg@2x.png
Normal file
After Width: | Height: | Size: 701 B |
|
@ -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));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<ScrollArea> _scroll;
|
||||
int32 _topSkip, _bottomSkip;
|
||||
int _topSkip, _bottomSkip;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<MTPWallPaper> &result) {
|
||||
void BackgroundBox::Inner::gotWallpapers(const MTPVector<MTPWallPaper> &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<MTPWallPaper> &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) {
|
||||
}
|
||||
|
|
|
@ -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> _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;
|
||||
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<UserData*>;
|
||||
namespace Ui {
|
||||
class MultiSelect;
|
||||
template <typename Widget>
|
||||
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<UserData*> selected();
|
||||
QVector<MTPInputUser> 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<MTPPeer> &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 <typename FilterCallback>
|
||||
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<Dialogs::IndexedList> _customList;
|
||||
Dialogs::IndexedList *_contacts = nullptr;
|
||||
Dialogs::Row *_sel = nullptr;
|
||||
QString _filter;
|
||||
typedef QVector<Dialogs::Row*> 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<PeerData*, ContactData*> ContactsData;
|
||||
ContactsData _contactsData;
|
||||
typedef QMap<PeerData*, bool> CheckedContacts;
|
||||
CheckedContacts _checkedContacts;
|
||||
|
||||
ContactData *contactData(Dialogs::Row *row);
|
||||
|
||||
bool _searching = false;
|
||||
QString _lastQuery;
|
||||
typedef QVector<PeerData*> ByUsernameRows;
|
||||
typedef QVector<ContactData*> 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> _inner;
|
||||
ChildWidget<Ui::WidgetSlideWrap<Ui::MultiSelect>> _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<mtpRequestId, QString> 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<void(PeerData *peer, bool selected)> callback);
|
||||
void peerUnselected(PeerData *peer);
|
||||
|
||||
void updateFilter(QString filter = QString());
|
||||
void updateSelection();
|
||||
|
||||
void selectSkip(int32 dir);
|
||||
void selectSkipPage(int32 h, int32 dir);
|
||||
|
||||
QVector<UserData*> selected();
|
||||
QVector<MTPInputUser> selectedInputs();
|
||||
bool allAdmins() const {
|
||||
return _allAdmins.checked();
|
||||
}
|
||||
void setAllAdminsChangedCallback(base::lambda_unique<void()> allAdminsChangedCallback) {
|
||||
_allAdminsChangedCallback = std_::move(allAdminsChangedCallback);
|
||||
}
|
||||
|
||||
void loadProfilePhotos(int32 yFrom);
|
||||
void chooseParticipant();
|
||||
|
||||
void peopleReceived(const QString &query, const QVector<MTPPeer> &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<UserData*> MemberRows;
|
||||
typedef QVector<QDateTime> MemberDates;
|
||||
typedef QVector<MemberRole> MemberRoles;
|
||||
typedef QVector<MemberData*> 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<void()> updateCallback);
|
||||
|
||||
ContactsBox *_addBox;
|
||||
std_::unique_ptr<Ui::RoundImageCheckbox> 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 <typename FilterCallback>
|
||||
void addDialogsToList(FilterCallback callback);
|
||||
|
||||
bool usingMultiSelect() const {
|
||||
return (_chat != nullptr) || (_creating != CreatingGroupNone && (!_channel || _membersFilter != MembersFilter::Admins));
|
||||
}
|
||||
|
||||
base::lambda_unique<void(PeerData *peer, bool selected)> _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<void()> _allAdminsChangedCallback;
|
||||
|
||||
PeerData *_addToPeer = nullptr;
|
||||
UserData *_addAdmin = nullptr;
|
||||
mtpRequestId _addAdminRequestId = 0;
|
||||
ConfirmBox *_addAdminBox = nullptr;
|
||||
|
||||
int32 _time;
|
||||
|
||||
std_::unique_ptr<Dialogs::IndexedList> _customList;
|
||||
Dialogs::IndexedList *_contacts = nullptr;
|
||||
Dialogs::Row *_sel = nullptr;
|
||||
QString _filter;
|
||||
using FilteredDialogs = QVector<Dialogs::Row*>;
|
||||
FilteredDialogs _filtered;
|
||||
int _filteredSel = -1;
|
||||
bool _mouseSel = false;
|
||||
|
||||
using ContactsData = QMap<PeerData*, ContactData*>;
|
||||
ContactsData _contactsData;
|
||||
using CheckedContacts = OrderedSet<PeerData*>;
|
||||
CheckedContacts _checkedContacts;
|
||||
|
||||
ContactData *contactData(Dialogs::Row *row);
|
||||
|
||||
bool _searching = false;
|
||||
QString _lastQuery;
|
||||
using ByUsernameRows = QVector<PeerData*>;
|
||||
using ByUsernameDatas = QVector<ContactData*>;
|
||||
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;
|
||||
|
||||
};
|
||||
|
|
608
Telegram/SourceFiles/boxes/members_box.cpp
Normal file
|
@ -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<MTPChannelParticipant>::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;
|
||||
}
|
178
Telegram/SourceFiles/boxes/members_box.h
Normal file
|
@ -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<UserData*>;
|
||||
|
||||
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> _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<UserData*> MemberRows;
|
||||
typedef QVector<QDateTime> MemberDates;
|
||||
typedef QVector<MemberRole> MemberRoles;
|
||||
typedef QVector<MemberData*> MemberDatas;
|
||||
MemberRows _rows;
|
||||
MemberDates _dates;
|
||||
MemberRoles _roles;
|
||||
MemberDatas _datas;
|
||||
|
||||
int32 _aboutWidth;
|
||||
Text _about;
|
||||
int32 _aboutHeight;
|
||||
|
||||
QPoint _lastMousePos;
|
||||
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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<SessionData> 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<Data>;
|
||||
|
||||
void gotAuthorizations(const MTPaccount_Authorizations &result);
|
||||
|
||||
bool _loading;
|
||||
|
||||
Data _current;
|
||||
List _list;
|
||||
|
||||
class Inner;
|
||||
ChildWidget<Inner> _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<uint64, IconedButton*> 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;
|
||||
|
||||
};
|
||||
|
|
|
@ -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::IndexedList>(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<Chat*>(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<void()> 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<void(PeerData *peer, bool selected)> 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<MTPPeer> &people) {
|
||||
void ShareBox::Inner::peopleReceived(const QString &query, const QVector<MTPPeer> &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<MTPPeer> &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<MTPPeer> &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<PeerData*> ShareInner::selected() const {
|
||||
QVector<PeerData*> ShareBox::Inner::selected() const {
|
||||
QVector<PeerData*> 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<int32*>(shareHashData.data());
|
||||
|
|
|
@ -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<internal::ShareInner> _inner;
|
||||
ChildWidget<InputField> _filter;
|
||||
ChildWidget<IconedButton> _filterCancel;
|
||||
class Inner;
|
||||
ChildWidget<Inner> _inner;
|
||||
ChildWidget<Ui::MultiSelect> _select;
|
||||
|
||||
ChildWidget<BoxButton> _copy;
|
||||
ChildWidget<BoxButton> _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<void(PeerData *peer, bool selected)> callback);
|
||||
void peerUnselected(PeerData *peer);
|
||||
|
||||
QVector<PeerData*> 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<void()> 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<Icon> 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<Dialogs::Row*>;
|
||||
FilteredDialogs _filtered;
|
||||
|
||||
QPixmap _wideCheckCache, _wideCheckIconCache;
|
||||
|
||||
using DataMap = QMap<PeerData*, Chat*>;
|
||||
DataMap _dataMap;
|
||||
using SelectedChats = OrderedSet<PeerData*>;
|
||||
SelectedChats _selected;
|
||||
|
||||
base::lambda_unique<void(PeerData *peer, bool selected)> _peerSelectedChangedCallback;
|
||||
|
||||
ChatData *data(Dialogs::Row *row);
|
||||
|
||||
bool _searching = false;
|
||||
|
@ -221,5 +219,3 @@ private:
|
|||
ByUsernameDatas d_byUsernameFiltered;
|
||||
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
|
1254
Telegram/SourceFiles/boxes/stickers_box.cpp
Normal file
239
Telegram/SourceFiles/boxes/stickers_box.h
Normal file
|
@ -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> _inner;
|
||||
ChildWidget<BoxButton> _save = { nullptr };
|
||||
ChildWidget<BoxButton> _cancel = { nullptr };
|
||||
OrderedSet<mtpRequestId> _disenableRequests;
|
||||
mtpRequestId _reorderRequest = 0;
|
||||
ChildWidget<Ui::PlainShadow> _topShadow = { nullptr };
|
||||
ChildWidget<ScrollableBoxShadow> _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<StickerSetRow*>;
|
||||
|
||||
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<uint64> _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;
|
||||
};
|
|
@ -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> _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<internal::StickersInner> _inner;
|
||||
ChildWidget<BoxButton> _save = { nullptr };
|
||||
ChildWidget<BoxButton> _cancel = { nullptr };
|
||||
OrderedSet<mtpRequestId> _disenableRequests;
|
||||
mtpRequestId _reorderRequest = 0;
|
||||
ChildWidget<Ui::PlainShadow> _topShadow = { nullptr };
|
||||
ChildWidget<ScrollableBoxShadow> _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<StickerSetRow*>;
|
||||
|
||||
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<uint64> _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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Return, Args...> {
|
|||
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 <typename Lambda, typename = IsOther<Lambda>>
|
||||
lambda_wrap(const Lambda &other) : Parent(&internal::lambda_wrap_helper_copy<Lambda, Return, Args...>::instance, typename Parent::Private()) {
|
||||
internal::lambda_wrap_helper_copy<Lambda, Return, Args...>::construct_copy_lambda_method(this->storage_, &other);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -334,6 +334,16 @@ QVector<int> 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) {
|
||||
|
|
|
@ -48,6 +48,8 @@ private:
|
|||
|
||||
};
|
||||
|
||||
void paintEmpty(Painter &p, int width, int height);
|
||||
|
||||
void serviceColorsUpdated();
|
||||
|
||||
} // namespace HistoryLayout
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -136,6 +136,8 @@ private:
|
|||
void findCurrent();
|
||||
void loadBack();
|
||||
|
||||
void generateTransparentBrush();
|
||||
|
||||
void updateCursor();
|
||||
void setZoomLevel(int newZoom);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ public:
|
|||
void maximizedChanged(bool maximized, bool force = false);
|
||||
|
||||
HitTestType hitTest(const QPoint &p);
|
||||
QRect iconRect() const;
|
||||
|
||||
void setHideLevel(float64 level);
|
||||
|
||||
|
|
|
@ -102,6 +102,22 @@ namespace anim {
|
|||
float64 easeInQuint(const float64 &delta, const float64 &dt);
|
||||
float64 easeOutQuint(const float64 &delta, const float64 &dt);
|
||||
|
||||
template <int BumpRatioNumerator, int BumpRatioDenominator>
|
||||
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()>;
|
||||
|
||||
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 <typename Lambda>
|
||||
void start(Lambda &&updateCallback, const ValueType &from, const ValueType &to, float64 duration, anim::transition transition = anim::linear) {
|
||||
if (!_data) {
|
||||
_data = std_::make_unique<Data>(from, std_::move(updateCallback));
|
||||
_data = std_::make_unique<Data>(from, std_::forward<Lambda>(updateCallback));
|
||||
}
|
||||
_data->value.start(to);
|
||||
_data->duration = duration;
|
||||
|
@ -517,11 +539,17 @@ public:
|
|||
|
||||
private:
|
||||
struct Data {
|
||||
Data(const ValueType &from, Callback &&updateCallback)
|
||||
template <typename Lambda, typename = std_::enable_if_t<std_::is_rvalue_reference<Lambda&&>::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<void()> &updateCallback)
|
||||
: value(from, from)
|
||||
, a_animation(animation(this, &Data::step))
|
||||
, updateCallback(base::lambda_wrap<void()>(updateCallback)) {
|
||||
}
|
||||
void step(float64 ms, bool timer) {
|
||||
auto dt = (ms >= duration) ? 1. : (ms / duration);
|
||||
if (dt >= 1) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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> _inner;
|
||||
ChildWidget<Ui::MultiSelect> _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;
|
||||
|
||||
};
|
||||
|
|
217
Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp
Normal file
|
@ -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<void()> 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
|
68
Telegram/SourceFiles/ui/effects/round_image_checkbox.h
Normal file
|
@ -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<void(Painter &p, int x, int y, int outerWidth, int size)>;
|
||||
RoundImageCheckbox(const style::RoundImageCheckbox &st, base::lambda_wrap<void()> 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<void()> _updateCallback;
|
||||
PaintRoundImage _paintRoundImage;
|
||||
|
||||
bool _checked = false;
|
||||
QPixmap _wideCache;
|
||||
FloatAnimation _selection;
|
||||
std_::vector_of_moveable<Icon> _icons;
|
||||
|
||||
// Those pixmaps are shared among all checkboxes that have the same style.
|
||||
QPixmap _wideCheckBgCache, _wideCheckFullCache;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
|
@ -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<QTouchEvent*>(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<QTouchEvent*>(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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<const char*>(data), size);
|
||||
auto baForStream = QByteArray::fromRawData(reinterpret_cast<const char*>(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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,6 +105,9 @@ public:
|
|||
return std_::make_unique<Icon>(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 {
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
856
Telegram/SourceFiles/ui/widgets/multi_select.cpp
Normal file
|
@ -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<void()> 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<void()> 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<SlideAnimation> _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<void()> _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<void(const QString &query)> callback) {
|
||||
_queryChangedCallback = std_::move(callback);
|
||||
}
|
||||
|
||||
void MultiSelect::setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback) {
|
||||
_inner->setSubmittedCallback(std_::move(callback));
|
||||
}
|
||||
|
||||
void MultiSelect::setResizedCallback(base::lambda_unique<void()> 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<Inner::Item>(_st.item, itemId, text, color, std_::move(paintRoundImage)), way);
|
||||
}
|
||||
|
||||
void MultiSelect::setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> 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<void(const QString &query)> callback) {
|
||||
_queryChangedCallback = std_::move(callback);
|
||||
}
|
||||
|
||||
void MultiSelect::Inner::setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> 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> 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<void(uint64 itemId)> callback) {
|
||||
_itemRemovedCallback = std_::move(callback);
|
||||
}
|
||||
|
||||
void MultiSelect::Inner::setResizedCallback(base::lambda_unique<void(int heightDelta)> 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
|
169
Telegram/SourceFiles/ui/widgets/multi_select.h
Normal file
|
@ -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<void(const QString &query)> callback);
|
||||
void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback);
|
||||
void setResizedCallback(base::lambda_unique<void()> callback);
|
||||
|
||||
enum class AddItemWay {
|
||||
Default,
|
||||
SkipAnimation,
|
||||
};
|
||||
using PaintRoundImage = base::lambda_unique<void(Painter &p, int x, int y, int outerWidth, int size)>;
|
||||
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<void(uint64 itemId)> 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<ScrollArea> _scroll;
|
||||
|
||||
class Inner;
|
||||
ChildWidget<Inner> _inner;
|
||||
|
||||
base::lambda_unique<void()> _resizedCallback;
|
||||
base::lambda_unique<void(const QString &query)> _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<void(int activeTop, int activeBottom)>;
|
||||
Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder, ScrollCallback callback);
|
||||
|
||||
QString getQuery() const;
|
||||
bool setInnerFocus();
|
||||
void clearQuery();
|
||||
|
||||
void setQueryChangedCallback(base::lambda_unique<void(const QString &query)> callback);
|
||||
void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback);
|
||||
|
||||
class Item;
|
||||
void addItem(std_::unique_ptr<Item> item, AddItemWay way);
|
||||
void setItemText(uint64 itemId, const QString &text);
|
||||
|
||||
void setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback);
|
||||
void removeItem(uint64 itemId);
|
||||
|
||||
void setResizedCallback(base::lambda_unique<void(int heightDelta)> 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<Item*>;
|
||||
Items _items;
|
||||
using RemovingItems = OrderedSet<Item*>;
|
||||
RemovingItems _removingItems;
|
||||
|
||||
int _selected = -1;
|
||||
int _active = -1;
|
||||
bool _overDelete = false;
|
||||
|
||||
int _fieldLeft = 0;
|
||||
int _fieldTop = 0;
|
||||
int _fieldWidth = 0;
|
||||
ChildWidget<InputField> _field;
|
||||
ChildWidget<Ui::IconButton> _cancel;
|
||||
|
||||
int _newHeight = 0;
|
||||
IntAnimation _height;
|
||||
|
||||
base::lambda_unique<void(const QString &query)> _queryChangedCallback;
|
||||
base::lambda_unique<void(bool ctrlShiftEnter)> _submittedCallback;
|
||||
base::lambda_unique<void(uint64 itemId)> _itemRemovedCallback;
|
||||
base::lambda_unique<void(int heightDelta)> _resizedCallback;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
|
@ -30,7 +30,7 @@ public:
|
|||
using UpdateCallback = base::lambda_unique<void()>;
|
||||
WidgetSlideWrap(QWidget *parent, Widget *entity
|
||||
, style::margins entityPadding
|
||||
, UpdateCallback &&updateCallback
|
||||
, UpdateCallback updateCallback
|
||||
, int duration = st::widgetSlideDuration) : TWidget(parent)
|
||||
, _entity(entity)
|
||||
, _padding(entityPadding)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -39,19 +39,17 @@ class ChatBackground : public base::Observable<ChatBackgroundUpdate> {
|
|||
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;
|
||||
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|