/* 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 "settings/settings_chat.h" #include "settings/settings_common.h" #include "boxes/connection_box.h" #include "boxes/auto_download_box.h" #include "boxes/stickers_box.h" #include "boxes/background_box.h" #include "boxes/background_preview_box.h" #include "boxes/download_path_box.h" #include "boxes/local_storage_box.h" #include "boxes/edit_color_box.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" #include "ui/effects/radial_animation.h" #include "ui/toast/toast.h" #include "ui/image/image.h" #include "ui/image/image_source.h" #include "lang/lang_keys.h" #include "window/themes/window_theme_editor.h" #include "window/themes/window_theme.h" #include "window/themes/window_themes_embedded.h" #include "window/window_session_controller.h" #include "info/profile/info_profile_button.h" #include "storage/localstorage.h" #include "core/file_utilities.h" #include "core/application.h" #include "data/data_session.h" #include "chat_helpers/emoji_sets_manager.h" #include "platform/platform_info.h" #include "support/support_common.h" #include "support/support_templates.h" #include "main/main_session.h" #include "mainwidget.h" #include "styles/style_settings.h" #include "styles/style_boxes.h" namespace Settings { class BackgroundRow : public Ui::RpWidget { public: BackgroundRow( QWidget *parent, not_null controller); protected: void paintEvent(QPaintEvent *e) override; int resizeGetHeight(int newWidth) override; private: void updateImage(); float64 radialProgress() const; bool radialLoading() const; QRect radialRect() const; void radialStart(); crl::time radialTimeShift() const; void radialAnimationCallback(crl::time now); QPixmap _background; object_ptr _chooseFromGallery; object_ptr _chooseFromFile; Ui::RadialAnimation _radial; }; class DefaultTheme final : public Ui::AbstractCheckView { public: using Type = Window::Theme::EmbeddedType; using Scheme = Window::Theme::EmbeddedScheme; DefaultTheme(Scheme scheme, bool checked); QSize getSize() const override; void paint( Painter &p, int left, int top, int outerWidth) override; QImage prepareRippleMask() const override; bool checkRippleStartPosition(QPoint position) const override; void setColorizer(const Window::Theme::Colorizer *colorizer); private: void checkedChangedHook(anim::type animated) override; Scheme _scheme; Scheme _colorized; Ui::RadioView _radio; }; void ChooseFromFile( not_null<::Main::Session*> session, not_null parent); BackgroundRow::BackgroundRow( QWidget *parent, not_null controller) : RpWidget(parent) , _chooseFromGallery( this, tr::lng_settings_bg_from_gallery(tr::now), st::settingsLink) , _chooseFromFile(this, tr::lng_settings_bg_from_file(tr::now), st::settingsLink) , _radial([=](crl::time now) { radialAnimationCallback(now); }) { updateImage(); _chooseFromGallery->addClickHandler([=] { Ui::show(Box(&controller->session())); }); _chooseFromFile->addClickHandler([=] { ChooseFromFile(&controller->session(), this); }); using Update = const Window::Theme::BackgroundUpdate; base::ObservableViewer( *Window::Theme::Background() ) | rpl::filter([](const Update &update) { return (update.type == Update::Type::New || update.type == Update::Type::Start || update.type == Update::Type::Changed); }) | rpl::start_with_next([=] { updateImage(); }, lifetime()); } void BackgroundRow::paintEvent(QPaintEvent *e) { Painter p(this); const auto radial = _radial.animating(); const auto radialOpacity = radial ? _radial.opacity() : 0.; if (radial) { const auto backThumb = App::main()->newBackgroundThumb(); if (!backThumb) { p.drawPixmap(0, 0, _background); } else { const auto &pix = backThumb->pixBlurred( Data::FileOrigin(), st::settingsBackgroundThumb); const auto factor = cIntRetinaFactor(); p.drawPixmap( 0, 0, st::settingsBackgroundThumb, st::settingsBackgroundThumb, pix, 0, (pix.height() - st::settingsBackgroundThumb * factor) / 2, st::settingsBackgroundThumb * factor, st::settingsBackgroundThumb * factor); } const auto outer = radialRect(); const auto inner = QRect( QPoint( outer.x() + (outer.width() - st::radialSize.width()) / 2, outer.y() + (outer.height() - st::radialSize.height()) / 2), st::radialSize); p.setPen(Qt::NoPen); p.setOpacity(radialOpacity); p.setBrush(st::radialBg); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } p.setOpacity(1); const auto arc = inner.marginsRemoved(QMargins( st::radialLine, st::radialLine, st::radialLine, st::radialLine)); _radial.draw(p, arc, st::radialLine, st::radialFg); } else { p.drawPixmap(0, 0, _background); } } int BackgroundRow::resizeGetHeight(int newWidth) { auto linkTop = st::settingsFromGalleryTop; auto linkLeft = st::settingsBackgroundThumb + st::settingsThumbSkip; auto linkWidth = newWidth - linkLeft; _chooseFromGallery->resizeToWidth( qMin(linkWidth, _chooseFromGallery->naturalWidth())); _chooseFromFile->resizeToWidth( qMin(linkWidth, _chooseFromFile->naturalWidth())); _chooseFromGallery->moveToLeft(linkLeft, linkTop, newWidth); linkTop += _chooseFromGallery->height() + st::settingsFromFileTop; _chooseFromFile->moveToLeft(linkLeft, linkTop, newWidth); return st::settingsBackgroundThumb; } float64 BackgroundRow::radialProgress() const { return App::main()->chatBackgroundProgress(); } bool BackgroundRow::radialLoading() const { const auto main = App::main(); if (main->chatBackgroundLoading()) { main->checkChatBackground(); if (main->chatBackgroundLoading()) { return true; } else { const_cast(this)->updateImage(); } } return false; } QRect BackgroundRow::radialRect() const { return QRect( 0, 0, st::settingsBackgroundThumb, st::settingsBackgroundThumb); } void BackgroundRow::radialStart() { if (radialLoading() && !_radial.animating()) { _radial.start(radialProgress()); if (const auto shift = radialTimeShift()) { _radial.update( radialProgress(), !radialLoading(), crl::now() + shift); } } } crl::time BackgroundRow::radialTimeShift() const { return st::radialDuration; } void BackgroundRow::radialAnimationCallback(crl::time now) { const auto updated = _radial.update( radialProgress(), !radialLoading(), now + radialTimeShift()); if (!anim::Disabled() || updated) { rtlupdate(radialRect()); } } void BackgroundRow::updateImage() { int32 size = st::settingsBackgroundThumb * cIntRetinaFactor(); QImage back(size, size, QImage::Format_ARGB32_Premultiplied); back.setDevicePixelRatio(cRetinaFactor()); { Painter p(&back); PainterHighQualityEnabler hq(p); if (const auto color = Window::Theme::Background()->colorForFill()) { p.fillRect( 0, 0, st::settingsBackgroundThumb, st::settingsBackgroundThumb, *color); } else { const auto &pix = Window::Theme::Background()->pixmap(); const auto sx = (pix.width() > pix.height()) ? ((pix.width() - pix.height()) / 2) : 0; const auto sy = (pix.height() > pix.width()) ? ((pix.height() - pix.width()) / 2) : 0; const auto s = (pix.width() > pix.height()) ? pix.height() : pix.width(); p.drawPixmap( 0, 0, st::settingsBackgroundThumb, st::settingsBackgroundThumb, pix, sx, sy, s, s); } } Images::prepareRound(back, ImageRoundRadius::Small); _background = App::pixmapFromImageInPlace(std::move(back)); _background.setDevicePixelRatio(cRetinaFactor()); rtlupdate(radialRect()); if (radialLoading()) { radialStart(); } } DefaultTheme::DefaultTheme(Scheme scheme, bool checked) : AbstractCheckView(st::defaultRadio.duration, checked, nullptr) , _scheme(scheme) , _radio(st::defaultRadio, checked, [=] { update(); }) { setColorizer(nullptr); } void DefaultTheme::setColorizer(const Window::Theme::Colorizer *colorizer) { _colorized = _scheme; if (colorizer) { Window::Theme::Colorize(_colorized, colorizer); } _radio.setToggledOverride(_colorized.radiobuttonActive); _radio.setUntoggledOverride(_colorized.radiobuttonInactive); update(); } QSize DefaultTheme::getSize() const { return st::settingsThemePreviewSize; } void DefaultTheme::paint( Painter &p, int left, int top, int outerWidth) { const auto received = QRect( st::settingsThemeBubblePosition, st::settingsThemeBubbleSize); const auto sent = QRect( outerWidth - received.width() - st::settingsThemeBubblePosition.x(), received.y() + received.height() + st::settingsThemeBubbleSkip, received.width(), received.height()); const auto radius = st::settingsThemeBubbleRadius; p.fillRect( QRect(QPoint(), st::settingsThemePreviewSize), _colorized.background); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(_colorized.received); p.drawRoundedRect(rtlrect(received, outerWidth), radius, radius); p.setBrush(_colorized.sent); p.drawRoundedRect(rtlrect(sent, outerWidth), radius, radius); const auto radio = _radio.getSize(); _radio.paint( p, (outerWidth - radio.width()) / 2, getSize().height() - radio.height() - st::settingsThemeRadioBottom, outerWidth); } QImage DefaultTheme::prepareRippleMask() const { return QImage(); } bool DefaultTheme::checkRippleStartPosition(QPoint position) const { return false; } void DefaultTheme::checkedChangedHook(anim::type animated) { _radio.setChecked(checked(), animated); } void ChooseFromFile( not_null<::Main::Session*> session, not_null parent) { const auto &imgExtensions = cImgExtensions(); auto filters = QStringList( qsl("Theme files (*.tdesktop-theme *.tdesktop-palette *") + imgExtensions.join(qsl(" *")) + qsl(")")); filters.push_back(FileDialog::AllFilesFilter()); const auto callback = crl::guard(session, [=]( const FileDialog::OpenResult &result) { if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { return; } if (!result.paths.isEmpty()) { const auto filePath = result.paths.front(); const auto hasExtension = [&](QLatin1String extension) { return filePath.endsWith(extension, Qt::CaseInsensitive); }; if (hasExtension(qstr(".tdesktop-theme")) || hasExtension(qstr(".tdesktop-palette"))) { Window::Theme::Apply(filePath); return; } } auto image = result.remoteContent.isEmpty() ? App::readImage(result.paths.front()) : App::readImage(result.remoteContent); if (image.isNull() || image.width() <= 0 || image.height() <= 0) { return; } auto local = Data::CustomWallPaper(); local.setLocalImageAsThumbnail(std::make_shared( std::make_unique( std::move(image), "JPG"))); Ui::show(Box(session, local)); }); FileDialog::GetOpenPath( parent.get(), tr::lng_choose_image(tr::now), filters.join(qsl(";;")), crl::guard(parent, callback)); } QString DownloadPathText() { if (Global::DownloadPath().isEmpty()) { return tr::lng_download_path_default(tr::now); } else if (Global::DownloadPath() == qsl("tmp")) { return tr::lng_download_path_temp(tr::now); } return QDir::toNativeSeparators(Global::DownloadPath()); } void SetupStickersEmoji( not_null controller, not_null container) { AddDivider(container); AddSkip(container); AddSubsectionTitle(container, tr::lng_settings_stickers_emoji()); const auto session = &controller->session(); auto wrap = object_ptr(container); const auto inner = wrap.data(); container->add(object_ptr( container, std::move(wrap), QMargins(0, 0, 0, st::settingsCheckbox.margin.bottom()))); const auto checkbox = [&](const QString &label, bool checked) { return object_ptr( container, label, checked, st::settingsCheckbox); }; const auto add = [&](const QString &label, bool checked, auto &&handle) { inner->add( checkbox(label, checked), st::settingsCheckboxPadding )->checkedChanges( ) | rpl::start_with_next( std::move(handle), inner->lifetime()); }; add( tr::lng_settings_large_emoji(tr::now), session->settings().largeEmoji(), [=](bool checked) { session->settings().setLargeEmoji(checked); session->saveSettingsDelayed(); }); add( tr::lng_settings_replace_emojis(tr::now), session->settings().replaceEmoji(), [=](bool checked) { session->settings().setReplaceEmoji(checked); session->saveSettingsDelayed(); }); add( tr::lng_settings_suggest_emoji(tr::now), session->settings().suggestEmoji(), [=](bool checked) { session->settings().setSuggestEmoji(checked); session->saveSettingsDelayed(); }); add( tr::lng_settings_suggest_by_emoji(tr::now), session->settings().suggestStickersByEmoji(), [=](bool checked) { session->settings().setSuggestStickersByEmoji(checked); session->saveSettingsDelayed(); }); add( tr::lng_settings_loop_stickers(tr::now), session->settings().loopAnimatedStickers(), [=](bool checked) { session->settings().setLoopAnimatedStickers(checked); session->saveSettingsDelayed(); }); AddButton( container, tr::lng_stickers_you_have(), st::settingsChatButton, &st::settingsIconStickers, st::settingsChatIconLeft )->addClickHandler([=] { Ui::show(Box(session, StickersBox::Section::Installed)); }); AddButton( container, tr::lng_emoji_manage_sets(), st::settingsChatButton, &st::settingsIconEmoji, st::settingsChatIconLeft )->addClickHandler([] { Ui::show(Box()); }); AddSkip(container, st::settingsCheckboxesSkip); } void SetupMessages( not_null controller, not_null container) { AddDivider(container); AddSkip(container); AddSubsectionTitle(container, tr::lng_settings_messages()); AddSkip(container, st::settingsSendTypeSkip); using SendByType = Ui::InputSubmitSettings; const auto skip = st::settingsSendTypeSkip; auto wrap = object_ptr(container); const auto inner = wrap.data(); container->add( object_ptr( container, std::move(wrap), QMargins(0, skip, 0, skip))); const auto group = std::make_shared>( controller->session().settings().sendSubmitWay()); const auto add = [&](SendByType value, const QString &text) { inner->add( object_ptr>( inner, group, value, text, st::settingsSendType), st::settingsSendTypePadding); }; const auto small = st::settingsSendTypePadding; const auto top = skip; add(SendByType::Enter, tr::lng_settings_send_enter(tr::now)); add( SendByType::CtrlEnter, (Platform::IsMac() ? tr::lng_settings_send_cmdenter(tr::now) : tr::lng_settings_send_ctrlenter(tr::now))); group->setChangedCallback([=](SendByType value) { controller->session().settings().setSendSubmitWay(value); if (App::main()) { App::main()->ctrlEnterSubmitUpdated(); } Local::writeUserSettings(); }); AddSkip(inner, st::settingsCheckboxesSkip); } void SetupExport( not_null controller, not_null container) { AddButton( container, tr::lng_settings_export_data(), st::settingsButton )->addClickHandler([=] { const auto session = &controller->session(); Ui::hideSettingsAndLayer(); App::CallDelayed( st::boxDuration, session, [=] { session->data().startExport(); }); }); } void SetupLocalStorage( not_null controller, not_null container) { AddButton( container, tr::lng_settings_manage_local_storage(), st::settingsButton )->addClickHandler([=] { LocalStorageBox::Show(&controller->session()); }); } void SetupDataStorage( not_null controller, not_null container) { using namespace rpl::mappers; AddDivider(container); AddSkip(container); AddSubsectionTitle(container, tr::lng_settings_data_storage()); const auto ask = AddButton( container, tr::lng_download_path_ask(), st::settingsButton )->toggleOn(rpl::single(Global::AskDownloadPath())); #ifndef OS_WIN_STORE const auto showpath = Ui::CreateChild>(ask); const auto path = container->add( object_ptr>( container, object_ptr