/* 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) : RpWidget(parent) { _data.path = psDownloadPath(); _data.internalLinksDomain = Global::InternalLinksDomain(); setupContent(); } 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); _refreshButtons.fire({}); 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) { addOption( container, lng_export_option_info, Type::PersonalInfo | Type::Userpics); addOption(container, lng_export_option_contacts, Type::Contacts); addOption(container, lng_export_option_sessions, Type::Sessions); 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); } 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); _dataTypesChanges.events_starting_with_copy( _data.types ) | 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>( _data.format); formatGroup->setChangedCallback([=](Format format) { _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_text, Format::Text); addFormatOption(lng_export_option_json, Format::Json); } void SettingsWidget::addLocationLabel( not_null container) { auto pathLabel = _locationChanges.events_starting_with_copy( _data.path ) | rpl::map([](const QString &path) { const auto text = (path == psDownloadPath()) ? QString("Downloads/Telegram Desktop") : path; auto pathLink = TextWithEntities{ QDir::toNativeSeparators(text), EntitiesInText() }; pathLink.entities.push_back(EntityInText( EntityInTextCustomUrl, 0, text.size(), "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(); })); _refreshButtons.events( ) | rpl::start_with_next([=] { refreshButtons(buttons); 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), ((_data.types & types) == types), st::defaultBoxCheckbox), st::exportSettingPadding); base::ObservableViewer( checkbox->checkedChanged ) | rpl::start_with_next([=](bool checked) { if (checked) { _data.types |= types; } else { _data.types &= ~types; } _dataTypesChanges.fire_copy(_data.types); _refreshButtons.fire({}); }, lifetime()); return checkbox; } 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), ((_data.fullChats & types) != types), st::defaultBoxCheckbox), st::exportSubSettingPadding)); base::ObservableViewer( onlyMy->entity()->checkedChanged ) | rpl::start_with_next([=](bool checked) { 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), ((_data.media.types & type) == type), st::defaultBoxCheckbox), st::exportSettingPadding); base::ObservableViewer( checkbox->checkedChanged ) | rpl::start_with_next([=](bool checked) { if (checked) { _data.media.types |= type; } else { _data.media.types &= ~type; } _refreshButtons.fire({}); }, 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 (_data.media.sizeLimit <= SizeLimitByIndex(i)) { slider->setValue(i / float64(kSizeValueCount)); break; } } const auto label = Ui::CreateChild( container.get(), st::exportFileSizeLabel); const auto refreshSizeLimit = [=] { const auto limit = _data.media.sizeLimit / kMegabyte; const auto size = ((limit > 0) ? QString::number(limit) : QString::number(float64(_data.media.sizeLimit) / kMegabyte)) + " MB"; const auto text = lng_export_option_size_limit(lt_size, size); label->setText(text); }; slider->setAdjustCallback([=](float64 value) { return std::round(value * kSizeValueCount) / kSizeValueCount; }); slider->setChangeProgressCallback([=](float64 value) { const auto index = int(std::round(value * kSizeValueCount)); _data.media.sizeLimit = SizeLimitByIndex(index); refreshSizeLimit(); }); refreshSizeLimit(); 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) { container->hideChildren(); const auto children = container->children(); for (const auto child : children) { if (child->isWidgetType()) { child->deleteLater(); } } const auto start = _data.types ? Ui::CreateChild( container.get(), langFactory(lng_export_start), st::defaultBoxButton) : nullptr; if (start) { start->show(); start->addClickHandler([=] { _startClicks.fire(base::duplicate(_data)); }); 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 ready = [=](QString &&result) { _data.path = result; _locationChanges.fire(std::move(result)); }; FileDialog::GetFolder(this, lang(lng_export_folder), _data.path, ready); } rpl::producer SettingsWidget::startClicks() const { return _startClicks.events(); } rpl::producer<> SettingsWidget::cancelClicks() const { return _cancelClicks.value( ) | rpl::map([](Wrap &&wrap) { return std::move(wrap.value); }) | rpl::flatten_latest(); } } // namespace View } // namespace Export