/* 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 "dropdown.h" #include "styles/style_stickers.h" #include "boxes/confirmbox.h" #include "boxes/stickersetbox.h" #include "inline_bots/inline_bot_result.h" #include "inline_bots/inline_bot_layout_item.h" #include "dialogs/dialogs_layout.h" #include "historywidget.h" #include "localstorage.h" #include "lang.h" #include "mainwindow.h" #include "apiwrap.h" #include "mainwidget.h" Dropdown::Dropdown(QWidget *parent, const style::dropdown &st) : TWidget(parent) , _st(st) , _width(_st.width) , a_opacity(0) , _a_appearance(animation(this, &Dropdown::step_appearance)) , _shadow(_st.shadow) { resetButtons(); _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); } } void Dropdown::ignoreShow(bool ignore) { _ignore = ignore; } void Dropdown::onWndActiveChanged() { if (!App::wnd()->windowHandle()->isActive() && !isHidden()) { leaveEvent(0); } } IconedButton *Dropdown::addButton(IconedButton *button) { button->setParent(this); int32 nw = _st.padding.left() + _st.padding.right() + button->width(); if (nw > _width) { _width = nw; for (int32 i = 0, l = _buttons.size(); i < l; ++i) _buttons[i]->resize(_width - _st.padding.left() - _st.padding.right(), _buttons[i]->height()); } else { button->resize(_width - _st.padding.left() - _st.padding.right(), button->height()); } if (!button->isHidden()) { if (_height > _st.padding.top() + _st.padding.bottom()) { _height += _st.border; } _height += button->height(); } _buttons.push_back(button); connect(button, SIGNAL(stateChanged(int, ButtonStateChangeSource)), this, SLOT(buttonStateChanged(int, ButtonStateChangeSource))); resize(_width, _height); return button; } void Dropdown::resetButtons() { _width = qMax(_st.padding.left() + _st.padding.right(), int(_st.width)); _height = _st.padding.top() + _st.padding.bottom(); for (int32 i = 0, l = _buttons.size(); i < l; ++i) { delete _buttons[i]; } _buttons.clear(); resize(_width, _height); _selected = -1; } void Dropdown::updateButtons() { int32 top = _st.padding.top(), starttop = top; for (Buttons::const_iterator i = _buttons.cbegin(), e = _buttons.cend(); i != e; ++i) { if (!(*i)->isHidden()) { (*i)->move(_st.padding.left(), top); if ((*i)->width() != _width - _st.padding.left() - _st.padding.right()) { (*i)->resize(_width - _st.padding.left() - _st.padding.right(), (*i)->height()); } top += (*i)->height() + _st.border; } } _height = top + _st.padding.bottom() - (top > starttop ? _st.border : 0); resize(_width, _height); } void Dropdown::resizeEvent(QResizeEvent *e) { int32 top = _st.padding.top(); for (Buttons::const_iterator i = _buttons.cbegin(), e = _buttons.cend(); i != e; ++i) { if (!(*i)->isHidden()) { (*i)->move(_st.padding.left(), top); top += (*i)->height() + _st.border; } } } void Dropdown::paintEvent(QPaintEvent *e) { Painter p(this); if (_a_appearance.animating()) { p.setOpacity(a_opacity.current()); } // draw shadow QRect r(_st.padding.left(), _st.padding.top(), _width - _st.padding.left() - _st.padding.right(), _height - _st.padding.top() - _st.padding.bottom()); _shadow.paint(p, r, _st.shadowShift); if (!_buttons.isEmpty() && _st.border > 0) { // paint separators p.setPen(_st.borderColor->p); int32 top = _st.padding.top(), i = 0, l = _buttons.size(); for (; i < l; ++i) { if (!_buttons.at(i)->isHidden()) break; } if (i < l) { top += _buttons.at(i)->height(); for (++i; i < l; ++i) { if (!_buttons.at(i)->isHidden()) { p.fillRect(_st.padding.left(), top, _width - _st.padding.left() - _st.padding.right(), _st.border, _st.borderColor->b); top += _st.border + _buttons.at(i)->height(); } } } } } void Dropdown::enterEvent(QEvent *e) { _hideTimer.stop(); if (_hiding) showStart(); return TWidget::enterEvent(e); } void Dropdown::leaveEvent(QEvent *e) { if (_a_appearance.animating()) { hideStart(); } else { _hideTimer.start(300); } return TWidget::leaveEvent(e); } void Dropdown::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { if (_selected >= 0 && _selected < _buttons.size()) { emit _buttons[_selected]->clicked(); return; } } else if (e->key() == Qt::Key_Escape) { hideStart(); return; } if ((e->key() != Qt::Key_Up && e->key() != Qt::Key_Down) || _buttons.size() < 1) return; bool none = (_selected < 0 || _selected >= _buttons.size()); int32 delta = (e->key() == Qt::Key_Down ? 1 : -1); int32 newSelected = none ? (e->key() == Qt::Key_Down ? 0 : _buttons.size() - 1) : (_selected + delta); if (newSelected < 0) { newSelected = _buttons.size() - 1; } else if (newSelected >= _buttons.size()) { newSelected = 0; } int32 startFrom = newSelected; while (_buttons.at(newSelected)->isHidden()) { newSelected += delta; if (newSelected < 0) { newSelected = _buttons.size() - 1; } else if (newSelected >= _buttons.size()) { newSelected = 0; } if (newSelected == startFrom) return; } if (!none) { _buttons[_selected]->setOver(false); } _selected = newSelected; _buttons[_selected]->setOver(true); } void Dropdown::buttonStateChanged(int oldState, ButtonStateChangeSource source) { if (source == ButtonByUser) { for (int32 i = 0, l = _buttons.size(); i < l; ++i) { if (_buttons[i]->getState() & Button::StateOver) { if (i != _selected) { _buttons[i]->setOver(false); } } } } else if (source == ButtonByHover) { bool found = false; for (int32 i = 0, l = _buttons.size(); i < l; ++i) { if (_buttons[i]->getState() & Button::StateOver) { found = true; if (i != _selected) { int32 sel = _selected; _selected = i; if (sel >= 0 && sel < _buttons.size()) { _buttons[sel]->setOver(false); } } } } if (!found) { _selected = -1; } } } void Dropdown::otherEnter() { _hideTimer.stop(); showStart(); } void Dropdown::otherLeave() { if (_a_appearance.animating()) { hideStart(); } else { _hideTimer.start(0); } } void Dropdown::fastHide() { if (_a_appearance.animating()) { _a_appearance.stop(); } a_opacity = anim::fvalue(0, 0); _hideTimer.stop(); hide(); } void Dropdown::adjustButtons() { for (Buttons::const_iterator i = _buttons.cbegin(), e = _buttons.cend(); i != e; ++i) { (*i)->setOpacity(a_opacity.current()); } } void Dropdown::hideStart() { _hiding = true; a_opacity.start(0); _a_appearance.start(); } void Dropdown::hideFinish() { emit hiding(); hide(); for (Buttons::const_iterator i = _buttons.cbegin(), e = _buttons.cend(); i != e; ++i) { (*i)->clearState(); } _selected = -1; } void Dropdown::showStart() { if (!isHidden() && a_opacity.current() == 1) { return; } _selected = -1; _hiding = false; show(); a_opacity.start(1); _a_appearance.start(); } void Dropdown::step_appearance(float64 ms, bool timer) { float64 dt = ms / _st.duration; if (dt >= 1) { _a_appearance.stop(); a_opacity.finish(); if (_hiding) { hideFinish(); } } else { a_opacity.update(dt, anim::linear); } adjustButtons(); if (timer) update(); } bool Dropdown::eventFilter(QObject *obj, QEvent *e) { if (e->type() == QEvent::Enter) { otherEnter(); } else if (e->type() == QEvent::Leave) { otherLeave(); } else if (e->type() == QEvent::MouseButtonPress && static_cast(e)->button() == Qt::LeftButton) { if (isHidden() || _hiding) { otherEnter(); } else { otherLeave(); } } return false; } DragArea::DragArea(QWidget *parent) : TWidget(parent) , _hiding(false) , _in(false) , a_opacity(0) , a_color(st::dragColor->c) , _a_appearance(animation(this, &DragArea::step_appearance)) , _shadow(st::boxShadow) { setMouseTracking(true); setAcceptDrops(true); } void DragArea::mouseMoveEvent(QMouseEvent *e) { if (_hiding) return; bool newIn = QRect(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()).contains(e->pos()); if (newIn != _in) { _in = newIn; a_opacity.start(1); a_color.start((_in ? st::dragDropColor : st::dragColor)->c); _a_appearance.start(); } } void DragArea::dragMoveEvent(QDragMoveEvent *e) { QRect r(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()); bool newIn = r.contains(e->pos()); if (newIn != _in) { _in = newIn; a_opacity.start(1); a_color.start((_in ? st::dragDropColor : st::dragColor)->c); _a_appearance.start(); } e->setDropAction(_in ? Qt::CopyAction : Qt::IgnoreAction); e->accept(); } void DragArea::setText(const QString &text, const QString &subtext) { _text = text; _subtext = subtext; update(); } void DragArea::paintEvent(QPaintEvent *e) { Painter p(this); if (_a_appearance.animating()) { p.setOpacity(a_opacity.current()); } QRect r(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()); // draw shadow _shadow.paint(p, r, st::boxShadowShift); p.fillRect(r, st::white->b); p.setPen(a_color.current()); p.setFont(st::dragFont->f); p.drawText(QRect(0, (height() - st::dragHeight) / 2, width(), st::dragFont->height), _text, QTextOption(style::al_top)); p.setFont(st::dragSubfont->f); p.drawText(QRect(0, (height() + st::dragHeight) / 2 - st::dragSubfont->height, width(), st::dragSubfont->height * 2), _subtext, QTextOption(style::al_top)); } void DragArea::dragEnterEvent(QDragEnterEvent *e) { static_cast(parentWidget())->dragEnterEvent(e); e->setDropAction(Qt::IgnoreAction); e->accept(); } void DragArea::dragLeaveEvent(QDragLeaveEvent *e) { static_cast(parentWidget())->dragLeaveEvent(e); _in = false; a_opacity.start(_hiding ? 0 : 1); a_color.start((_in ? st::dragDropColor : st::dragColor)->c); _a_appearance.start(); } void DragArea::dropEvent(QDropEvent *e) { static_cast(parentWidget())->dropEvent(e); if (e->isAccepted()) { emit dropped(e->mimeData()); } } void DragArea::otherEnter() { showStart(); } void DragArea::otherLeave() { hideStart(); } void DragArea::fastHide() { if (_a_appearance.animating()) { _a_appearance.stop(); } a_opacity = anim::fvalue(0, 0); hide(); } void DragArea::hideStart() { _hiding = true; _in = false; a_opacity.start(0); a_color.start((_in ? st::dragDropColor : st::dragColor)->c); _a_appearance.start(); } void DragArea::hideFinish() { hide(); _in = false; a_color = anim::cvalue(st::dragColor->c); } void DragArea::showStart() { _hiding = false; show(); a_opacity.start(1); a_color.start((_in ? st::dragDropColor : st::dragColor)->c); _a_appearance.start(); } void DragArea::step_appearance(float64 ms, bool timer) { float64 dt = ms / st::dropdownDef.duration; if (dt >= 1) { a_opacity.finish(); a_color.finish(); if (_hiding) { hideFinish(); } _a_appearance.stop(); } else { a_opacity.update(dt, anim::linear); a_color.update(dt, anim::linear); } if (timer) update(); }