tdesktop/Telegram/SourceFiles/boxes/create_poll_box.cpp
Kirsan 9c562931a2 Respect user settings "Send by ..." for:
forward dialog
send file dialog
edit caption dialog
notification replay
schedule messages
new channel dialog
group description edit dialog
create poll dialog
rate call dialog
report bot dialog
support mode
2020-02-11 12:29:34 +04:00

1008 lines
26 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/create_poll_box.h"
#include "lang/lang_keys.h"
#include "data/data_poll.h"
#include "ui/toast/toast.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/toast/toast.h"
#include "main/main_session.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/message_field.h"
#include "history/view/history_view_schedule_box.h"
#include "settings/settings_common.h"
#include "base/unique_qptr.h"
#include "base/event_filter.h"
#include "base/call_delayed.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_settings.h"
namespace {
constexpr auto kQuestionLimit = 255;
constexpr auto kMaxOptionsCount = PollData::kMaxOptions;
constexpr auto kOptionLimit = 100;
constexpr auto kWarnQuestionLimit = 80;
constexpr auto kWarnOptionLimit = 30;
constexpr auto kErrorLimit = 99;
class Options {
public:
Options(
not_null<QWidget*> outer,
not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session,
bool chooseCorrectEnabled);
[[nodiscard]] bool hasOptions() const;
[[nodiscard]] bool isValid() const;
[[nodiscard]] bool hasCorrect() const;
[[nodiscard]] std::vector<PollAnswer> toPollAnswers() const;
void focusFirst();
void enableChooseCorrect(bool enabled);
[[nodiscard]] rpl::producer<int> usedCount() const;
[[nodiscard]] rpl::producer<not_null<QWidget*>> scrollToWidget() const;
[[nodiscard]] rpl::producer<> backspaceInFront() const;
private:
class Option {
public:
Option(
not_null<QWidget*> outer,
not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session,
int position,
std::shared_ptr<Ui::RadiobuttonGroup> group);
Option(const Option &other) = delete;
Option &operator=(const Option &other) = delete;
void toggleRemoveAlways(bool toggled);
void enableChooseCorrect(
std::shared_ptr<Ui::RadiobuttonGroup> group);
void show(anim::type animated);
void destroy(FnMut<void()> done);
[[nodisacrd]] bool hasShadow() const;
void createShadow();
void destroyShadow();
[[nodiscard]] bool isEmpty() const;
[[nodiscard]] bool isGood() const;
[[nodiscard]] bool isTooLong() const;
[[nodiscard]] bool isCorrect() const;
[[nodiscard]] bool hasFocus() const;
void setFocus() const;
void clearValue();
void setPlaceholder() const;
void removePlaceholder() const;
not_null<Ui::InputField*> field() const;
[[nodiscard]] PollAnswer toPollAnswer(int index) const;
[[nodiscard]] rpl::producer<Qt::MouseButton> removeClicks() const;
private:
void createRemove();
void createWarning();
void toggleCorrectSpace(bool visible);
void updateFieldGeometry();
base::unique_qptr<Ui::SlideWrap<Ui::RpWidget>> _wrap;
not_null<Ui::RpWidget*> _content;
base::unique_qptr<Ui::FadeWrapScaled<Ui::Radiobutton>> _correct;
Ui::Animations::Simple _correctShown;
bool _hasCorrect = false;
Ui::InputField *_field = nullptr;
base::unique_qptr<Ui::PlainShadow> _shadow;
base::unique_qptr<Ui::CrossButton> _remove;
rpl::variable<bool> *_removeAlways = nullptr;
};
[[nodiscard]] bool full() const;
[[nodiscard]] bool correctShadows() const;
void fixShadows();
void removeEmptyTail();
void addEmptyOption();
void checkLastOption();
void validateState();
void fixAfterErase();
void destroy(std::unique_ptr<Option> option);
void removeDestroyed(not_null<Option*> field);
int findField(not_null<Ui::InputField*> field) const;
[[nodiscard]] auto createChooseCorrectGroup()
-> std::shared_ptr<Ui::RadiobuttonGroup>;
not_null<QWidget*> _outer;
not_null<Ui::VerticalLayout*> _container;
const not_null<Main::Session*> _session;
std::shared_ptr<Ui::RadiobuttonGroup> _chooseCorrectGroup;
int _position = 0;
std::vector<std::unique_ptr<Option>> _list;
std::vector<std::unique_ptr<Option>> _destroyed;
rpl::variable<int> _usedCount = 0;
bool _hasOptions = false;
bool _isValid = false;
bool _hasCorrect = false;
rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
rpl::event_stream<> _backspaceInFront;
};
void InitField(
not_null<QWidget*> container,
not_null<Ui::InputField*> field,
not_null<Main::Session*> session) {
field->setInstantReplaces(Ui::InstantReplaces::Default());
field->setInstantReplacesEnabled(
session->settings().replaceEmojiValue());
auto options = Ui::Emoji::SuggestionsController::Options();
options.suggestExactFirstWord = false;
Ui::Emoji::SuggestionsController::Init(
container,
field,
session,
options);
}
not_null<Ui::FlatLabel*> CreateWarningLabel(
not_null<QWidget*> parent,
not_null<Ui::InputField*> field,
int valueLimit,
int warnLimit) {
const auto result = Ui::CreateChild<Ui::FlatLabel>(
parent.get(),
QString(),
st::createPollWarning);
result->setAttribute(Qt::WA_TransparentForMouseEvents);
QObject::connect(field, &Ui::InputField::changed, [=] {
Ui::PostponeCall(crl::guard(field, [=] {
const auto length = field->getLastText().size();
const auto value = valueLimit - length;
const auto shown = (value < warnLimit)
&& (field->height() > st::createPollOptionField.heightMin);
result->setRichText((value >= 0)
? QString::number(value)
: textcmdLink(1, QString::number(value)));
result->setVisible(shown);
}));
});
return result;
}
void FocusAtEnd(not_null<Ui::InputField*> field) {
field->setFocus();
field->setCursorPosition(field->getLastText().size());
field->ensureCursorVisible();
}
Options::Option::Option(
not_null<QWidget*> outer,
not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session,
int position,
std::shared_ptr<Ui::RadiobuttonGroup> group)
: _wrap(container->insert(
position,
object_ptr<Ui::SlideWrap<Ui::RpWidget>>(
container,
object_ptr<Ui::RpWidget>(container))))
, _content(_wrap->entity())
, _field(
Ui::CreateChild<Ui::InputField>(
_content.get(),
st::createPollOptionField,
Ui::InputField::Mode::NoNewlines,
tr::lng_polls_create_option_add())) {
InitField(outer, _field, session);
_field->setMaxLength(kOptionLimit + kErrorLimit);
_field->show();
_wrap->hide(anim::type::instant);
_content->widthValue(
) | rpl::start_with_next([=] {
updateFieldGeometry();
}, _field->lifetime());
_field->heightValue(
) | rpl::start_with_next([=](int height) {
_content->resize(_content->width(), height);
}, _field->lifetime());
QObject::connect(_field, &Ui::InputField::changed, [=] {
Ui::PostponeCall(crl::guard(_field, [=] {
if (_hasCorrect) {
_correct->toggle(isGood(), anim::type::normal);
}
}));
});
createShadow();
createRemove();
createWarning();
enableChooseCorrect(group);
_correctShown.stop();
if (_correct) {
_correct->finishAnimating();
}
updateFieldGeometry();
}
bool Options::Option::hasShadow() const {
return (_shadow != nullptr);
}
void Options::Option::createShadow() {
Expects(_content != nullptr);
if (_shadow) {
return;
}
_shadow.reset(Ui::CreateChild<Ui::PlainShadow>(field().get()));
_shadow->show();
field()->sizeValue(
) | rpl::start_with_next([=](QSize size) {
const auto left = st::createPollFieldPadding.left();
_shadow->setGeometry(
left,
size.height() - st::lineWidth,
size.width() - left,
st::lineWidth);
}, _shadow->lifetime());
}
void Options::Option::destroyShadow() {
_shadow = nullptr;
}
void Options::Option::createRemove() {
using namespace rpl::mappers;
const auto field = this->field();
auto &lifetime = field->lifetime();
const auto remove = Ui::CreateChild<Ui::CrossButton>(
field.get(),
st::createPollOptionRemove);
remove->hide(anim::type::instant);
const auto toggle = lifetime.make_state<rpl::variable<bool>>(false);
_removeAlways = lifetime.make_state<rpl::variable<bool>>(false);
QObject::connect(field, &Ui::InputField::changed, [=] {
// Don't capture 'this'! Because Option is a value type.
*toggle = !field->getLastText().isEmpty();
});
rpl::combine(
toggle->value(),
_removeAlways->value(),
_1 || _2
) | rpl::start_with_next([=](bool shown) {
remove->toggle(shown, anim::type::normal);
}, remove->lifetime());
field->widthValue(
) | rpl::start_with_next([=](int width) {
remove->moveToRight(
st::createPollOptionRemovePosition.x(),
st::createPollOptionRemovePosition.y(),
width);
}, remove->lifetime());
_remove.reset(remove);
}
void Options::Option::createWarning() {
using namespace rpl::mappers;
const auto field = this->field();
const auto warning = CreateWarningLabel(
field,
field,
kOptionLimit,
kWarnOptionLimit);
rpl::combine(
field->sizeValue(),
warning->sizeValue()
) | rpl::start_with_next([=](QSize size, QSize label) {
warning->moveToLeft(
(size.width()
- label.width()
- st::createPollWarningPosition.x()),
(size.height()
- label.height()
- st::createPollWarningPosition.y()),
size.width());
}, warning->lifetime());
}
bool Options::Option::isEmpty() const {
return field()->getLastText().trimmed().isEmpty();
}
bool Options::Option::isGood() const {
return !field()->getLastText().trimmed().isEmpty() && !isTooLong();
}
bool Options::Option::isTooLong() const {
return (field()->getLastText().size() > kOptionLimit);
}
bool Options::Option::isCorrect() const {
return isGood() && _correct && _correct->entity()->Checkbox::checked();
}
bool Options::Option::hasFocus() const {
return field()->hasFocus();
}
void Options::Option::setFocus() const {
FocusAtEnd(field());
}
void Options::Option::clearValue() {
field()->setText(QString());
}
void Options::Option::setPlaceholder() const {
field()->setPlaceholder(tr::lng_polls_create_option_add());
}
void Options::Option::toggleRemoveAlways(bool toggled) {
*_removeAlways = toggled;
}
void Options::Option::enableChooseCorrect(
std::shared_ptr<Ui::RadiobuttonGroup> group) {
if (!group) {
if (_correct) {
_hasCorrect = false;
_correct->hide(anim::type::normal);
toggleCorrectSpace(false);
}
return;
}
static auto Index = 0;
const auto button = Ui::CreateChild<Ui::FadeWrapScaled<Ui::Radiobutton>>(
_content.get(),
object_ptr<Ui::Radiobutton>(
_content.get(),
group,
++Index,
QString(),
st::defaultCheckbox));
button->entity()->resize(
button->entity()->height(),
button->entity()->height());
button->hide(anim::type::instant);
_content->sizeValue(
) | rpl::start_with_next([=](QSize size) {
const auto left = st::createPollFieldPadding.left();
button->moveToLeft(
left,
(size.height() - button->heightNoMargins()) / 2);
}, button->lifetime());
_correct.reset(button);
_hasCorrect = true;
if (isGood()) {
_correct->show(anim::type::normal);
} else {
_correct->hide(anim::type::instant);
}
toggleCorrectSpace(true);
}
void Options::Option::toggleCorrectSpace(bool visible) {
_correctShown.start(
[=] { updateFieldGeometry(); },
visible ? 0. : 1.,
visible ? 1. : 0.,
st::fadeWrapDuration);
}
void Options::Option::updateFieldGeometry() {
const auto shown = _correctShown.value(_hasCorrect ? 1. : 0.);
const auto skip = st::defaultRadio.diameter
+ st::defaultCheckbox.textPosition.x();
const auto left = anim::interpolate(0, skip, shown);
const auto width = _content->width() - left;
_field->resizeToWidth(_content->width() - left);
_field->moveToLeft(left, 0);
}
not_null<Ui::InputField*> Options::Option::field() const {
return _field;
}
void Options::Option::removePlaceholder() const {
field()->setPlaceholder(rpl::single(QString()));
}
PollAnswer Options::Option::toPollAnswer(int index) const {
Expects(index >= 0 && index < kMaxOptionsCount);
auto result = PollAnswer{
field()->getLastText().trimmed(),
QByteArray(1, ('0' + index))
};
result.correct = _correct ? _correct->entity()->Checkbox::checked() : false;
return result;
}
rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
return _remove->clicks();
}
Options::Options(
not_null<QWidget*> outer,
not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session,
bool chooseCorrectEnabled)
: _outer(outer)
, _container(container)
, _session(session)
, _chooseCorrectGroup(chooseCorrectEnabled
? createChooseCorrectGroup()
: nullptr)
, _position(_container->count()) {
checkLastOption();
}
bool Options::full() const {
return (_list.size() == kMaxOptionsCount);
}
bool Options::hasOptions() const {
return _hasOptions;
}
bool Options::isValid() const {
return _isValid;
}
bool Options::hasCorrect() const {
return _hasCorrect;
}
rpl::producer<int> Options::usedCount() const {
return _usedCount.value();
}
rpl::producer<not_null<QWidget*>> Options::scrollToWidget() const {
return _scrollToWidget.events();
}
rpl::producer<> Options::backspaceInFront() const {
return _backspaceInFront.events();
}
void Options::Option::show(anim::type animated) {
_wrap->show(animated);
}
void Options::Option::destroy(FnMut<void()> done) {
if (anim::Disabled() || _wrap->isHidden()) {
Ui::PostponeCall(std::move(done));
return;
}
_wrap->hide(anim::type::normal);
base::call_delayed(
st::slideWrapDuration * 2,
_content.get(),
std::move(done));
}
std::vector<PollAnswer> Options::toPollAnswers() const {
auto result = std::vector<PollAnswer>();
result.reserve(_list.size());
auto counter = int(0);
const auto makeAnswer = [&](const std::unique_ptr<Option> &option) {
return option->toPollAnswer(counter++);
};
ranges::copy(
_list
| ranges::view::filter(&Option::isGood)
| ranges::view::transform(makeAnswer),
ranges::back_inserter(result));
return result;
}
void Options::focusFirst() {
Expects(!_list.empty());
_list.front()->setFocus();
}
std::shared_ptr<Ui::RadiobuttonGroup> Options::createChooseCorrectGroup() {
auto result = std::make_shared<Ui::RadiobuttonGroup>(0);
result->setChangedCallback([=](int) {
validateState();
});
return result;
}
void Options::enableChooseCorrect(bool enabled) {
_chooseCorrectGroup = enabled
? createChooseCorrectGroup()
: nullptr;
for (auto &option : _list) {
option->enableChooseCorrect(_chooseCorrectGroup);
}
validateState();
}
bool Options::correctShadows() const {
// Last one should be without shadow.
const auto noShadow = ranges::find(
_list,
true,
ranges::not_fn(&Option::hasShadow));
return (noShadow == end(_list) - 1);
}
void Options::fixShadows() {
if (correctShadows()) {
return;
}
for (auto &option : _list) {
option->createShadow();
}
_list.back()->destroyShadow();
}
void Options::removeEmptyTail() {
// Only one option at the end of options list can be empty.
// Remove all other trailing empty options.
// Only last empty and previous option have non-empty placeholders.
const auto focused = ranges::find_if(
_list,
&Option::hasFocus);
const auto end = _list.end();
const auto reversed = ranges::view::reverse(_list);
const auto emptyItem = ranges::find_if(
reversed,
ranges::not_fn(&Option::isEmpty)).base();
const auto focusLast = (focused > emptyItem) && (focused < end);
if (emptyItem == end) {
return;
}
if (focusLast) {
(*emptyItem)->setFocus();
}
for (auto i = emptyItem + 1; i != end; ++i) {
destroy(std::move(*i));
}
_list.erase(emptyItem + 1, end);
fixAfterErase();
}
void Options::destroy(std::unique_ptr<Option> option) {
const auto value = option.get();
option->destroy([=] { removeDestroyed(value); });
_destroyed.push_back(std::move(option));
}
void Options::fixAfterErase() {
Expects(!_list.empty());
const auto last = _list.end() - 1;
(*last)->setPlaceholder();
(*last)->toggleRemoveAlways(false);
if (last != begin(_list)) {
(*(last - 1))->setPlaceholder();
(*(last - 1))->toggleRemoveAlways(false);
}
fixShadows();
}
void Options::addEmptyOption() {
if (full()) {
return;
} else if (!_list.empty() && _list.back()->isEmpty()) {
return;
}
if (_list.size() > 1) {
(*(_list.end() - 2))->removePlaceholder();
(*(_list.end() - 2))->toggleRemoveAlways(true);
}
_list.push_back(std::make_unique<Option>(
_outer,
_container,
_session,
_position + _list.size() + _destroyed.size(),
_chooseCorrectGroup));
const auto field = _list.back()->field();
QObject::connect(field, &Ui::InputField::submitted, [=] {
const auto index = findField(field);
if (_list[index]->isGood() && index + 1 < _list.size()) {
_list[index + 1]->setFocus();
}
});
QObject::connect(field, &Ui::InputField::changed, [=] {
Ui::PostponeCall(crl::guard(field, [=] {
validateState();
}));
});
QObject::connect(field, &Ui::InputField::focused, [=] {
_scrollToWidget.fire_copy(field);
});
base::install_event_filter(field, [=](not_null<QEvent*> event) {
if (event->type() != QEvent::KeyPress
|| !field->getLastText().isEmpty()) {
return base::EventFilterResult::Continue;
}
const auto key = static_cast<QKeyEvent*>(event.get())->key();
if (key != Qt::Key_Backspace) {
return base::EventFilterResult::Continue;
}
const auto index = findField(field);
if (index > 0) {
_list[index - 1]->setFocus();
} else {
_backspaceInFront.fire({});
}
return base::EventFilterResult::Cancel;
});
_list.back()->removeClicks(
) | rpl::take(1) | rpl::start_with_next([=] {
Ui::PostponeCall(crl::guard(field, [=] {
Expects(!_list.empty());
const auto item = begin(_list) + findField(field);
if (item == _list.end() - 1) {
(*item)->clearValue();
return;
}
if ((*item)->hasFocus()) {
(*(item + 1))->setFocus();
}
destroy(std::move(*item));
_list.erase(item);
fixAfterErase();
validateState();
}));
}, field->lifetime());
_list.back()->show((_list.size() == 1)
? anim::type::instant
: anim::type::normal);
fixShadows();
}
void Options::removeDestroyed(not_null<Option*> option) {
const auto i = ranges::find(
_destroyed,
option.get(),
&std::unique_ptr<Option>::get);
Assert(i != end(_destroyed));
_destroyed.erase(i);
}
void Options::validateState() {
checkLastOption();
_hasOptions = (ranges::count_if(_list, &Option::isGood) > 1);
_isValid = _hasOptions
&& (ranges::find_if(_list, &Option::isTooLong) == end(_list));
_hasCorrect = ranges::find_if(_list, &Option::isCorrect) != end(_list);
const auto lastEmpty = !_list.empty() && _list.back()->isEmpty();
_usedCount = _list.size() - (lastEmpty ? 1 : 0);
}
int Options::findField(not_null<Ui::InputField*> field) const {
const auto result = ranges::find(
_list,
field,
&Option::field) - begin(_list);
Ensures(result >= 0 && result < _list.size());
return result;
}
void Options::checkLastOption() {
removeEmptyTail();
addEmptyOption();
}
} // namespace
CreatePollBox::CreatePollBox(
QWidget*,
not_null<Main::Session*> session,
PollData::Flags chosen,
PollData::Flags disabled,
Api::SendType sendType)
: _session(session)
, _chosen(chosen)
, _disabled(disabled)
, _sendType(sendType) {
}
rpl::producer<CreatePollBox::Result> CreatePollBox::submitRequests() const {
return _submitRequests.events();
}
void CreatePollBox::setInnerFocus() {
_setInnerFocus();
}
void CreatePollBox::submitFailed(const QString &error) {
Ui::Toast::Show(error);
}
not_null<Ui::InputField*> CreatePollBox::setupQuestion(
not_null<Ui::VerticalLayout*> container) {
using namespace Settings;
AddSubsectionTitle(container, tr::lng_polls_create_question());
const auto question = container->add(
object_ptr<Ui::InputField>(
container,
st::createPollField,
Ui::InputField::Mode::MultiLine,
tr::lng_polls_create_question_placeholder()),
st::createPollFieldPadding);
InitField(getDelegate()->outerContainer(), question, _session);
question->setMaxLength(kQuestionLimit + kErrorLimit);
question->setSubmitSettings(_session->settings().sendSubmitWay());
const auto warning = CreateWarningLabel(
container,
question,
kQuestionLimit,
kWarnQuestionLimit);
rpl::combine(
question->geometryValue(),
warning->sizeValue()
) | rpl::start_with_next([=](QRect geometry, QSize label) {
warning->moveToLeft(
(container->width()
- label.width()
- st::createPollWarningPosition.x()),
(geometry.y()
- st::createPollFieldPadding.top()
- st::settingsSubsectionTitlePadding.bottom()
- st::settingsSubsectionTitle.style.font->height
+ st::settingsSubsectionTitle.style.font->ascent
- st::createPollWarning.style.font->ascent),
geometry.width());
}, warning->lifetime());
return question;
}
object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
using namespace Settings;
const auto id = rand_value<uint64>();
const auto error = lifetime().make_state<Errors>(Error::Question);
auto result = object_ptr<Ui::VerticalLayout>(this);
const auto container = result.data();
const auto question = setupQuestion(container);
AddDivider(container);
AddSkip(container);
container->add(
object_ptr<Ui::FlatLabel>(
container,
tr::lng_polls_create_options(),
st::settingsSubsectionTitle),
st::createPollFieldTitlePadding);
const auto options = lifetime().make_state<Options>(
getDelegate()->outerContainer(),
container,
_session,
(_chosen & PollData::Flag::Quiz));
auto limit = options->usedCount() | rpl::after_next([=](int count) {
setCloseByEscape(!count);
setCloseByOutsideClick(!count);
}) | rpl::map([=](int count) {
return (count < kMaxOptionsCount)
? tr::lng_polls_create_limit(tr::now, lt_count, kMaxOptionsCount - count)
: tr::lng_polls_create_maximum(tr::now);
}) | rpl::after_next([=] {
container->resizeToWidth(container->widthNoMargins());
});
container->add(
object_ptr<Ui::DividerLabel>(
container,
object_ptr<Ui::FlatLabel>(
container,
std::move(limit),
st::boxDividerLabel),
st::createPollLimitPadding));
AddSkip(container);
AddSubsectionTitle(container, tr::lng_polls_create_settings());
const auto anonymous = (!(_disabled & PollData::Flag::PublicVotes))
? container->add(
object_ptr<Ui::Checkbox>(
container,
tr::lng_polls_create_anonymous(tr::now),
!(_chosen & PollData::Flag::PublicVotes),
st::defaultCheckbox),
st::createPollCheckboxMargin)
: nullptr;
const auto hasMultiple = !(_chosen & PollData::Flag::Quiz)
|| !(_disabled & PollData::Flag::Quiz);
const auto multiple = hasMultiple
? container->add(
object_ptr<Ui::Checkbox>(
container,
tr::lng_polls_create_multiple_choice(tr::now),
(_chosen & PollData::Flag::MultiChoice),
st::defaultCheckbox),
st::createPollCheckboxMargin)
: nullptr;
const auto quiz = container->add(
object_ptr<Ui::Checkbox>(
container,
tr::lng_polls_create_quiz_mode(tr::now),
(_chosen & PollData::Flag::Quiz),
st::defaultCheckbox),
st::createPollCheckboxMargin);
quiz->setDisabled(_disabled & PollData::Flag::Quiz);
if (multiple) {
multiple->setDisabled((_disabled & PollData::Flag::MultiChoice)
|| (_chosen & PollData::Flag::Quiz));
multiple->events(
) | rpl::filter([=](not_null<QEvent*> e) {
return (e->type() == QEvent::MouseButtonPress) && quiz->checked();
}) | rpl::start_with_next([=] {
Ui::Toast::Show(tr::lng_polls_create_one_answer(tr::now));
}, multiple->lifetime());
}
using namespace rpl::mappers;
quiz->checkedChanges(
) | rpl::start_with_next([=](bool checked) {
if (multiple) {
if (checked && multiple->checked()) {
multiple->setChecked(false);
}
multiple->setDisabled(checked
|| (_disabled & PollData::Flag::MultiChoice));
}
options->enableChooseCorrect(checked);
}, quiz->lifetime());
const auto isValidQuestion = [=] {
const auto text = question->getLastText().trimmed();
return !text.isEmpty() && (text.size() <= kQuestionLimit);
};
connect(question, &Ui::InputField::submitted, [=] {
if (isValidQuestion()) {
options->focusFirst();
}
});
_setInnerFocus = [=] {
question->setFocusFast();
};
const auto collectResult = [=] {
using Flag = PollData::Flag;
auto result = PollData(&_session->data(), id);
result.question = question->getLastText().trimmed();
result.answers = options->toPollAnswers();
const auto publicVotes = (anonymous && !anonymous->checked());
const auto multiChoice = (multiple && multiple->checked());
result.setFlags(Flag(0)
| (publicVotes ? Flag::PublicVotes : Flag(0))
| (multiChoice ? Flag::MultiChoice : Flag(0))
| (quiz->checked() ? Flag::Quiz : Flag(0)));
return result;
};
const auto collectError = [=] {
if (isValidQuestion()) {
*error &= ~Error::Question;
} else {
*error |= Error::Question;
}
if (!options->hasOptions()) {
*error |= Error::Options;
} else if (!options->isValid()) {
*error |= Error::Other;
} else {
*error &= ~(Error::Options | Error::Other);
}
if (quiz->checked() && !options->hasCorrect()) {
*error |= Error::Correct;
} else {
*error &= ~Error::Correct;
}
};
const auto showError = [=](const QString &text) {
Ui::Toast::Show(text);
};
const auto send = [=](Api::SendOptions sendOptions) {
collectError();
if (*error & Error::Question) {
showError(tr::lng_polls_choose_question(tr::now));
question->setFocus();
} else if (*error & Error::Options) {
showError(tr::lng_polls_choose_answers(tr::now));
options->focusFirst();
} else if (*error & Error::Correct) {
showError(tr::lng_polls_choose_correct(tr::now));
} else if (!*error) {
_submitRequests.fire({ collectResult(), sendOptions });
}
};
const auto sendSilent = [=] {
auto options = Api::SendOptions();
options.silent = true;
send(options);
};
const auto sendScheduled = [=] {
Ui::show(
HistoryView::PrepareScheduleBox(
this,
SendMenuType::Scheduled,
send),
Ui::LayerOption::KeepOther);
};
options->scrollToWidget(
) | rpl::start_with_next([=](not_null<QWidget*> widget) {
scrollToWidget(widget);
}, lifetime());
options->backspaceInFront(
) | rpl::start_with_next([=] {
FocusAtEnd(question);
}, lifetime());
const auto submit = addButton(
tr::lng_polls_create_button(),
[=] { send({}); });
if (_sendType == Api::SendType::Normal) {
const auto sendMenuType = [=] {
collectError();
return *error ? SendMenuType::Disabled : SendMenuType::Scheduled;
};
SetupSendMenuAndShortcuts(
submit.data(),
sendMenuType,
sendSilent,
sendScheduled);
}
addButton(tr::lng_cancel(), [=] { closeBox(); });
return result;
}
void CreatePollBox::prepare() {
setTitle(tr::lng_polls_create_title());
const auto inner = setInnerWidget(setupContent());
setDimensionsToContent(st::boxWideWidth, inner);
}