383 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			383 lines
		
	
	
	
		
			10 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 "ui/boxes/choose_language_box.h"
 | |
| 
 | |
| #include "lang/lang_keys.h"
 | |
| #include "spellcheck/spellcheck_types.h"
 | |
| #include "ui/layers/generic_box.h"
 | |
| #include "ui/widgets/buttons.h"
 | |
| #include "ui/widgets/multi_select.h"
 | |
| #include "ui/wrap/slide_wrap.h"
 | |
| #include "ui/painter.h"
 | |
| #include "base/debug_log.h"
 | |
| #include "styles/style_info.h"
 | |
| #include "styles/style_layers.h"
 | |
| #include "styles/style_settings.h"
 | |
| 
 | |
| namespace Ui {
 | |
| namespace {
 | |
| 
 | |
| const auto kLanguageNamePrefix = "cloud_lng_language_";
 | |
| const auto kTranslateToPrefix = "cloud_lng_translate_to_";
 | |
| 
 | |
| [[nodiscard]] std::vector<LanguageId> TranslationLanguagesList() {
 | |
| 	// If adding some languages here you need to check that it is
 | |
| 	// supported on the server. Right now server supports those:
 | |
| 	//
 | |
| 	// 'af', 'sq', 'am', 'ar', 'hy', 'az', 'eu', 'be', 'bn', 'bs', 'bg',
 | |
| 	// 'ca', 'ceb', 'zh-CN', 'zh', 'zh-TW', 'co', 'hr', 'cs', 'da', 'nl',
 | |
| 	// 'en', 'eo', 'et', 'fi', 'fr', 'fy', 'gl', 'ka', 'de', 'el', 'gu',
 | |
| 	// 'ht', 'ha', 'haw', 'he', 'iw', 'hi', 'hmn', 'hu', 'is', 'ig', 'id',
 | |
| 	// 'ga', 'it', 'ja', 'jv', 'kn', 'kk', 'km', 'rw', 'ko', 'ku', 'ky',
 | |
| 	// 'lo', 'la', 'lv', 'lt', 'lb', 'mk', 'mg', 'ms', 'ml', 'mt', 'mi',
 | |
| 	// 'mr', 'mn', 'my', 'ne', 'no', 'ny', 'or', 'ps', 'fa', 'pl', 'pt',
 | |
| 	// 'pa', 'ro', 'ru', 'sm', 'gd', 'sr', 'st', 'sn', 'sd', 'si', 'sk',
 | |
| 	// 'sl', 'so', 'es', 'su', 'sw', 'sv', 'tl', 'tg', 'ta', 'tt', 'te',
 | |
| 	// 'th', 'tr', 'tk', 'uk', 'ur', 'ug', 'uz', 'vi', 'cy', 'xh', 'yi',
 | |
| 	// 'yo', 'zu',
 | |
| 	return {
 | |
| 		{ QLocale::English },
 | |
| 		{ QLocale::Arabic },
 | |
| 		{ QLocale::Belarusian },
 | |
| 		{ QLocale::Catalan },
 | |
| 		{ QLocale::Chinese },
 | |
| 		{ QLocale::Dutch },
 | |
| 		{ QLocale::French },
 | |
| 		{ QLocale::German },
 | |
| 		{ QLocale::Indonesian },
 | |
| 		{ QLocale::Italian },
 | |
| 		{ QLocale::Japanese },
 | |
| 		{ QLocale::Korean },
 | |
| 		{ QLocale::Polish },
 | |
| 		{ QLocale::Portuguese },
 | |
| 		{ QLocale::Russian },
 | |
| 		{ QLocale::Spanish },
 | |
| 		{ QLocale::Ukrainian },
 | |
| 
 | |
| 		{ QLocale::Afrikaans },
 | |
| 		{ QLocale::Albanian },
 | |
| 		{ QLocale::Amharic },
 | |
| 		{ QLocale::Armenian },
 | |
| 		{ QLocale::Azerbaijani },
 | |
| 		{ QLocale::Basque },
 | |
| 		{ QLocale::Bosnian },
 | |
| 		{ QLocale::Bulgarian },
 | |
| 		{ QLocale::Burmese },
 | |
| 		{ QLocale::Croatian },
 | |
| 		{ QLocale::Czech },
 | |
| 		{ QLocale::Danish },
 | |
| 		{ QLocale::Esperanto },
 | |
| 		{ QLocale::Estonian },
 | |
| 		{ QLocale::Finnish },
 | |
| 		{ QLocale::Gaelic },
 | |
| 		{ QLocale::Galician },
 | |
| 		{ QLocale::Georgian },
 | |
| 		{ QLocale::Greek },
 | |
| 		{ QLocale::Gusii },
 | |
| 		{ QLocale::Hausa },
 | |
| 		{ QLocale::Hebrew },
 | |
| 		{ QLocale::Hungarian },
 | |
| 		{ QLocale::Icelandic },
 | |
| 		{ QLocale::Igbo },
 | |
| 		{ QLocale::Irish },
 | |
| 		{ QLocale::Kazakh },
 | |
| 		{ QLocale::Kinyarwanda },
 | |
| 		{ QLocale::Kurdish },
 | |
| 		{ QLocale::Lao },
 | |
| 		{ QLocale::Latvian },
 | |
| 		{ QLocale::Lithuanian },
 | |
| 		{ QLocale::Luxembourgish },
 | |
| 		{ QLocale::Macedonian },
 | |
| 		{ QLocale::Malagasy },
 | |
| 		{ QLocale::Malay },
 | |
| 		{ QLocale::Maltese },
 | |
| 		{ QLocale::Maori },
 | |
| 		{ QLocale::Mongolian },
 | |
| 		{ QLocale::Nepali },
 | |
| 		{ QLocale::Pashto },
 | |
| 		{ QLocale::Persian },
 | |
| 		{ QLocale::Romanian },
 | |
| 		{ QLocale::Serbian },
 | |
| 		{ QLocale::Shona },
 | |
| 		{ QLocale::Sindhi },
 | |
| 		{ QLocale::Sinhala },
 | |
| 		{ QLocale::Slovak },
 | |
| 		{ QLocale::Slovenian },
 | |
| 		{ QLocale::Somali },
 | |
| 		{ QLocale::Sundanese },
 | |
| 		{ QLocale::Swahili },
 | |
| 		{ QLocale::Swedish },
 | |
| 		{ QLocale::Tajik },
 | |
| 		{ QLocale::Tatar },
 | |
| 		{ QLocale::Teso },
 | |
| 		{ QLocale::Thai },
 | |
| 		{ QLocale::Turkish },
 | |
| 		{ QLocale::Turkmen },
 | |
| 		{ QLocale::Urdu },
 | |
| 		{ QLocale::Uzbek },
 | |
| 		{ QLocale::Vietnamese },
 | |
| 		{ QLocale::Welsh },
 | |
| 		{ QLocale::WesternFrisian },
 | |
| 		{ QLocale::Xhosa },
 | |
| 		{ QLocale::Yiddish },
 | |
| 	};
 | |
| }
 | |
| 
 | |
| class Row final : public SettingsButton {
 | |
| public:
 | |
| 	Row(not_null<RpWidget*> parent, LanguageId id);
 | |
| 
 | |
| 	[[nodiscard]] bool filtered(const QString &query) const;
 | |
| 	[[nodiscard]] LanguageId id() const;
 | |
| 
 | |
| 	int resizeGetHeight(int newWidth) override;
 | |
| 
 | |
| protected:
 | |
| 	void paintEvent(QPaintEvent *e) override;
 | |
| 
 | |
| private:
 | |
| 	const style::PeerListItem &_st;
 | |
| 	const LanguageId _id;
 | |
| 	const QString _status;
 | |
| 	const QString _titleText;
 | |
| 	Text::String _title;
 | |
| 
 | |
| };
 | |
| 
 | |
| Row::Row(not_null<RpWidget*> parent, LanguageId id)
 | |
| : SettingsButton(parent, rpl::never<QString>())
 | |
| , _st(st::inviteLinkListItem)
 | |
| , _id(id)
 | |
| , _status(LanguageName(id))
 | |
| , _titleText(LanguageNameNative(id))
 | |
| , _title(_st.nameStyle, _titleText) {
 | |
| }
 | |
| 
 | |
| LanguageId Row::id() const {
 | |
| 	return _id;
 | |
| }
 | |
| 
 | |
| bool Row::filtered(const QString &query) const {
 | |
| 	return _status.startsWith(query, Qt::CaseInsensitive)
 | |
| 		|| _titleText.startsWith(query, Qt::CaseInsensitive);
 | |
| }
 | |
| 
 | |
| int Row::resizeGetHeight(int newWidth) {
 | |
| 	return _st.height;
 | |
| }
 | |
| 
 | |
| void Row::paintEvent(QPaintEvent *e) {
 | |
| 	auto p = Painter(this);
 | |
| 
 | |
| 	const auto paintOver = (isOver() || isDown()) && !isDisabled();
 | |
| 	SettingsButton::paintBg(p, e->rect(), paintOver);
 | |
| 	SettingsButton::paintRipple(p, 0, 0);
 | |
| 	SettingsButton::paintToggle(p, width());
 | |
| 
 | |
| 	const auto &color = st::windowSubTextFg;
 | |
| 	p.setPen(Qt::NoPen);
 | |
| 	p.setBrush(color);
 | |
| 
 | |
| 	const auto left = st::settingsSubsectionTitlePadding.left();
 | |
| 	const auto toggleRect = SettingsButton::maybeToggleRect();
 | |
| 	const auto right = left
 | |
| 		+ (toggleRect.isEmpty() ? 0 : (width() - toggleRect.x()));
 | |
| 
 | |
| 	const auto availableWidth = std::min(
 | |
| 		_title.maxWidth(),
 | |
| 		width() - left - right);
 | |
| 	p.setPen(_st.nameFg);
 | |
| 	_title.drawLeft(
 | |
| 		p,
 | |
| 		left,
 | |
| 		_st.namePosition.y(),
 | |
| 		availableWidth,
 | |
| 		width() - left - right);
 | |
| 
 | |
| 	p.setPen(paintOver ? _st.statusFgOver : _st.statusFg);
 | |
| 	p.setFont(st::contactsStatusFont);
 | |
| 	p.drawTextLeft(
 | |
| 		left,
 | |
| 		_st.statusPosition.y(),
 | |
| 		width() - left - right,
 | |
| 		_status);
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| QString LanguageNameTranslated(const QString &twoLetterCode) {
 | |
| 	return Lang::GetNonDefaultValue(
 | |
| 		kLanguageNamePrefix + twoLetterCode.toUtf8());
 | |
| }
 | |
| 
 | |
| QString LanguageNameLocal(LanguageId id) {
 | |
| 	return QLocale::languageToString(id.language());
 | |
| }
 | |
| 
 | |
| QString LanguageName(LanguageId id) {
 | |
| 	const auto translated = LanguageNameTranslated(id.twoLetterCode());
 | |
| 	return translated.isEmpty() ? LanguageNameLocal(id) : translated;
 | |
| }
 | |
| 
 | |
| QString LanguageNameNative(LanguageId id) {
 | |
| 	const auto locale = id.locale();
 | |
| 	if (locale.language() == QLocale::English
 | |
| 			&& (locale.country() == QLocale::UnitedStates
 | |
| 				|| locale.country() == QLocale::AnyCountry)) {
 | |
| 		return u"English"_q;
 | |
| 	} else if (locale.language() == QLocale::Spanish) {
 | |
| 		return QString::fromUtf8("\x45\x73\x70\x61\xc3\xb1\x6f\x6c");
 | |
| 	} else {
 | |
| 		const auto name = locale.nativeLanguageName();
 | |
| 		return name.left(1).toUpper() + name.mid(1);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| rpl::producer<QString> TranslateBarTo(LanguageId id) {
 | |
| 	const auto translated = Lang::GetNonDefaultValue(
 | |
| 		kTranslateToPrefix + id.twoLetterCode().toUtf8());
 | |
| 	return (translated.isEmpty()
 | |
| 		? tr::lng_translate_bar_to_other
 | |
| 		: tr::lng_translate_bar_to)(
 | |
| 			lt_name,
 | |
| 			rpl::single(translated.isEmpty()
 | |
| 				? LanguageNameLocal(id)
 | |
| 				: translated));
 | |
| }
 | |
| 
 | |
| QString TranslateMenuDont(tr::now_t, LanguageId id) {
 | |
| 	const auto translated = Lang::GetNonDefaultValue(
 | |
| 		kTranslateToPrefix + id.twoLetterCode().toUtf8());
 | |
| 	return (translated.isEmpty()
 | |
| 		? tr::lng_translate_menu_dont_other
 | |
| 		: tr::lng_translate_menu_dont)(
 | |
| 			tr::now,
 | |
| 			lt_name,
 | |
| 			translated.isEmpty() ? LanguageNameLocal(id) : translated);
 | |
| }
 | |
| 
 | |
| void ChooseLanguageBox(
 | |
| 		not_null<GenericBox*> box,
 | |
| 		rpl::producer<QString> title,
 | |
| 		Fn<void(std::vector<LanguageId>)> callback,
 | |
| 		std::vector<LanguageId> selected,
 | |
| 		bool multiselect,
 | |
| 		Fn<bool(LanguageId)> toggleCheck) {
 | |
| 	box->setMinHeight(st::boxWidth);
 | |
| 	box->setMaxHeight(st::boxWidth);
 | |
| 	box->setTitle(std::move(title));
 | |
| 
 | |
| 	const auto multiSelect = box->setPinnedToTopContent(
 | |
| 		object_ptr<MultiSelect>(
 | |
| 			box,
 | |
| 			st::defaultMultiSelect,
 | |
| 			tr::lng_participant_filter()));
 | |
| 	box->setFocusCallback([=] { multiSelect->setInnerFocus(); });
 | |
| 
 | |
| 	const auto container = box->verticalLayout();
 | |
| 	const auto langs = [&] {
 | |
| 		auto list = TranslationLanguagesList();
 | |
| 		for (const auto id : list) {
 | |
| 			LOG(("cloud_lng_language_%1").arg(id.twoLetterCode()));
 | |
| 		}
 | |
| 		const auto current = LanguageId{ QLocale(
 | |
| 			Lang::LanguageIdOrDefault(Lang::Id())).language() };
 | |
| 		if (const auto i = ranges::find(list, current); i != end(list)) {
 | |
| 			base::reorder(list, std::distance(begin(list), i), 0);
 | |
| 		}
 | |
| 		ranges::stable_partition(list, [&](LanguageId id) {
 | |
| 			return ranges::contains(selected, id);
 | |
| 		});
 | |
| 		return list;
 | |
| 	}();
 | |
| 	struct ToggleOne {
 | |
| 		LanguageId id;
 | |
| 		bool selected = false;
 | |
| 	};
 | |
| 	struct State {
 | |
| 		rpl::event_stream<ToggleOne> toggles;
 | |
| 	};
 | |
| 	const auto state = box->lifetime().make_state<State>();
 | |
| 	auto rows = std::vector<not_null<SlideWrap<Row>*>>();
 | |
| 	rows.reserve(langs.size());
 | |
| 	for (const auto &id : langs) {
 | |
| 		const auto button = container->add(
 | |
| 			object_ptr<SlideWrap<Row>>(
 | |
| 				container,
 | |
| 				object_ptr<Row>(container, id)));
 | |
| 		if (multiselect) {
 | |
| 			button->entity()->toggleOn(rpl::single(
 | |
| 				ranges::contains(selected, id)
 | |
| 			) | rpl::then(state->toggles.events(
 | |
| 			) | rpl::filter([=](ToggleOne one) {
 | |
| 				return one.id == id;
 | |
| 			}) | rpl::map([=](ToggleOne one) {
 | |
| 				return one.selected;
 | |
| 			})));
 | |
| 
 | |
| 			button->entity()->toggledChanges(
 | |
| 			) | rpl::start_with_next([=](bool value) {
 | |
| 				if (toggleCheck && !toggleCheck(id)) {
 | |
| 					state->toggles.fire({ .id = id, .selected = !value });
 | |
| 				}
 | |
| 			}, button->lifetime());
 | |
| 		} else {
 | |
| 			button->entity()->setClickedCallback([=] {
 | |
| 				callback({ id });
 | |
| 				box->closeBox();
 | |
| 			});
 | |
| 		}
 | |
| 		rows.push_back(button);
 | |
| 	}
 | |
| 
 | |
| 	multiSelect->setQueryChangedCallback([=](const QString &query) {
 | |
| 		for (const auto &row : rows) {
 | |
| 			const auto toggled = row->entity()->filtered(query);
 | |
| 			if (toggled != row->toggled()) {
 | |
| 				row->toggle(toggled, anim::type::instant);
 | |
| 			}
 | |
| 		}
 | |
| 	});
 | |
| 
 | |
| 	{
 | |
| 		const auto label = CreateChild<FlatLabel>(
 | |
| 			box.get(),
 | |
| 			tr::lng_languages_none(),
 | |
| 			st::membersAbout);
 | |
| 		box->verticalLayout()->geometryValue(
 | |
| 		) | rpl::start_with_next([=](const QRect &geometry) {
 | |
| 			const auto shown = (geometry.height() <= 0);
 | |
| 			label->setVisible(shown);
 | |
| 			if (shown) {
 | |
| 				label->moveToLeft(
 | |
| 					(geometry.width() - label->width()) / 2,
 | |
| 					geometry.y() + st::membersAbout.style.font->height * 4);
 | |
| 				label->stackUnder(box->verticalLayout());
 | |
| 			}
 | |
| 		}, label->lifetime());
 | |
| 	}
 | |
| 
 | |
| 	if (multiselect) {
 | |
| 		box->addButton(tr::lng_settings_save(), [=] {
 | |
| 			auto result = ranges::views::all(
 | |
| 				rows
 | |
| 			) | ranges::views::filter([](const auto &row) {
 | |
| 				return row->entity()->toggled();
 | |
| 			}) | ranges::views::transform([](const auto &row) {
 | |
| 				return row->entity()->id();
 | |
| 			}) | ranges::to_vector;
 | |
| 			if (!result.empty()) {
 | |
| 				callback(std::move(result));
 | |
| 			}
 | |
| 			box->closeBox();
 | |
| 		});
 | |
| 	}
 | |
| 	box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
 | |
| }
 | |
| 
 | |
| } // namespace Ui
 | 
