932 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			932 lines
		
	
	
	
		
			24 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 "settings/settings_chat.h"
 | |
| 
 | |
| #include "settings/settings_common.h"
 | |
| #include "boxes/connection_box.h"
 | |
| #include "boxes/stickers_box.h"
 | |
| #include "boxes/background_box.h"
 | |
| #include "boxes/download_path_box.h"
 | |
| #include "boxes/local_storage_box.h"
 | |
| #include "ui/wrap/vertical_layout.h"
 | |
| #include "ui/wrap/slide_wrap.h"
 | |
| #include "ui/widgets/checkbox.h"
 | |
| #include "ui/widgets/labels.h"
 | |
| #include "ui/effects/radial_animation.h"
 | |
| #include "lang/lang_keys.h"
 | |
| #include "window/themes/window_theme_editor.h"
 | |
| #include "window/themes/window_theme.h"
 | |
| #include "info/profile/info_profile_button.h"
 | |
| #include "storage/localstorage.h"
 | |
| #include "core/file_utilities.h"
 | |
| #include "data/data_session.h"
 | |
| #include "auth_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);
 | |
| 
 | |
| 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();
 | |
| 	TimeMs radialTimeShift() const;
 | |
| 	void step_radial(TimeMs ms, bool timer);
 | |
| 
 | |
| 	QPixmap _background;
 | |
| 	object_ptr<Ui::LinkButton> _chooseFromGallery;
 | |
| 	object_ptr<Ui::LinkButton> _chooseFromFile;
 | |
| 
 | |
| 	Ui::RadialAnimation _radial;
 | |
| 
 | |
| };
 | |
| 
 | |
| class DefaultTheme final : public Ui::AbstractCheckView {
 | |
| public:
 | |
| 	enum class Type {
 | |
| 		DayBlue,
 | |
| 		Default,
 | |
| 		Night,
 | |
| 		NightGreen,
 | |
| 	};
 | |
| 	struct Scheme {
 | |
| 		Type type = Type();
 | |
| 		QColor background;
 | |
| 		QColor sent;
 | |
| 		QColor received;
 | |
| 		QColor radiobuttonInactive;
 | |
| 		QColor radiobuttonActive;
 | |
| 		QString name;
 | |
| 		QString path;
 | |
| 	};
 | |
| 	DefaultTheme(Scheme scheme, bool checked);
 | |
| 
 | |
| 	QSize getSize() const override;
 | |
| 	void paint(
 | |
| 		Painter &p,
 | |
| 		int left,
 | |
| 		int top,
 | |
| 		int outerWidth,
 | |
| 		TimeMs ms) override;
 | |
| 	QImage prepareRippleMask() const override;
 | |
| 	bool checkRippleStartPosition(QPoint position) const override;
 | |
| 
 | |
| private:
 | |
| 	void checkedChangedHook(anim::type animated) override;
 | |
| 
 | |
| 	Scheme _scheme;
 | |
| 	Ui::RadioView _radio;
 | |
| 
 | |
| };
 | |
| 
 | |
| void ChooseFromFile(not_null<QWidget*> parent);
 | |
| 
 | |
| BackgroundRow::BackgroundRow(QWidget *parent) : RpWidget(parent)
 | |
| , _chooseFromGallery(
 | |
| 	this,
 | |
| 	lang(lng_settings_bg_from_gallery),
 | |
| 	st::settingsLink)
 | |
| , _chooseFromFile(this, lang(lng_settings_bg_from_file), st::settingsLink)
 | |
| , _radial(animation(this, &BackgroundRow::step_radial)) {
 | |
| 	updateImage();
 | |
| 
 | |
| 	_chooseFromGallery->addClickHandler([] {
 | |
| 		Ui::show(Box<BackgroundBox>());
 | |
| 	});
 | |
| 	_chooseFromFile->addClickHandler([=] {
 | |
| 		ChooseFromFile(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);
 | |
| 
 | |
| 	bool radial = false;
 | |
| 	float64 radialOpacity = 0;
 | |
| 	if (_radial.animating()) {
 | |
| 		_radial.step(getms());
 | |
| 		radial = _radial.animating();
 | |
| 		radialOpacity = _radial.opacity();
 | |
| 	}
 | |
| 	if (radial) {
 | |
| 		const auto backThumb = App::main()->newBackgroundThumb();
 | |
| 		if (backThumb->isNull()) {
 | |
| 			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<BackgroundRow*>(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(),
 | |
| 				getms() + shift);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| TimeMs BackgroundRow::radialTimeShift() const {
 | |
| 	return st::radialDuration;
 | |
| }
 | |
| 
 | |
| void BackgroundRow::step_radial(TimeMs ms, bool timer) {
 | |
| 	const auto updated = _radial.update(
 | |
| 		radialProgress(),
 | |
| 		!radialLoading(),
 | |
| 		ms + radialTimeShift());
 | |
| 	if (timer && _radial.animating() && (!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);
 | |
| 
 | |
| 		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(); }) {
 | |
| 	_radio.setToggledOverride(_scheme.radiobuttonActive);
 | |
| 	_radio.setUntoggledOverride(_scheme.radiobuttonInactive);
 | |
| }
 | |
| 
 | |
| QSize DefaultTheme::getSize() const {
 | |
| 	return st::settingsThemePreviewSize;
 | |
| }
 | |
| 
 | |
| void DefaultTheme::paint(
 | |
| 		Painter &p,
 | |
| 		int left,
 | |
| 		int top,
 | |
| 		int outerWidth,
 | |
| 		TimeMs ms) {
 | |
| 	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),
 | |
| 		_scheme.background);
 | |
| 
 | |
| 	PainterHighQualityEnabler hq(p);
 | |
| 	p.setPen(Qt::NoPen);
 | |
| 	p.setBrush(_scheme.received);
 | |
| 	p.drawRoundedRect(rtlrect(received, outerWidth), radius, radius);
 | |
| 	p.setBrush(_scheme.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,
 | |
| 		getms());
 | |
| }
 | |
| 
 | |
| 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<QWidget*> 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 = [=](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;
 | |
| 		} else if (image.width() > 4096 * image.height()) {
 | |
| 			image = image.copy(
 | |
| 				(image.width() - 4096 * image.height()) / 2,
 | |
| 				0,
 | |
| 				4096 * image.height(),
 | |
| 				image.height());
 | |
| 		} else if (image.height() > 4096 * image.width()) {
 | |
| 			image = image.copy(
 | |
| 				0,
 | |
| 				(image.height() - 4096 * image.width()) / 2,
 | |
| 				image.width(),
 | |
| 				4096 * image.width());
 | |
| 		}
 | |
| 
 | |
| 		Window::Theme::Background()->setImage(
 | |
| 			Window::Theme::kCustomBackground,
 | |
| 			std::move(image));
 | |
| 		Window::Theme::Background()->setTile(false);
 | |
| 	};
 | |
| 	FileDialog::GetOpenPath(
 | |
| 		parent.get(),
 | |
| 		lang(lng_choose_image),
 | |
| 		filters.join(qsl(";;")),
 | |
| 		crl::guard(parent, callback));
 | |
| 
 | |
| }
 | |
| 
 | |
| QString DownloadPathText() {
 | |
| 	if (Global::DownloadPath().isEmpty()) {
 | |
| 		return lang(lng_download_path_default);
 | |
| 	} else if (Global::DownloadPath() == qsl("tmp")) {
 | |
| 		return lang(lng_download_path_temp);
 | |
| 	}
 | |
| 	return QDir::toNativeSeparators(Global::DownloadPath());
 | |
| }
 | |
| 
 | |
| void SetupStickersEmoji(not_null<Ui::VerticalLayout*> container) {
 | |
| 	AddDivider(container);
 | |
| 	AddSkip(container);
 | |
| 
 | |
| 	AddSubsectionTitle(container, lng_settings_stickers_emoji);
 | |
| 
 | |
| 	auto wrap = object_ptr<Ui::VerticalLayout>(container);
 | |
| 	const auto inner = wrap.data();
 | |
| 	container->add(object_ptr<Ui::OverrideMargins>(
 | |
| 		container,
 | |
| 		std::move(wrap),
 | |
| 		QMargins(0, 0, 0, st::settingsCheckbox.margin.bottom())));
 | |
| 
 | |
| 	const auto checkbox = [&](LangKey label, bool checked) {
 | |
| 		return object_ptr<Ui::Checkbox>(
 | |
| 			container,
 | |
| 			lang(label),
 | |
| 			checked,
 | |
| 			st::settingsCheckbox);
 | |
| 	};
 | |
| 	const auto add = [&](LangKey label, bool checked, auto &&handle) {
 | |
| 		base::ObservableViewer(
 | |
| 			inner->add(
 | |
| 				checkbox(label, checked),
 | |
| 				st::settingsCheckboxPadding
 | |
| 			)->checkedChanged
 | |
| 		) | rpl::start_with_next(
 | |
| 			std::move(handle),
 | |
| 			inner->lifetime());
 | |
| 	};
 | |
| 	add(
 | |
| 		lng_settings_replace_emojis,
 | |
| 		Global::ReplaceEmoji(),
 | |
| 		[](bool checked) {
 | |
| 			Global::SetReplaceEmoji(checked);
 | |
| 			Global::RefReplaceEmojiChanged().notify();
 | |
| 			Local::writeUserSettings();
 | |
| 		});
 | |
| 
 | |
| 	add(
 | |
| 		lng_settings_suggest_emoji,
 | |
| 		Global::SuggestEmoji(),
 | |
| 		[](bool checked) {
 | |
| 			Global::SetSuggestEmoji(checked);
 | |
| 			Local::writeUserSettings();
 | |
| 		});
 | |
| 
 | |
| 	add(
 | |
| 		lng_settings_suggest_by_emoji,
 | |
| 		Global::SuggestStickersByEmoji(),
 | |
| 		[](bool checked) {
 | |
| 			Global::SetSuggestStickersByEmoji(checked);
 | |
| 			Local::writeUserSettings();
 | |
| 		});
 | |
| 
 | |
| 	AddButton(
 | |
| 		container,
 | |
| 		lng_stickers_you_have,
 | |
| 		st::settingsChatButton,
 | |
| 		&st::settingsIconStickers,
 | |
| 		st::settingsChatIconLeft
 | |
| 	)->addClickHandler([] {
 | |
| 		Ui::show(Box<StickersBox>(StickersBox::Section::Installed));
 | |
| 	});
 | |
| 
 | |
| 	AddSkip(container, st::settingsCheckboxesSkip);
 | |
| }
 | |
| 
 | |
| void SetupMessages(not_null<Ui::VerticalLayout*> container) {
 | |
| 	AddDivider(container);
 | |
| 	AddSkip(container);
 | |
| 
 | |
| 	AddSubsectionTitle(container, lng_settings_messages);
 | |
| 
 | |
| 	AddSkip(container, st::settingsSendTypeSkip);
 | |
| 
 | |
| 	enum class SendByType {
 | |
| 		Enter,
 | |
| 		CtrlEnter,
 | |
| 	};
 | |
| 
 | |
| 	const auto skip = st::settingsSendTypeSkip;
 | |
| 	const auto group = std::make_shared<Ui::RadioenumGroup<SendByType>>(
 | |
| 		cCtrlEnter() ? SendByType::CtrlEnter : SendByType::Enter);
 | |
| 	auto wrap = object_ptr<Ui::VerticalLayout>(container);
 | |
| 	const auto inner = wrap.data();
 | |
| 	container->add(
 | |
| 		object_ptr<Ui::OverrideMargins>(
 | |
| 			container,
 | |
| 			std::move(wrap),
 | |
| 			QMargins(0, skip, 0, skip)));
 | |
| 
 | |
| 	const auto add = [&](
 | |
| 		SendByType value,
 | |
| 		LangKey key,
 | |
| 		style::margins padding) {
 | |
| 		inner->add(
 | |
| 			object_ptr<Ui::Radioenum<SendByType>>(
 | |
| 				inner,
 | |
| 				group,
 | |
| 				value,
 | |
| 				lang(key),
 | |
| 				st::settingsSendType),
 | |
| 			st::settingsSendTypePadding);
 | |
| 	};
 | |
| 	const auto small = st::settingsSendTypePadding;
 | |
| 	const auto top = skip;
 | |
| 	add(
 | |
| 		SendByType::Enter,
 | |
| 		lng_settings_send_enter,
 | |
| 		{ small.left(), small.top() + top, small.right(), small.bottom() });
 | |
| 	add(
 | |
| 		SendByType::CtrlEnter,
 | |
| 		((cPlatform() == dbipMac || cPlatform() == dbipMacOld)
 | |
| 			? lng_settings_send_cmdenter
 | |
| 			: lng_settings_send_ctrlenter),
 | |
| 		{ small.left(), small.top(), small.right(), small.bottom() + top });
 | |
| 	group->setChangedCallback([](SendByType value) {
 | |
| 		cSetCtrlEnter(value == SendByType::CtrlEnter);
 | |
| 		if (App::main()) {
 | |
| 			App::main()->ctrlEnterSubmitUpdated();
 | |
| 		}
 | |
| 		Local::writeUserSettings();
 | |
| 	});
 | |
| 
 | |
| 	AddSkip(inner, st::settingsCheckboxesSkip);
 | |
| }
 | |
| 
 | |
| void SetupExport(not_null<Ui::VerticalLayout*> container) {
 | |
| 	AddButton(
 | |
| 		container,
 | |
| 		lng_settings_export_data,
 | |
| 		st::settingsButton
 | |
| 	)->addClickHandler([] {
 | |
| 		Ui::hideSettingsAndLayer();
 | |
| 		App::CallDelayed(
 | |
| 			st::boxDuration,
 | |
| 			&Auth(),
 | |
| 			[] { Auth().data().startExport(); });
 | |
| 	});
 | |
| }
 | |
| 
 | |
| void SetupLocalStorage(not_null<Ui::VerticalLayout*> container) {
 | |
| 	AddButton(
 | |
| 		container,
 | |
| 		lng_settings_manage_local_storage,
 | |
| 		st::settingsButton
 | |
| 	)->addClickHandler([] {
 | |
| 		LocalStorageBox::Show(&Auth().data().cache());
 | |
| 	});
 | |
| }
 | |
| 
 | |
| void SetupDataStorage(not_null<Ui::VerticalLayout*> container) {
 | |
| 	using namespace rpl::mappers;
 | |
| 
 | |
| 	AddDivider(container);
 | |
| 	AddSkip(container);
 | |
| 
 | |
| 	AddSubsectionTitle(container, lng_settings_data_storage);
 | |
| 
 | |
| 	const auto ask = AddButton(
 | |
| 		container,
 | |
| 		lng_download_path_ask,
 | |
| 		st::settingsButton
 | |
| 	)->toggleOn(rpl::single(Global::AskDownloadPath()));
 | |
| 
 | |
| #ifndef OS_WIN_STORE
 | |
| 	const auto showpath = Ui::AttachAsChild(
 | |
| 		ask,
 | |
| 		rpl::event_stream<bool>());
 | |
| 	const auto path = container->add(
 | |
| 		object_ptr<Ui::SlideWrap<Button>>(
 | |
| 			container,
 | |
| 			object_ptr<Button>(
 | |
| 				container,
 | |
| 				Lang::Viewer(lng_download_path),
 | |
| 				st::settingsButton)));
 | |
| 	auto pathtext = rpl::single(
 | |
| 		rpl::empty_value()
 | |
| 	) | rpl::then(base::ObservableViewer(
 | |
| 		Global::RefDownloadPathChanged()
 | |
| 	)) | rpl::map([] {
 | |
| 		return DownloadPathText();
 | |
| 	});
 | |
| 	CreateRightLabel(
 | |
| 		path->entity(),
 | |
| 		std::move(pathtext),
 | |
| 		st::settingsButton,
 | |
| 		lng_download_path);
 | |
| 	path->entity()->addClickHandler([] {
 | |
| 		Ui::show(Box<DownloadPathBox>());
 | |
| 	});
 | |
| 	path->toggleOn(ask->toggledValue() | rpl::map(!_1));
 | |
| #endif // OS_WIN_STORE
 | |
| 
 | |
| 	ask->toggledValue(
 | |
| 	) | rpl::filter([](bool checked) {
 | |
| 		return (checked != Global::AskDownloadPath());
 | |
| 	}) | rpl::start_with_next([=](bool checked) {
 | |
| 		Global::SetAskDownloadPath(checked);
 | |
| 		Local::writeUserSettings();
 | |
| 
 | |
| #ifndef OS_WIN_STORE
 | |
| 		showpath->fire_copy(!checked);
 | |
| #endif // OS_WIN_STORE
 | |
| 
 | |
| 	}, ask->lifetime());
 | |
| 
 | |
| 	AddButton(
 | |
| 		container,
 | |
| 		lng_media_auto_settings,
 | |
| 		st::settingsButton
 | |
| 	)->addClickHandler([] {
 | |
| 		Ui::show(Box<AutoDownloadBox>());
 | |
| 	});
 | |
| 
 | |
| 	SetupLocalStorage(container);
 | |
| 	SetupExport(container);
 | |
| 
 | |
| 	AddSkip(container, st::settingsCheckboxesSkip);
 | |
| }
 | |
| 
 | |
| void SetupChatBackground(not_null<Ui::VerticalLayout*> container) {
 | |
| 	AddDivider(container);
 | |
| 	AddSkip(container);
 | |
| 
 | |
| 	AddSubsectionTitle(container, lng_settings_section_background);
 | |
| 
 | |
| 	container->add(
 | |
| 		object_ptr<BackgroundRow>(container),
 | |
| 		st::settingsBackgroundPadding);
 | |
| 
 | |
| 	const auto skipTop = st::settingsCheckbox.margin.top();
 | |
| 	const auto skipBottom = st::settingsCheckbox.margin.bottom();
 | |
| 	auto wrap = object_ptr<Ui::VerticalLayout>(container);
 | |
| 	const auto inner = wrap.data();
 | |
| 	container->add(
 | |
| 		object_ptr<Ui::OverrideMargins>(
 | |
| 			container,
 | |
| 			std::move(wrap),
 | |
| 			QMargins(0, skipTop, 0, skipBottom)));
 | |
| 
 | |
| 	AddSkip(container, st::settingsTileSkip);
 | |
| 
 | |
| 	const auto tile = inner->add(
 | |
| 		object_ptr<Ui::Checkbox>(
 | |
| 			inner,
 | |
| 			lang(lng_settings_bg_tile),
 | |
| 			Window::Theme::Background()->tile(),
 | |
| 			st::settingsCheckbox),
 | |
| 		st::settingsSendTypePadding);
 | |
| 	const auto adaptive = inner->add(
 | |
| 		object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
 | |
| 			inner,
 | |
| 			object_ptr<Ui::Checkbox>(
 | |
| 				inner,
 | |
| 				lang(lng_settings_adaptive_wide),
 | |
| 				Global::AdaptiveForWide(),
 | |
| 				st::settingsCheckbox),
 | |
| 			st::settingsSendTypePadding));
 | |
| 
 | |
| 	base::ObservableViewer(
 | |
| 		tile->checkedChanged
 | |
| 	) | rpl::start_with_next([](bool checked) {
 | |
| 		Window::Theme::Background()->setTile(checked);
 | |
| 	}, tile->lifetime());
 | |
| 
 | |
| 	using Update = const Window::Theme::BackgroundUpdate;
 | |
| 	base::ObservableViewer(
 | |
| 		*Window::Theme::Background()
 | |
| 	) | rpl::filter([](const Update &update) {
 | |
| 		return (update.type == Update::Type::Changed);
 | |
| 	}) | rpl::map([] {
 | |
| 		return Window::Theme::Background()->tile();
 | |
| 	}) | rpl::start_with_next([=](bool tiled) {
 | |
| 		tile->setChecked(tiled);
 | |
| 	}, tile->lifetime());
 | |
| 
 | |
| 	adaptive->toggleOn(rpl::single(
 | |
| 		rpl::empty_value()
 | |
| 	) | rpl::then(base::ObservableViewer(
 | |
| 		Adaptive::Changed()
 | |
| 	)) | rpl::map([] {
 | |
| 		return (Global::AdaptiveChatLayout() == Adaptive::ChatLayout::Wide);
 | |
| 	}));
 | |
| 
 | |
| 	base::ObservableViewer(
 | |
| 		adaptive->entity()->checkedChanged
 | |
| 	) | rpl::start_with_next([](bool checked) {
 | |
| 		Global::SetAdaptiveForWide(checked);
 | |
| 		Adaptive::Changed().notify();
 | |
| 		Local::writeUserSettings();
 | |
| 	}, adaptive->lifetime());
 | |
| }
 | |
| 
 | |
| void SetupDefaultThemes(not_null<Ui::VerticalLayout*> container) {
 | |
| 	using Type = DefaultTheme::Type;
 | |
| 	using Scheme = DefaultTheme::Scheme;
 | |
| 	const auto block = container->add(object_ptr<Ui::FixedHeightWidget>(
 | |
| 		container));
 | |
| 	const auto scheme = DefaultTheme::Scheme();
 | |
| 	const auto color = [](str_const hex) {
 | |
| 		Expects(hex.size() == 6);
 | |
| 
 | |
| 		const auto component = [](char a, char b) {
 | |
| 			const auto convert = [](char ch) {
 | |
| 				Expects((ch >= '0' && ch <= '9')
 | |
| 					|| (ch >= 'A' && ch <= 'F')
 | |
| 					|| (ch >= 'a' && ch <= 'f'));
 | |
| 
 | |
| 				return (ch >= '0' && ch <= '9')
 | |
| 					? int(ch - '0')
 | |
| 					: int(ch - ((ch >= 'A' && ch <= 'F') ? 'A' : 'a') + 10);
 | |
| 			};
 | |
| 			return convert(a) * 16 + convert(b);
 | |
| 		};
 | |
| 
 | |
| 		return QColor(
 | |
| 			component(hex[0], hex[1]),
 | |
| 			component(hex[2], hex[3]),
 | |
| 			component(hex[4], hex[5]));
 | |
| 	};
 | |
| 	static const auto schemes = {
 | |
| 		Scheme{
 | |
| 			Type::DayBlue,
 | |
| 			color("7ec4ea"),
 | |
| 			color("d7f0ff"),
 | |
| 			color("ffffff"),
 | |
| 			color("d7f0ff"),
 | |
| 			color("ffffff"),
 | |
| 			"Blue",
 | |
| 			":/gui/day-blue.tdesktop-theme"
 | |
| 		},
 | |
| 		Scheme{
 | |
| 			Type::Default,
 | |
| 			color("90ce89"),
 | |
| 			color("eaffdc"),
 | |
| 			color("ffffff"),
 | |
| 			color("eaffdc"),
 | |
| 			color("ffffff"),
 | |
| 			"Classic",
 | |
| 			QString()
 | |
| 		},
 | |
| 		Scheme{
 | |
| 			Type::Night,
 | |
| 			color("485761"),
 | |
| 			color("5ca7d4"),
 | |
| 			color("6b808d"),
 | |
| 			color("6b808d"),
 | |
| 			color("5ca7d4"),
 | |
| 			"Midnight",
 | |
| 			":/gui/night.tdesktop-theme"
 | |
| 		},
 | |
| 		Scheme{
 | |
| 			Type::NightGreen,
 | |
| 			color("485761"),
 | |
| 			color("74bf93"),
 | |
| 			color("6b808d"),
 | |
| 			color("6b808d"),
 | |
| 			color("74bf93"),
 | |
| 			"Matrix",
 | |
| 			":/gui/night-green.tdesktop-theme"
 | |
| 		},
 | |
| 	};
 | |
| 	const auto chosen = [&] {
 | |
| 		if (Window::Theme::IsNonDefaultBackground()) {
 | |
| 			return Type(-1);
 | |
| 		}
 | |
| 		const auto path = Window::Theme::Background()->themeAbsolutePath();
 | |
| 		for (const auto scheme : schemes) {
 | |
| 			if (path == scheme.path) {
 | |
| 				return scheme.type;
 | |
| 			}
 | |
| 		}
 | |
| 		return Type(-1);
 | |
| 	};
 | |
| 	const auto group = std::make_shared<Ui::RadioenumGroup<Type>>(chosen());
 | |
| 	auto buttons = ranges::view::all(
 | |
| 		schemes
 | |
| 	) | ranges::view::transform([&](const Scheme &scheme) {
 | |
| 		auto check = std::make_unique<DefaultTheme>(scheme, false);
 | |
| 		const auto weak = check.get();
 | |
| 		const auto result = Ui::CreateChild<Ui::Radioenum<Type>>(
 | |
| 			block,
 | |
| 			group,
 | |
| 			scheme.type,
 | |
| 			scheme.name,
 | |
| 			st::settingsTheme,
 | |
| 			std::move(check));
 | |
| 		weak->setUpdateCallback([=] { result->update(); });
 | |
| 		return result;
 | |
| 	}) | ranges::to_vector;
 | |
| 
 | |
| 	using Update = const Window::Theme::BackgroundUpdate;
 | |
| 	const auto apply = [=](const Scheme &scheme) {
 | |
| 		const auto isNight = [](const Scheme &scheme) {
 | |
| 			const auto type = scheme.type;
 | |
| 			return (type != Type::DayBlue) && (type != Type::Default);
 | |
| 		};
 | |
| 		const auto currentlyIsCustom = (chosen() == Type(-1));
 | |
| 		if (Window::Theme::IsNightMode() == isNight(scheme)) {
 | |
| 			Window::Theme::ApplyDefaultWithPath(scheme.path);
 | |
| 		} else {
 | |
| 			Window::Theme::ToggleNightMode(scheme.path);
 | |
| 		}
 | |
| 		if (!currentlyIsCustom) {
 | |
| 			Window::Theme::KeepApplied();
 | |
| 		}
 | |
| 	};
 | |
| 	group->setChangedCallback([=](Type type) {
 | |
| 		const auto i = ranges::find_if(schemes, [&](const Scheme &scheme) {
 | |
| 			return (type == scheme.type && type != chosen());
 | |
| 		});
 | |
| 		if (i != end(schemes)) {
 | |
| 			apply(*i);
 | |
| 		}
 | |
| 	});
 | |
| 	base::ObservableViewer(
 | |
| 		*Window::Theme::Background()
 | |
| 	) | rpl::filter([](const Update &update) {
 | |
| 		return (update.type == Update::Type::ApplyingTheme
 | |
| 			|| update.type == Update::Type::New);
 | |
| 	}) | rpl::map([=] {
 | |
| 		return chosen();
 | |
| 	}) | rpl::start_with_next([=](Type type) {
 | |
| 		group->setValue(type);
 | |
| 	}, container->lifetime());
 | |
| 
 | |
| 	for (const auto button : buttons) {
 | |
| 		button->setCheckAlignment(style::al_top);
 | |
| 		button->resizeToWidth(button->width());
 | |
| 	}
 | |
| 	block->resize(block->width(), buttons[0]->height());
 | |
| 	block->widthValue(
 | |
| 	) | rpl::start_with_next([buttons = std::move(buttons)](int width) {
 | |
| 		Expects(!buttons.empty());
 | |
| 
 | |
| 		//     |------|      |---------|        |-------|      |-------|
 | |
| 		// pad | blue | skip | classic | 3*skip | night | skip | night | pad
 | |
| 		//     |------|      |---------|        |-------|      |-------|
 | |
| 		const auto padding = st::settingsButton.padding;
 | |
| 		width -= padding.left() + padding.right();
 | |
| 		const auto desired = st::settingsThemePreviewSize.width();
 | |
| 		const auto count = int(buttons.size());
 | |
| 		const auto smallSkips = (count / 2);
 | |
| 		const auto bigSkips = ((count - 1) / 2);
 | |
| 		const auto skipRatio = 3;
 | |
| 		const auto skipSegments = smallSkips + bigSkips * skipRatio;
 | |
| 		const auto minSkip = st::settingsThemeMinSkip;
 | |
| 		const auto single = [&] {
 | |
| 			if (width >= skipSegments * minSkip + count * desired) {
 | |
| 				return desired;
 | |
| 			}
 | |
| 			return (width - skipSegments * minSkip) / count;
 | |
| 		}();
 | |
| 		if (single <= 0) {
 | |
| 			return;
 | |
| 		}
 | |
| 		const auto fullSkips = width - count * single;
 | |
| 		const auto segment = fullSkips / float64(skipSegments);
 | |
| 		const auto smallSkip = segment;
 | |
| 		const auto bigSkip = segment * skipRatio;
 | |
| 		auto left = padding.left() + 0.;
 | |
| 		auto index = 0;
 | |
| 		for (const auto button : buttons) {
 | |
| 			button->resizeToWidth(single);
 | |
| 			button->moveToLeft(int(std::round(left)), 0);
 | |
| 			left += button->width() + ((index++ % 2) ? bigSkip : smallSkip);
 | |
| 		}
 | |
| 	}, block->lifetime());
 | |
| 
 | |
| 	AddSkip(container);
 | |
| }
 | |
| 
 | |
| void SetupThemeOptions(not_null<Ui::VerticalLayout*> container) {
 | |
| 	AddSkip(container, st::settingsPrivacySkip);
 | |
| 
 | |
| 	AddSubsectionTitle(container, lng_settings_themes);
 | |
| 
 | |
| 	AddSkip(container, st::settingsThemesTopSkip);
 | |
| 	SetupDefaultThemes(container);
 | |
| 	AddSkip(container, st::settingsThemesBottomSkip);
 | |
| 
 | |
| 	AddButton(
 | |
| 		container,
 | |
| 		lng_settings_bg_edit_theme,
 | |
| 		st::settingsChatButton,
 | |
| 		&st::settingsIconThemes,
 | |
| 		st::settingsChatIconLeft
 | |
| 	)->addClickHandler(App::LambdaDelayed(
 | |
| 		st::settingsChatButton.ripple.hideDuration,
 | |
| 		container,
 | |
| 		[] { Window::Theme::Editor::Start(); }));
 | |
| 
 | |
| 	AddSkip(container);
 | |
| }
 | |
| 
 | |
| Chat::Chat(QWidget *parent, not_null<UserData*> self)
 | |
| : Section(parent)
 | |
| , _self(self) {
 | |
| 	setupContent();
 | |
| }
 | |
| 
 | |
| void Chat::setupContent() {
 | |
| 	const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
 | |
| 
 | |
| 	SetupThemeOptions(content);
 | |
| 	SetupChatBackground(content);
 | |
| 	SetupStickersEmoji(content);
 | |
| 	SetupMessages(content);
 | |
| 
 | |
| 	Ui::ResizeFitChild(this, content);
 | |
| }
 | |
| 
 | |
| } // namespace Settings
 | 
