/* 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 "export/view/export_view_settings.h" #include "export/output/export_output_abstract.h" #include "lang/lang_keys.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/continuous_sliders.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/fade_wrap.h" #include "platform/platform_specific.h" #include "core/file_utilities.h" #include "styles/style_widgets.h" #include "styles/style_export.h" #include "styles/style_boxes.h" namespace Export { namespace View { namespace { constexpr auto kSizeValueCount = 80; constexpr auto kMegabyte = 1024 * 1024; int SizeLimitByIndex(int index) { Expects(index >= 0 && index <= kSizeValueCount); const auto megabytes = [&] { if (index <= 10) { return index; } else if (index <= 30) { return 10 + (index - 10) * 2; } else if (index <= 40) { return 50 + (index - 30) * 5; } else if (index <= 60) { return 100 + (index - 40) * 10; } else if (index <= 70) { return 300 + (index - 60) * 20; } else { return 500 + (index - 70) * 100; } }; if (!index) { return kMegabyte / 2; } return megabytes() * kMegabyte; } } // namespace SettingsWidget::SettingsWidget(QWidget *parent, Settings data) : RpWidget(parent) , _internal_data(std::move(data)) { setupContent(); } const Settings &SettingsWidget::readData() const { return _internal_data; } template void SettingsWidget::changeData(Callback &&callback) { callback(_internal_data); _changes.fire_copy(_internal_data); } void SettingsWidget::setupContent() { const auto scroll = Ui::CreateChild( this, st::boxLayerScroll); const auto wrap = scroll->setOwnedWidget(object_ptr( scroll, object_ptr(scroll))); const auto content = static_cast(wrap->entity()); const auto buttons = setupButtons(scroll, wrap); setupOptions(content); setupPathAndFormat(content); sizeValue( ) | rpl::start_with_next([=](QSize size) { scroll->resize(size.width(), size.height() - buttons->height()); wrap->resizeToWidth(size.width()); content->resizeToWidth(size.width()); }, lifetime()); } void SettingsWidget::setupOptions(not_null container) { addOptionWithAbout( container, lng_export_option_info, Type::PersonalInfo | Type::Userpics, lng_export_option_info_about); addOptionWithAbout( container, lng_export_option_contacts, Type::Contacts, lng_export_option_contacts_about); addHeader(container, lng_export_header_chats); addOption( container, lng_export_option_personal_chats, Type::PersonalChats); addOption(container, lng_export_option_bot_chats, Type::BotChats); addChatOption( container, lng_export_option_private_groups, Type::PrivateGroups); addChatOption( container, lng_export_option_private_channels, Type::PrivateChannels); addChatOption( container, lng_export_option_public_groups, Type::PublicGroups); addChatOption( container, lng_export_option_public_channels, Type::PublicChannels); setupMediaOptions(container); addHeader(container, lng_export_header_other); addOptionWithAbout( container, lng_export_option_sessions, Type::Sessions, lng_export_option_sessions_about); addOptionWithAbout( container, lng_export_option_other, Type::OtherData, lng_export_option_other_about); } void SettingsWidget::setupMediaOptions( not_null container) { const auto mediaWrap = container->add( object_ptr>( container, object_ptr(container))); const auto media = mediaWrap->entity(); addHeader(media, lng_export_header_media); addMediaOption(media, lng_export_option_photos, MediaType::Photo); addMediaOption(media, lng_export_option_video_files, MediaType::Video); addMediaOption(media, lng_export_option_voice_messages, MediaType::VoiceMessage); addMediaOption(media, lng_export_option_video_messages, MediaType::VideoMessage); addMediaOption(media, lng_export_option_stickers, MediaType::Sticker); addMediaOption(media, lng_export_option_gifs, MediaType::GIF); addMediaOption(media, lng_export_option_files, MediaType::File); addSizeSlider(media); value() | rpl::map([](const Settings &data) { return data.types; }) | rpl::distinct_until_changed( ) | rpl::start_with_next([=](Settings::Types types) { mediaWrap->toggle((types & (Type::PersonalChats | Type::BotChats | Type::PrivateGroups | Type::PrivateChannels | Type::PublicGroups | Type::PublicChannels)) != 0, anim::type::normal); }, mediaWrap->lifetime()); widthValue( ) | rpl::start_with_next([=](int width) { mediaWrap->resizeToWidth(width); }, mediaWrap->lifetime()); } void SettingsWidget::setupPathAndFormat( not_null container) { const auto formatGroup = std::make_shared>( readData().format); formatGroup->setChangedCallback([=](Format format) { changeData([&](Settings &data) { data.format = format; }); }); const auto addFormatOption = [&](LangKey key, Format format) { const auto radio = container->add( object_ptr>( container, formatGroup, format, lang(key), st::defaultBoxCheckbox), st::exportSettingPadding); }; addHeader(container, lng_export_header_format); addLocationLabel(container); addFormatOption(lng_export_option_html, Format::Html); addFormatOption(lng_export_option_json, Format::Json); } void SettingsWidget::addLocationLabel( not_null container) { auto pathLabel = value() | rpl::map([](const Settings &data) { return data.path; }) | rpl::distinct_until_changed( ) | rpl::map([](const QString &path) { const auto check = [](const QString &value) { const auto result = value.endsWith('/') ? value.mid(0, value.size() - 1) : value; return (cPlatform() == dbipWindows) ? result.toLower() : result; }; const auto text = (check(path) == check(psDownloadPath())) ? QString("Downloads/Telegram Desktop") : path; auto pathLink = TextWithEntities{ QDir::toNativeSeparators(text), EntitiesInText() }; pathLink.entities.push_back(EntityInText( EntityInTextCustomUrl, 0, text.size(), QString("internal:edit_export_path"))); return lng_export_option_location__generic( lt_path, pathLink); }); const auto label = container->add( object_ptr( container, std::move(pathLabel), st::exportLocationLabel), st::exportLocationPadding); label->setClickHandlerFilter([=](auto&&...) { chooseFolder(); return false; }); } not_null SettingsWidget::setupButtons( not_null scroll, not_null wrap) { using namespace rpl::mappers; const auto buttonsPadding = st::boxButtonPadding; const auto buttonsHeight = buttonsPadding.top() + st::defaultBoxButton.height + buttonsPadding.bottom(); const auto buttons = Ui::CreateChild( this, buttonsHeight); const auto topShadow = Ui::CreateChild(this); const auto bottomShadow = Ui::CreateChild(this); topShadow->toggleOn(scroll->scrollTopValue( ) | rpl::map(_1 > 0)); bottomShadow->toggleOn(rpl::combine( scroll->heightValue(), scroll->scrollTopValue(), wrap->heightValue(), _2 ) | rpl::map([=](int top) { return top < scroll->scrollTopMax(); })); value() | rpl::map([](const Settings &data) { return data.types != Types(0); }) | rpl::distinct_until_changed( ) | rpl::start_with_next([=](bool canStart) { refreshButtons(buttons, canStart); topShadow->raise(); bottomShadow->raise(); }, buttons->lifetime()); sizeValue( ) | rpl::start_with_next([=](QSize size) { buttons->resizeToWidth(size.width()); buttons->moveToLeft(0, size.height() - buttons->height()); topShadow->resizeToWidth(size.width()); topShadow->moveToLeft(0, 0); bottomShadow->resizeToWidth(size.width()); bottomShadow->moveToLeft(0, buttons->y() - st::lineWidth); }, buttons->lifetime()); return buttons; } void SettingsWidget::addHeader( not_null container, LangKey key) { container->add( object_ptr( container, lang(key), Ui::FlatLabel::InitType::Simple, st::exportHeaderLabel), st::exportHeaderPadding); } not_null SettingsWidget::addOption( not_null container, LangKey key, Types types) { const auto checkbox = container->add( object_ptr( container, lang(key), ((readData().types & types) == types), st::defaultBoxCheckbox), st::exportSettingPadding); base::ObservableViewer( checkbox->checkedChanged ) | rpl::start_with_next([=](bool checked) { changeData([&](Settings &data) { if (checked) { data.types |= types; } else { data.types &= ~types; } }); }, lifetime()); return checkbox; } not_null SettingsWidget::addOptionWithAbout( not_null container, LangKey key, Types types, LangKey about) { const auto result = addOption(container, key, types); const auto label = container->add( object_ptr( container, lang(about), Ui::FlatLabel::InitType::Simple, st::exportAboutOptionLabel), st::exportAboutOptionPadding); return result; } void SettingsWidget::addChatOption( not_null container, LangKey key, Types types) { const auto checkbox = addOption(container, key, types); const auto onlyMy = container->add( object_ptr>( container, object_ptr( container, lang(lng_export_option_only_my), ((readData().fullChats & types) != types), st::defaultBoxCheckbox), st::exportSubSettingPadding)); base::ObservableViewer( onlyMy->entity()->checkedChanged ) | rpl::start_with_next([=](bool checked) { changeData([&](Settings &data) { if (checked) { data.fullChats &= ~types; } else { data.fullChats |= types; } }); }, checkbox->lifetime()); onlyMy->toggleOn(base::ObservableViewer( checkbox->checkedChanged )); onlyMy->toggle(checkbox->checked(), anim::type::instant); if (types & (Type::PublicGroups | Type::PublicChannels)) { onlyMy->entity()->setChecked(true); onlyMy->entity()->setDisabled(true); } } void SettingsWidget::addMediaOption( not_null container, LangKey key, MediaType type) { const auto checkbox = container->add( object_ptr( container, lang(key), ((readData().media.types & type) == type), st::defaultBoxCheckbox), st::exportSettingPadding); base::ObservableViewer( checkbox->checkedChanged ) | rpl::start_with_next([=](bool checked) { changeData([&](Settings &data) { if (checked) { data.media.types |= type; } else { data.media.types &= ~type; } }); }, lifetime()); } void SettingsWidget::addSizeSlider( not_null container) { using namespace rpl::mappers; const auto slider = container->add( object_ptr(container, st::exportFileSizeSlider), st::exportFileSizePadding); slider->resize(st::exportFileSizeSlider.seekSize); slider->setAlwaysDisplayMarker(true); slider->setDirection(Ui::ContinuousSlider::Direction::Horizontal); for (auto i = 0; i != kSizeValueCount + 1; ++i) { if (readData().media.sizeLimit <= SizeLimitByIndex(i)) { slider->setValue(i / float64(kSizeValueCount)); break; } } const auto label = Ui::CreateChild( container.get(), st::exportFileSizeLabel); slider->setAdjustCallback([=](float64 value) { return std::round(value * kSizeValueCount) / kSizeValueCount; }); slider->setChangeProgressCallback([=](float64 value) { const auto index = int(std::round(value * kSizeValueCount)); changeData([&](Settings &data) { data.media.sizeLimit = SizeLimitByIndex(index); }); }); value() | rpl::map([](const Settings &data) { return data.media.sizeLimit; }) | rpl::start_with_next([=](int sizeLimit) { const auto limit = sizeLimit / kMegabyte; const auto size = ((limit > 0) ? QString::number(limit) : QString::number(float64(sizeLimit) / kMegabyte)) + " MB"; const auto text = lng_export_option_size_limit(lt_size, size); label->setText(text); }, slider->lifetime()); rpl::combine( label->widthValue(), slider->geometryValue(), _2 ) | rpl::start_with_next([=](QRect geometry) { label->moveToRight( st::exportFileSizePadding.right(), geometry.y() - label->height() - st::exportFileSizeLabelBottom); }, label->lifetime()); } void SettingsWidget::refreshButtons( not_null container, bool canStart) { container->hideChildren(); const auto children = container->children(); for (const auto child : children) { if (child->isWidgetType()) { child->deleteLater(); } } const auto start = canStart ? Ui::CreateChild( container.get(), langFactory(lng_export_start), st::defaultBoxButton) : nullptr; if (start) { start->show(); _startClicks = start->clicks(); container->sizeValue( ) | rpl::start_with_next([=](QSize size) { const auto right = st::boxButtonPadding.right(); const auto top = st::boxButtonPadding.top(); start->moveToRight(right, top); }, start->lifetime()); } const auto cancel = Ui::CreateChild( container.get(), langFactory(lng_cancel), st::defaultBoxButton); cancel->show(); _cancelClicks = cancel->clicks(); rpl::combine( container->sizeValue(), start ? start->widthValue() : rpl::single(0) ) | rpl::start_with_next([=](QSize size, int width) { const auto right = st::boxButtonPadding.right() + (width ? width + st::boxButtonPadding.left() : 0); const auto top = st::boxButtonPadding.top(); cancel->moveToRight(right, top); }, cancel->lifetime()); } void SettingsWidget::chooseFolder() { const auto callback = [=](QString &&result) { changeData([&](Settings &data) { data.path = std::move(result); }); }; FileDialog::GetFolder( this, lang(lng_export_folder), readData().path, callback); } rpl::producer SettingsWidget::changes() const { return _changes.events(); } rpl::producer SettingsWidget::value() const { return rpl::single(readData()) | rpl::then(changes()); } rpl::producer<> SettingsWidget::startClicks() const { return _startClicks.value( ) | rpl::map([](Wrap &&wrap) { return std::move(wrap.value); }) | rpl::flatten_latest(); } rpl::producer<> SettingsWidget::cancelClicks() const { return _cancelClicks.value( ) | rpl::map([](Wrap &&wrap) { return std::move(wrap.value); }) | rpl::flatten_latest(); } } // namespace View } // namespace Export