diff --git a/data/langs/bg-BG.txt b/data/langs/bg-BG.txt deleted file mode 100644 index ae158a07..00000000 --- a/data/langs/bg-BG.txt +++ /dev/null @@ -1,22 +0,0 @@ -ara Арабски -be Белгийски -bg Български -br Бразилски -cz Чешки -de Немски -dk Датски -es Испански -emoji Емоджи -fi Фински -fr Френски -gr Гръцки -it Италиански -jp Японски -no Норвежки -pl Полски -ru Руски -se Шведски -th Тайски -ua Украински -terminal Терминал -us Английски (САЩ) diff --git a/data/langs/cs-CZ.txt b/data/langs/cs-CZ.txt deleted file mode 100644 index 6c87837e..00000000 --- a/data/langs/cs-CZ.txt +++ /dev/null @@ -1,21 +0,0 @@ -be Belgická -cz Česká -cz+qwerty Česká (QWERTY) -de Německá -dk Dánská -emoji Emoji -es Španělská -fi Finská -fr Francouzská -gr Řecká -it Italská -jp Japonská -jp+kana Japonská (Kana) -no Norská -pl Polská -ru Ruská -se Švédská -terminal Terminál -th Thajská -ua Ukrajinská -us Anglická (USA) diff --git a/data/langs/de-DE.txt b/data/langs/de-DE.txt deleted file mode 100644 index e89678c3..00000000 --- a/data/langs/de-DE.txt +++ /dev/null @@ -1,2 +0,0 @@ -emoji Emoji -terminal Terminal diff --git a/data/langs/en-US.txt b/data/langs/en-US.txt deleted file mode 100644 index e89678c3..00000000 --- a/data/langs/en-US.txt +++ /dev/null @@ -1,2 +0,0 @@ -emoji Emoji -terminal Terminal diff --git a/data/langs/es-ES.txt b/data/langs/es-ES.txt deleted file mode 100644 index d377de21..00000000 --- a/data/langs/es-ES.txt +++ /dev/null @@ -1,7 +0,0 @@ -us Inglés (EE.UU.) -de Alemán -el Griego -es Español -it Italiano -jp+kana Japonés (Kana) -no Noruego diff --git a/data/langs/fa-IR.txt b/data/langs/fa-IR.txt deleted file mode 100644 index 17a65aec..00000000 --- a/data/langs/fa-IR.txt +++ /dev/null @@ -1,2 +0,0 @@ -emoji ایموجی -terminal ترمینال diff --git a/data/langs/fur-IT.txt b/data/langs/fur-IT.txt deleted file mode 100644 index 05b78268..00000000 --- a/data/langs/fur-IT.txt +++ /dev/null @@ -1,18 +0,0 @@ -be Belgjic -br Brasilian -de Todesc -dk Danês -es Spagnûl -fi Finlandês -fr Francês -it+fur Furlan -gr Grêc -it Talian -jp+kana Gjaponês (Kana) -no Norvegjês -pl Polac -ru Rus -se Svedês -terminal Terminâl -ua Ucrain -us American (USA) diff --git a/data/langs/he-IL.txt b/data/langs/he-IL.txt deleted file mode 100644 index 221cdf77..00000000 --- a/data/langs/he-IL.txt +++ /dev/null @@ -1,19 +0,0 @@ -be בלגית -br פורטוגזית (ברזיל) -cz צ'כית -de גרמנית -dk דנית -es ספרדית -emoji אימוג'י -fi פינית -fr צרפתית -gr יוונית -il עברית -it איטלקית -no נורווגית -pl פולנית -ru רוסית -se שוודית -terminal טרמינל -ua אוקראינית -us אנגלית (ארה"ב) diff --git a/data/langs/hy-AM.txt b/data/langs/hy-AM.txt deleted file mode 100644 index a181b1dd..00000000 --- a/data/langs/hy-AM.txt +++ /dev/null @@ -1,11 +0,0 @@ -de Գերմաներէն -es Իսպաներէն -fi Ֆիններէն -gr Յունարէն -it Իտալերէն -no Նորուեգերէն -pl Լեհերէն -ru Ռուսերէն -se Շուեդերէն -terminal Տերմինալ -us Անգլերէն (ԱՄՆ) diff --git a/data/langs/ja-JP.txt b/data/langs/ja-JP.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/data/langs/pl-PL.txt b/data/langs/pl-PL.txt deleted file mode 100644 index 0884b6fb..00000000 --- a/data/langs/pl-PL.txt +++ /dev/null @@ -1,2 +0,0 @@ -emoji emoji -terminal terminal diff --git a/data/langs/ru-RU.txt b/data/langs/ru-RU.txt deleted file mode 100644 index 23b131e0..00000000 --- a/data/langs/ru-RU.txt +++ /dev/null @@ -1,11 +0,0 @@ -de Немецкий -es Испанский -fi Финский -gr Греческий -it Итальянский -no Норвежский -pl Польский -ru Русский -se Шведский -terminal Терминал -us Английский (США) diff --git a/data/popover.ui b/data/popover.ui new file mode 100644 index 00000000..b02e71c0 --- /dev/null +++ b/data/popover.ui @@ -0,0 +1,23 @@ + + + + + + Emoji + layout + emoji + + + + Terminal + layout + terminal + +
+ + Keyboard Settings + settings + +
+
+
diff --git a/data/popup.ui b/data/popup.ui deleted file mode 100644 index 214fbea6..00000000 --- a/data/popup.ui +++ /dev/null @@ -1,12 +0,0 @@ - - - - -
- - Keyboard Settings - settings - -
-
-
diff --git a/data/squeekboard.gresources.xml b/data/squeekboard.gresources.xml index 795d22b7..6c5d0213 100644 --- a/data/squeekboard.gresources.xml +++ b/data/squeekboard.gresources.xml @@ -4,7 +4,7 @@ common.css style.css style-Adwaita:dark.css - popup.ui + popover.ui icons/key-enter.svg icons/key-shift.svg icons/keyboard-mode-symbolic.svg diff --git a/src/lib.rs b/src/lib.rs index 287b5286..a133e9ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,6 @@ pub mod imservice; mod keyboard; mod layout; mod locale; -mod locale_config; mod manager; mod outputs; mod popover; diff --git a/src/locale.rs b/src/locale.rs index 63ff6fca..8bdae9ba 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -88,15 +88,6 @@ impl Drop for XkbInfo { } } -#[derive(Clone, Debug, PartialEq)] -pub struct Translation<'a>(pub &'a str); - -impl<'a> Translation<'a> { - pub fn to_owned(&'a self) -> OwnedTranslation { - OwnedTranslation(self.0.to_owned()) - } -} - #[derive(Clone, Debug, PartialEq)] pub struct OwnedTranslation(pub String); diff --git a/src/locale_config.rs b/src/locale_config.rs deleted file mode 100644 index 959dfde1..00000000 --- a/src/locale_config.rs +++ /dev/null @@ -1,535 +0,0 @@ -/*! Locale detection and management. - * Based on https://github.com/rust-locale/locale_config - * - * Ready for deletion/replacement once Debian starts packaging this, - * although this version doesn't need lazy_static. - * - * Copyright (c) 2016–2019 Jan Hudec - Copyright (c) 2016 A.J. Gardner - Copyright (c) 2019, Bastien Orivel - Copyright (c) 2019, Igor Gnatenko - Copyright (c) 2019, Sophie Tauchert <999eagle@999eagle.moe> - */ - -use regex::Regex; -use std::borrow::Cow; -use std::env; - -/// Errors that may be returned by `locale_config`. -#[derive(Copy,Clone,Debug,PartialEq,Eq)] -pub enum Error { - /// Provided definition was not well formed. - /// - /// This is returned when provided configuration string does not match even the rather loose - /// definition for language range from [RFC4647] or the composition format used by `Locale`. - /// - /// [RFC4647]: https://www.rfc-editor.org/rfc/rfc4647.txt - NotWellFormed, - /// Placeholder for adding more errors in future. **Do not match!**. - __NonExhaustive, -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, out: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - out.write_str(match self { - &Error::NotWellFormed => "Language tag is not well-formed.", - // this is exception: here we do want exhaustive match so we don't publish version with - // missing descriptions by mistake. - &Error::__NonExhaustive => panic!("Placeholder error must not be instantiated!"), - }) - } -} - - -/// Convenience Result alias. -type Result = ::std::result::Result; - -/// Iterator over `LanguageRange`s for specific category in a `Locale` -/// -/// Returns `LanguageRange`s in the `Locale` that are applicable to provided category. The tags -/// are returned in order of preference, which means the category-specific ones first and then -/// the generic ones. -/// -/// The iterator is guaranteed to return at least one value. -pub struct TagsFor<'a, 'c> { - src: &'a str, - tags: std::str::Split<'a, &'static str>, - category: Option<&'c str>, -} - -impl<'a, 'c> Iterator for TagsFor<'a, 'c> { - type Item = LanguageRange<'a>; - fn next(&mut self) -> Option { - if let Some(cat) = self.category { - while let Some(s) = self.tags.next() { - if s.starts_with(cat) && s[cat.len()..].starts_with("=") { - return Some( - LanguageRange { language: Cow::Borrowed(&s[cat.len()+1..]) }); - } - } - self.category = None; - self.tags = self.src.split(","); - } - while let Some(s) = self.tags.next() { - if s.find('=').is_none() { - return Some( - LanguageRange{ language: Cow::Borrowed(s) }); - } - } - return None; - } -} - -/// Language and culture identifier. -/// -/// This object holds a [RFC4647] extended language range. -/// -/// The internal data may be owned or shared from object with lifetime `'a`. The lifetime can be -/// extended using the `into_static()` method, which internally clones the data as needed. -/// -/// # Syntax -/// -/// The range is composed of `-`-separated alphanumeric subtags, possibly replaced by `*`s. It -/// might be empty. -/// -/// In agreement with [RFC4647], this object only requires that the tag matches: -/// -/// ```ebnf -/// language_tag = (alpha{1,8} | "*") -/// ("-" (alphanum{1,8} | "*"))* -/// ``` -/// -/// The exact interpretation is up to the downstream localization provider, but it expected that -/// it will be matched against a normalized [RFC5646] language tag, which has the structure: -/// -/// ```ebnf -/// language_tag = language -/// ("-" script)? -/// ("-" region)? -/// ("-" variant)* -/// ("-" extension)* -/// ("-" private)? -/// -/// language = alpha{2,3} ("-" alpha{3}){0,3} -/// -/// script = aplha{4} -/// -/// region = alpha{2} -/// | digit{3} -/// -/// variant = alphanum{5,8} -/// | digit alphanum{3} -/// -/// extension = [0-9a-wyz] ("-" alphanum{2,8})+ -/// -/// private = "x" ("-" alphanum{1,8})+ -/// ``` -/// -/// * `language` is an [ISO639] 2-letter or, where not defined, 3-letter code. A code for -/// macro-language might be followed by code of specific dialect. -/// * `script` is an [ISO15924] 4-letter code. -/// * `region` is either an [ISO3166] 2-letter code or, for areas other than countries, [UN M.49] -/// 3-digit numeric code. -/// * `variant` is a string indicating variant of the language. -/// * `extension` and `private` define additional options. The private part has same structure as -/// the Unicode [`-u-` extension][u_ext]. Available options are documented for the facets that -/// use them. -/// -/// The values obtained by inspecting the system are normalized according to those rules. -/// -/// The content will be case-normalized as recommended in [RFC5646] §2.1.1, namely: -/// -/// * `language` is written in lowercase, -/// * `script` is written with first capital, -/// * `country` is written in uppercase and -/// * all other subtags are written in lowercase. -/// -/// When detecting system configuration, additional options that may be generated under the -/// [`-u-` extension][u_ext] currently are: -/// -/// * `cf` — Currency format (`account` for parenthesized negative values, `standard` for minus -/// sign). -/// * `fw` — First day of week (`mon` to `sun`). -/// * `hc` — Hour cycle (`h12` for 1–12, `h23` for 0–23). -/// * `ms` — Measurement system (`metric` or `ussystem`). -/// * `nu` — Numbering system—only decimal systems are currently used. -/// * `va` — Variant when locale is specified in Unix format and the tag after `@` does not -/// correspond to any variant defined in [Language subtag registry]. -/// -/// And under the `-x-` extension, following options are defined: -/// -/// * `df` — Date format: -/// -/// * `iso`: Short date should be in ISO format of `yyyy-MM-dd`. -/// -/// For example `-df-iso`. -/// -/// * `dm` — Decimal separator for monetary: -/// -/// Followed by one or more Unicode codepoints in hexadecimal. For example `-dm-002d` means to -/// use comma. -/// -/// * `ds` — Decimal separator for numbers: -/// -/// Followed by one or more Unicode codepoints in hexadecimal. For example `-ds-002d` means to -/// use comma. -/// -/// * `gm` — Group (thousand) separator for monetary: -/// -/// Followed by one or more Unicode codepoints in hexadecimal. For example `-dm-00a0` means to -/// use non-breaking space. -/// -/// * `gs` — Group (thousand) separator for numbers: -/// -/// Followed by one or more Unicode codepoints in hexadecimal. For example `-ds-00a0` means to -/// use non-breaking space. -/// -/// * `ls` — List separator: -/// -/// Followed by one or more Unicode codepoints in hexadecimal. For example, `-ds-003b` means to -/// use a semicolon. -/// -/// [RFC5646]: https://www.rfc-editor.org/rfc/rfc5646.txt -/// [RFC4647]: https://www.rfc-editor.org/rfc/rfc4647.txt -/// [ISO639]: https://en.wikipedia.org/wiki/ISO_639 -/// [ISO15924]: https://en.wikipedia.org/wiki/ISO_15924 -/// [ISO3166]: https://en.wikipedia.org/wiki/ISO_3166 -/// [UN M.49]: https://en.wikipedia.org/wiki/UN_M.49 -/// [u_ext]: http://www.unicode.org/reports/tr35/#u_Extension -/// [Language subtag registry]: https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry -#[derive(Clone,Debug,Eq,Hash,PartialEq)] -pub struct LanguageRange<'a> { - language: Cow<'a, str> -} - -impl<'a> LanguageRange<'a> { - /// Return LanguageRange for the invariant locale. - /// - /// Invariant language is identified simply by empty string. - pub fn invariant() -> LanguageRange<'static> { - LanguageRange { language: Cow::Borrowed("") } - } - - /// Create language tag from Unix/Linux/GNU locale tag. - /// - /// Unix locale tags have the form - /// - /// > *language* [ `_` *region* ] [ `.` *encoding* ] [ `@` *variant* ] - /// - /// The *language* and *region* have the same format as RFC5646. *Encoding* is not relevant - /// here, since Rust always uses Utf-8. That leaves *variant*, which is unfortunately rather - /// free-form. So this function will translate known variants to corresponding RFC5646 subtags - /// and represent anything else with Unicode POSIX variant (`-u-va-`) extension. - /// - /// Note: This function is public here for benefit of applications that may come across this - /// kind of tags from other sources than system configuration. - pub fn from_unix(s: &str) -> Result> { - let unix_tag_regex = Regex::new(r"(?ix) ^ - (?P [[:alpha:]]{2,3} ) - (?: _ (?P [[:alpha:]]{2} | [[:digit:]]{3} ))? - (?: \. (?P [0-9a-zA-Z-]{1,20} ))? - (?: @ (?P [[:alnum:]]{1,20} ))? - $ ").unwrap(); - - let unix_invariant_regex = Regex::new(r"(?ix) ^ - (?: c | posix ) - (?: \. (?: [0-9a-zA-Z-]{1,20} ))? - $ ").unwrap(); - - if let Some(caps) = unix_tag_regex.captures(s) { - let src_variant = caps.name("variant").map(|m| m.as_str()).unwrap_or("").to_ascii_lowercase(); - let mut res = caps.name("language").map(|m| m.as_str()).unwrap().to_ascii_lowercase(); - let region = caps.name("region").map(|m| m.as_str()).unwrap_or(""); - let mut script = ""; - let mut variant = ""; - let mut uvariant = ""; - match src_variant.as_ref() { - // Variants seen in the wild in GNU LibC (via http://lh.2xlibre.net/) or in Debian - // GNU/Linux Stretch system. Treatment of things not found in RFC5646 subtag registry - // (http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) - // or CLDR according to notes at https://wiki.openoffice.org/wiki/LocaleMapping. - // Dialects: - // aa_ER@saaho - NOTE: Can't be found under that name in RFC5646 subtag registry, - // but there is language Saho with code ssy, which is likely that thing. - "saaho" if res == "aa" => res = String::from("ssy"), - // Scripts: - // @arabic - "arabic" => script = "Arab", - // @cyrillic - "cyrl" => script = "Cyrl", - "cyrillic" => script = "Cyrl", - // @devanagari - "devanagari" => script = "Deva", - // @hebrew - "hebrew" => script = "Hebr", - // tt@iqtelif - // Neither RFC5646 subtag registry nor CLDR knows anything about this, but as best - // as I can tell it is Tatar name for Latin (default is Cyrillic). - "iqtelif" => script = "Latn", - // @Latn - "latn" => script = "Latn", - // @latin - "latin" => script = "Latn", - // en@shaw - "shaw" => script = "Shaw", - // Variants: - // sr@ijekavianlatin - "ijekavianlatin" => { - script = "Latn"; - variant = "ijekavsk"; - }, - // sr@ije - "ije" => variant = "ijekavsk", - // sr@ijekavian - "ijekavian" => variant = "ijekavsk", - // ca@valencia - "valencia" => variant = "valencia", - // Currencies: - // @euro - NOTE: We follow suite of Java and Openoffice and ignore it, because it - // is default for all locales where it sometimes appears now, and because we use - // explicit currency in monetary formatting anyway. - "euro" => {}, - // Collation: - // gez@abegede - NOTE: This is collation, but CLDR does not have any code for it, - // so we for the moment leave it fall through as -u-va- instead of -u-co-. - // Anything else: - // en@boldquot, en@quot, en@piglatin - just randomish stuff - // @cjknarrow - beware, it's gonna end up as -u-va-cjknarro due to lenght limit - s if s.len() <= 8 => uvariant = &*s, - s => uvariant = &s[0..8], // the subtags are limited to 8 chars, but some are longer - }; - if script != "" { - res.push('-'); - res.push_str(script); - } - if region != "" { - res.push('-'); - res.push_str(&*region.to_ascii_uppercase()); - } - if variant != "" { - res.push('-'); - res.push_str(variant); - } - if uvariant != "" { - res.push_str("-u-va-"); - res.push_str(uvariant); - } - return Ok(LanguageRange { - language: Cow::Owned(res) - }); - } else if unix_invariant_regex.is_match(s) { - return Ok(LanguageRange::invariant()) - } else { - return Err(Error::NotWellFormed); - } - } -} - -impl<'a> AsRef for LanguageRange<'a> { - fn as_ref(&self) -> &str { - self.language.as_ref() - } -} - -/// Locale configuration. -/// -/// Users may accept several languages in some order of preference and may want to use rules from -/// different culture for some particular aspect of the program behaviour, and operating systems -/// allow them to specify this (to various extent). -/// -/// The `Locale` objects represent the user configuration. They contain: -/// -/// - The primary `LanguageRange`. -/// - Optional category-specific overrides. -/// - Optional fallbacks in case data (usually translations) for the primary language are not -/// available. -/// -/// The set of categories is open-ended. The `locale` crate uses five well-known categories -/// `messages`, `numeric`, `time`, `collate` and `monetary`, but some systems define additional -/// ones (GNU Linux has additionally `paper`, `name`, `address`, `telephone` and `measurement`) and -/// these are provided in the user default `Locale` and other libraries can use them. -/// -/// `Locale` is represented by a `,`-separated sequence of tags in `LanguageRange` syntax, where -/// all except the first one may be preceded by category name and `=` sign. -/// -/// The first tag indicates the default locale, the tags prefixed by category names indicate -/// _overrides_ for those categories and the remaining tags indicate fallbacks. -/// -/// Note that a syntactically valid value of HTTP `Accept-Language` header is a valid `Locale`. Not -/// the other way around though due to the presence of category selectors. -// TODO: Interning -#[derive(Clone,Debug,Eq,Hash,PartialEq)] -pub struct Locale { - // TODO: Intern the string for performance reasons - // XXX: Store pre-split to LanguageTags? - inner: String, -} - -impl Locale { - /// Construct invariant locale. - /// - /// Invariant locale is represented simply with empty string. - pub fn invariant() -> Locale { - Locale::from(LanguageRange::invariant()) - } - - /// Append fallback language tag. - /// - /// Adds fallback to the end of the list. - pub fn add(&mut self, tag: &LanguageRange) { - for i in self.inner.split(',') { - if i == tag.as_ref() { - return; // don't add duplicates - } - } - self.inner.push_str(","); - self.inner.push_str(tag.as_ref()); - } - - /// Append category override. - /// - /// Appending new override for a category that already has one will not replace the existing - /// override. This might change in future. - pub fn add_category(&mut self, category: &str, tag: &LanguageRange) { - if self.inner.split(',').next().unwrap() == tag.as_ref() { - return; // don't add useless override equal to the primary tag - } - for i in self.inner.split(',') { - if i.starts_with(category) && - i[category.len()..].starts_with("=") && - &i[category.len() + 1..] == tag.as_ref() { - return; // don't add duplicates - } - } - self.inner.push_str(","); - self.inner.push_str(category); - self.inner.push_str("="); - self.inner.push_str(tag.as_ref()); - } - - /// Iterate over `LanguageRange`s in this `Locale` applicable to given category. - /// - /// Returns `LanguageRange`s in the `Locale` that are applicable to provided category. The tags - /// are returned in order of preference, which means the category-specific ones first and then - /// the generic ones. - /// - /// The iterator is guaranteed to return at least one value. - pub fn tags_for<'a, 'c>(&'a self, category: &'c str) -> TagsFor<'a, 'c> { - let mut tags = self.inner.split(","); - while let Some(s) = tags.clone().next() { - if s.starts_with(category) && s[category.len()..].starts_with("=") { - return TagsFor { - src: self.inner.as_ref(), - tags: tags, - category: Some(category), - }; - } - tags.next(); - } - return TagsFor { - src: self.inner.as_ref(), - tags: self.inner.split(","), - category: None, - }; - } -} - -/// Locale is specified by a string tag. This is the way to access it. -// FIXME: Do we want to provide the full string representation? We would have it as single string -// then. -impl AsRef for Locale { - fn as_ref(&self) -> &str { - self.inner.as_ref() - } -} - -impl<'a> From> for Locale { - fn from(t: LanguageRange<'a>) -> Locale { - Locale { - inner: t.language.into_owned(), - } - } -} - -fn tag(s: &str) -> Result { - LanguageRange::from_unix(s) -} - -// TODO: Read /etc/locale.alias -fn tag_inv(s: &str) -> LanguageRange { - tag(s).unwrap_or(LanguageRange::invariant()) -} - -pub fn system_locale() -> Option { - // LC_ALL overrides everything - if let Ok(all) = env::var("LC_ALL") { - if let Ok(t) = tag(all.as_ref()) { - return Some(Locale::from(t)); - } - } - // LANG is default - let mut loc = - if let Ok(lang) = env::var("LANG") { - Locale::from(tag_inv(lang.as_ref())) - } else { - Locale::invariant() - }; - // category overrides - for &(cat, var) in [ - ("ctype", "LC_CTYPE"), - ("numeric", "LC_NUMERIC"), - ("time", "LC_TIME"), - ("collate", "LC_COLLATE"), - ("monetary", "LC_MONETARY"), - ("messages", "LC_MESSAGES"), - ("paper", "LC_PAPER"), - ("name", "LC_NAME"), - ("address", "LC_ADDRESS"), - ("telephone", "LC_TELEPHONE"), - ("measurement", "LC_MEASUREMENT"), - ].iter() { - if let Ok(val) = env::var(var) { - if let Ok(tag) = tag(val.as_ref()) - { - loc.add_category(cat, &tag); - } - } - } - // LANGUAGE defines fallbacks - if let Ok(langs) = env::var("LANGUAGE") { - for i in langs.split(':') { - if i != "" { - if let Ok(tag) = tag(i) { - loc.add(&tag); - } - } - } - } - if loc.as_ref() != "" { - return Some(loc); - } else { - return None; - } -} - -#[cfg(test)] -mod test { - use super::LanguageRange; - - #[test] - fn unix_tags() { - assert_eq!("cs-CZ", LanguageRange::from_unix("cs_CZ.UTF-8").unwrap().as_ref()); - assert_eq!("sr-RS-ijekavsk", LanguageRange::from_unix("sr_RS@ijekavian").unwrap().as_ref()); - assert_eq!("sr-Latn-ijekavsk", LanguageRange::from_unix("sr.UTF-8@ijekavianlatin").unwrap().as_ref()); - assert_eq!("en-Arab", LanguageRange::from_unix("en@arabic").unwrap().as_ref()); - assert_eq!("en-Arab", LanguageRange::from_unix("en.UTF-8@arabic").unwrap().as_ref()); - assert_eq!("de-DE", LanguageRange::from_unix("DE_de.UTF-8@euro").unwrap().as_ref()); - assert_eq!("ssy-ER", LanguageRange::from_unix("aa_ER@saaho").unwrap().as_ref()); - assert!(LanguageRange::from_unix("foo_BAR").is_err()); - assert!(LanguageRange::from_unix("en@arabic.UTF-8").is_err()); - assert_eq!("", LanguageRange::from_unix("C").unwrap().as_ref()); - assert_eq!("", LanguageRange::from_unix("C.UTF-8").unwrap().as_ref()); - assert_eq!("", LanguageRange::from_unix("C.ISO-8859-1").unwrap().as_ref()); - assert_eq!("", LanguageRange::from_unix("POSIX").unwrap().as_ref()); - } -} diff --git a/src/popover.rs b/src/popover.rs index 9a1b6902..feec80f1 100644 --- a/src/popover.rs +++ b/src/popover.rs @@ -5,9 +5,7 @@ use gtk; use std::ffi::CString; use std::cmp::Ordering; use ::layout::c::{ Bounds, EekGtkKeyboard }; -use ::locale; -use ::locale::{ OwnedTranslation, Translation, compare_current_locale }; -use ::locale_config::system_locale; +use ::locale::{ OwnedTranslation, compare_current_locale }; use ::logging; use ::manager; use ::resources; @@ -20,10 +18,9 @@ use gio::SimpleActionExt; use glib::translate::FromGlibPtrNone; use glib::variant::ToVariant; #[cfg(not(feature = "gtk_v0_5"))] -use gtk::BuilderExtManual; +use gtk::prelude::*; use gtk::PopoverExt; use gtk::WidgetExt; -use std::io::Write; use ::logging::Warn; mod c { @@ -111,46 +108,6 @@ mod variants { } } -fn make_menu_builder(inputs: Vec<(&str, OwnedTranslation)>) -> gtk::Builder { - let mut xml: Vec = Vec::new(); - writeln!( - xml, - " - - -
" - ).unwrap(); - for (input_name, translation) in inputs { - writeln!( - xml, - " - - {} - layout - {} - ", - translation.0, - input_name, - ).unwrap(); - } - writeln!( - xml, - " -
-
- - Keyboard Settings - settings - -
-
-
" - ).unwrap(); - gtk::Builder::new_from_string( - &String::from_utf8(xml).expect("Bad menu definition") - ) -} - fn get_settings(schema_name: &str) -> Option { let mut error_handler = logging::Print{}; gio::SettingsSchemaSource::get_default() @@ -250,13 +207,8 @@ fn get_current_layout( /// Translates all provided layout names according to current locale, /// for the purpose of display (i.e. errors will be caught and reported) fn translate_layout_names(layouts: &Vec) -> Vec { - // This procedure is rather ugly... - // Xkb lookup *must not* be applied to non-system layouts, - // so both translators can't be merged into one lookup table, - // therefore must be done in two steps. - // `XkbInfo` being temporary also means - // that its return values must be copied, - // forcing the use of `OwnedTranslation`. + // `XkbInfo` being temporary means that its return values must be + // copied, forcing the use of `OwnedTranslation`. enum Status { /// xkb names should get all translated here Translated(OwnedTranslation), @@ -265,7 +217,7 @@ fn translate_layout_names(layouts: &Vec) -> Vec { } // Attempt to take all xkb names from gnome-desktop's xkb info. - let xkb_translator = locale::XkbInfo::new(); + let xkb_translator = ::locale::XkbInfo::new(); let translated_names = layouts.iter() .map(|id| match id { @@ -277,49 +229,15 @@ fn translate_layout_names(layouts: &Vec) -> Vec { &format!("No display name for xkb layout {}", name), ).unwrap_or_else(|| Status::Remaining(name.clone())) }, - LayoutId::Local(name) => Status::Remaining(name.clone()), + LayoutId::Local (_) => unreachable!(), }); - // Non-xkb layouts and weird xkb layouts - // still need to be looked up in the internal database. - let builtin_translations = system_locale() - .map(|locale| - locale.tags_for("messages") - .next().unwrap() // guaranteed to exist - .as_ref() - .to_owned() - ) - .or_print(logging::Problem::Surprise, "No locale detected") - .and_then(|lang| { - resources::get_layout_names(lang.as_str()) - .or_print( - logging::Problem::Surprise, - &format!("No translations for locale {}", lang), - ) - }); - - match builtin_translations { - Some(translations) => { - translated_names - .map(|status| match status { - Status::Remaining(name) => { - translations.get(name.as_str()) - .unwrap_or(&Translation(name.as_str())) - .to_owned() - }, - Status::Translated(t) => t, - }) - .collect() - }, - None => { - translated_names - .map(|status| match status { - Status::Remaining(name) => OwnedTranslation(name), - Status::Translated(t) => t, - }) - .collect() - }, - } + translated_names + .map(|status| match status { + Status::Remaining(name) => OwnedTranslation(name), + Status::Translated(t) => t, + }) + .collect() } pub fn show( @@ -350,12 +268,12 @@ pub fn show( .chain(overlay_layouts) .collect(); - let translated_names = translate_layout_names(&all_layouts); - - // sorted collection of human and machine names + let translated_names = translate_layout_names(&system_layouts); + + // sorted collection of language layouts let mut human_names: Vec<(OwnedTranslation, LayoutId)> = translated_names .into_iter() - .zip(all_layouts.clone().into_iter()) + .zip(system_layouts.clone().into_iter()) .collect(); human_names.sort_unstable_by(|(tr_a, layout_a), (tr_b, layout_b)| { @@ -367,32 +285,14 @@ pub fn show( } }); - // GVariant doesn't natively support `enum`s, - // so the `choices` vector will serve as a lookup table. - let choices_with_translations: Vec<(String, (OwnedTranslation, LayoutId))> - = human_names.into_iter() - .enumerate() - .map(|(i, human_entry)| {( - format!("{}_{}", i, human_entry.1.get_name()), - human_entry, - )}).collect(); + let builder = gtk::Builder::new_from_resource("/sm/puri/squeekboard/popover.ui"); + let model: gio::Menu = builder.get_object("app-menu").unwrap(); - - let builder = make_menu_builder( - choices_with_translations.iter() - .map(|(id, (translation, _))| (id.as_str(), (*translation).clone())) - .collect() - ); - - let choices: Vec<(String, LayoutId)> - = choices_with_translations.into_iter() - .map(|(id, (_tr, layout))| (id, layout)) - .collect(); - - // Much more debuggable to populate the model & menu - // from a string representation - // than add items imperatively - let model: gio::MenuModel = builder.get_object("app-menu").unwrap(); + for (tr, l) in human_names.iter().rev() { + let detailed_action = format!("layout::{}", l.get_name()); + let item = gio::MenuItem::new(Some(&tr.0), Some(detailed_action.as_str())); + model.prepend_item (&item); + } let menu = gtk::Popover::new_from_model(Some(&window), &model); menu.set_pointing_to(>k::Rectangle { @@ -403,32 +303,36 @@ pub fn show( }); menu.set_constrain_to(gtk::PopoverConstraint::None); + let action_group = gio::SimpleActionGroup::new(); + if let Some(current_layout) = get_current_layout(manager, &system_layouts) { - let current_name_variant = choices.iter() + let current_layout_name = all_layouts.iter() .find( - |(_id, layout)| layout == ¤t_layout + |l| l.get_name() == current_layout.get_name() ).unwrap() - .0.to_variant(); + .get_name(); + log_print!(logging::Level::Debug, "Current Layout {}", current_layout_name); let layout_action = gio::SimpleAction::new_stateful( "layout", - Some(current_name_variant.type_()), - ¤t_name_variant, + Some(current_layout_name.to_variant().type_()), + ¤t_layout_name.to_variant() ); let menu_inner = menu.clone(); layout_action.connect_change_state(move |_action, state| { match state { Some(v) => { + log_print!(logging::Level::Debug, "Selected layout {}", v); v.get::() .or_print( logging::Problem::Bug, &format!("Variant is not string: {:?}", v) ) .map(|state| { - let (_id, layout) = choices.iter() + let layout = all_layouts.iter() .find( - |choices| state == choices.0 + |choices| state == choices.get_name() ).unwrap(); set_visible_layout( manager, @@ -443,20 +347,18 @@ pub fn show( }; menu_inner.popdown(); }); - - let settings_action = gio::SimpleAction::new("settings", None); - settings_action.connect_activate(move |_, _| { - let s = CString::new("region").unwrap(); - unsafe { c::popover_open_settings_panel(s.as_ptr()) }; - }); - - let action_group = gio::SimpleActionGroup::new(); action_group.add_action(&layout_action); - action_group.add_action(&settings_action); - - menu.insert_action_group("popup", Some(&action_group)); }; + let settings_action = gio::SimpleAction::new("settings", None); + settings_action.connect_activate(move |_, _| { + let s = CString::new("region").unwrap(); + unsafe { c::popover_open_settings_panel(s.as_ptr()) }; + }); + action_group.add_action(&settings_action); + + menu.insert_action_group("popup", Some(&action_group)); + menu.bind_model(Some(&model), Some("popup")); menu.popup(); } diff --git a/src/resources.rs b/src/resources.rs index f512921d..1795dbe9 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -2,11 +2,6 @@ * This could be done using GResource, but that would need additional work. */ -use std::collections::HashMap; -use ::locale::Translation; - -use std::iter::FromIterator; - // TODO: keep a list of what is a language layout, // and what a convenience layout. "_wide" is not a layout, // neither is "number" @@ -125,46 +120,6 @@ pub fn get_overlays() -> Vec<&'static str> { OVERLAY_NAMES.to_vec() } -/// Translations of the layout identifier strings -static LAYOUT_NAMES: &[(&'static str, &'static str)] = &[ - ("de-DE", include_str!("../data/langs/de-DE.txt")), - ("en-US", include_str!("../data/langs/en-US.txt")), - ("es-ES", include_str!("../data/langs/es-ES.txt")), - ("fur-IT", include_str!("../data/langs/fur-IT.txt")), - ("he-IL", include_str!("../data/langs/he-IL.txt")), - ("ja-JP", include_str!("../data/langs/ja-JP.txt")), - ("pl-PL", include_str!("../data/langs/pl-PL.txt")), - ("ru-RU", include_str!("../data/langs/ru-RU.txt")), -]; - -pub fn get_layout_names(lang: &str) - -> Option>> -{ - let translations = LAYOUT_NAMES.iter() - .find(|(name, _data)| *name == lang) - .map(|(_name, data)| *data); - translations.map(make_mapping) -} - -fn parse_line(line: &str) -> Option<(&str, Translation)> { - let comment = line.trim().starts_with("#"); - if comment { - None - } else { - let mut iter = line.splitn(2, " "); - let name = iter.next().unwrap(); - // will skip empty and unfinished lines - iter.next().map(|tr| (name, Translation(tr.trim()))) - } -} - -fn make_mapping(data: &str) -> HashMap<&str, Translation> { - HashMap::from_iter( - data.split("\n") - .filter_map(parse_line) - ) -} - #[cfg(test)] mod test { use super::*; @@ -175,32 +130,4 @@ mod test { assert!(get_keyboard(&format!("{}/us", name)).is_some()); } } - - #[test] - fn mapping_line() { - assert_eq!( - Some(("name", Translation("translation"))), - parse_line("name translation") - ); - } - - #[test] - fn mapping_bad() { - assert_eq!(None, parse_line("bad")); - } - - #[test] - fn mapping_empty() { - assert_eq!(None, parse_line("")); - } - - #[test] - fn mapping_comment() { - assert_eq!(None, parse_line("# comment")); - } - - #[test] - fn mapping_comment_offset() { - assert_eq!(None, parse_line(" # comment")); - } }