A GTK application that tests out several different ICU apis

This commit is contained in:
Savanni D'Gerinel 2024-02-01 16:26:05 -05:00
commit b022153a68
No known key found for this signature in database
GPG Key ID: 3467D9CC77F953B8
10 changed files with 2359 additions and 0 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
use flake

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
.direnv

1803
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

19
Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "locale"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
adw = { version = "0.4", package = "libadwaita", features = ["v1_2"] }
gio = "0.18.3"
gtk = { version = "0.6", package = "gtk4" }
icu = { version = "1.4.0", features = ["experimental"] }
icu_provider = "1.4.0"
icu_locid = "1.4.0"
libc = "0.2.150"
chrono = "*"
[build-dependencies]
glib-build-tools = "0.16"

7
build.rs Normal file
View File

@ -0,0 +1,7 @@
fn main() {
glib_build_tools::compile_resources(
"resources",
"resources/gresources.xml",
"com.luminescent-dreams.locale.gresource",
);
}

172
flake.lock Normal file
View File

@ -0,0 +1,172 @@
{
"nodes": {
"cargo2nix": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1699033427,
"narHash": "sha256-OVtd5IPbb4NvHibN+QvMrMxq7aZN5GFoINZSAXKjUdA=",
"owner": "cargo2nix",
"repo": "cargo2nix",
"rev": "c6f33051f412352f293e738cc8da6fd4c457080f",
"type": "github"
},
"original": {
"owner": "cargo2nix",
"repo": "cargo2nix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1697382362,
"narHash": "sha256-PvFjWFmSYOF6TjNZ/WjOeqa+sgaWm+83Fz37vEuATHA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ad9a253a0d34f313707f9c25fb8c95c65b1c8882",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "release-23.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1699099776,
"narHash": "sha256-X09iKJ27mGsGambGfkKzqvw5esP1L/Rf8H3u3fCqIiU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "85f1ba3e51676fa8cc604a3d863d729026a6b8eb",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
}
},
"root": {
"inputs": {
"cargo2nix": "cargo2nix",
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"cargo2nix",
"flake-utils"
],
"nixpkgs": [
"cargo2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1697336027,
"narHash": "sha256-ctmmw7j4liyfSh63v9rdFZeIoNYCkCvgqvtEOB7KhX8=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "e494404d36a41247987eeb1bfc2f1ca903e97764",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

33
flake.nix Normal file
View File

@ -0,0 +1,33 @@
{
description = "POSIX locale testing";
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
cargo2nix.url = "github:cargo2nix/cargo2nix";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, cargo2nix, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
c2n = import cargo2nix { inherit system; };
in {
devShells.default = pkgs.mkShell {
name = "locale-devshell";
buildInputs = [
# c2n.cargo2nix
pkgs.gtk4
pkgs.gtkd
pkgs.libadwaita
pkgs.pkg-config
pkgs.rustup
];
};
packages.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
packages.default = self.packages.x86_64-linux.hello;
});
}

3
resources/gresources.xml Normal file
View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
</gresources>

2
rust-toolchain Normal file
View File

@ -0,0 +1,2 @@
[toolchain]
channel = "1.73.0"

317
src/bin/gtk.rs Normal file
View File

@ -0,0 +1,317 @@
use chrono::{Datelike, Local, Timelike};
use gtk::prelude::*;
use icu::{
calendar::{
types::{IsoHour, IsoMinute, IsoSecond, NanoSecond, Time},
Date, DateTime,
},
datetime::{
options::length::{self, Bag},
DateTimeFormatter,
},
locid::locale,
};
use icu_locid::Locale;
use icu_provider::DataLocale;
use libc::{self, LC_MESSAGES, LC_TIME};
use std::{
env,
ffi::{CStr, CString},
sync::{Arc, RwLock},
time::Instant,
};
fn get_langinfo(value: libc::nl_item) -> String {
unsafe {
let r = libc::nl_langinfo(value);
CStr::from_ptr(r).to_string_lossy().into_owned()
}
}
trait ProvidesLocale {
fn language(&self) -> String;
fn time(&self) -> String;
// fn date_time_fmt(&self) -> String;
}
struct Linux {}
impl ProvidesLocale for Linux {
fn language(&self) -> String {
unsafe {
let locale_param = CString::new("").unwrap();
let lc_c = libc::setlocale(LC_MESSAGES, locale_param.as_ptr());
CStr::from_ptr(lc_c).to_string_lossy().into_owned()
}
}
fn time(&self) -> String {
unsafe {
let locale_param = CString::new("").unwrap();
let lc_c = libc::setlocale(LC_TIME, locale_param.as_ptr());
CStr::from_ptr(lc_c).to_string_lossy().into_owned()
}
}
/*
fn date_time_fmt(&self) -> String {
unsafe {
let r = libc::nl_langinfo(LC_TIME);
CStr::from_ptr(r).to_string_lossy().into_owned()
}
}
*/
}
fn create_label(name: &str, value: &str) -> gtk::Widget {
let name = gtk::Label::builder()
.justify(gtk::Justification::Left)
.label(name)
.halign(gtk::Align::Start)
.build();
let value = gtk::Label::builder()
.justify(gtk::Justification::Left)
.label(value)
.halign(gtk::Align::Start)
.build();
let layout = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.homogeneous(true)
.build();
layout.append(&name);
layout.append(&value);
layout.upcast::<gtk::Widget>()
}
fn render_data() -> gtk::Box {
let provider = Linux {};
let layout = gtk::Box::builder()
.halign(gtk::Align::Start)
.orientation(gtk::Orientation::Vertical)
.spacing(8)
.homogeneous(true)
.build();
layout.append(
&gtk::Label::builder()
.justify(gtk::Justification::Left)
.label(format!("{:?}", Instant::now()))
.build(),
);
layout.append(&create_label("Language", &provider.language()));
layout.append(&create_label("Time", &provider.time()));
/* let time_locale = provider.language().parse::<Locale>().unwrap();*/
let now = chrono::Utc::now();
let now: icu::calendar::DateTime<icu::calendar::Gregorian> = icu::calendar::DateTime::new(
Date::try_new_gregorian_date(now.year() as i32, now.month() as u8, now.day() as u8)
.unwrap(),
Time::new(
IsoHour::try_from(now.hour() as u8).unwrap(),
IsoMinute::try_from(now.minute() as u8).unwrap(),
IsoSecond::try_from(now.second() as u8).unwrap(),
NanoSecond::try_from(0 as u32).unwrap(),
),
);
let locales = ["en-US", "eo", "de-DE", "fr-FR", "es-ES", "zh-CN", "ja-JA"];
let styles: Vec<(String, Locale, Bag)> = locales
.into_iter()
.map(|locale| {
let lc = locale.parse::<Locale>().unwrap();
vec![
(
format!("{}, full date", locale),
lc.clone(),
length::Bag::from_date_style(length::Date::Full).into(),
),
(
format!("{}, long date", locale),
lc.clone(),
length::Bag::from_date_style(length::Date::Long).into(),
),
(
format!("{}, medium date", locale),
lc.clone(),
length::Bag::from_date_style(length::Date::Medium).into(),
),
(
format!("{}, short date", locale),
lc.clone(),
length::Bag::from_date_style(length::Date::Short).into(),
),
(
format!("{}, full time", locale),
lc.clone(),
length::Bag::from_time_style(length::Time::Full).into(),
),
(
format!("{}, long time", locale),
lc.clone(),
length::Bag::from_time_style(length::Time::Long).into(),
),
(
format!("{}, medium time", locale),
lc.clone(),
length::Bag::from_time_style(length::Time::Medium).into(),
),
(
format!("{}, short time", locale),
lc.clone(),
length::Bag::from_time_style(length::Time::Short).into(),
),
]
})
.flatten()
.collect();
for (descriptor, locale, bag) in styles {
let formatter = DateTimeFormatter::try_new(&DataLocale::from(locale), bag.into());
match formatter {
Ok(formatter) => layout.append(&create_label(
&descriptor,
&formatter.format_to_string(&now.to_any()).unwrap(),
)),
Err(formatter_err) => layout.append(&create_label(
&descriptor,
&format!("unhandled formatter: {:?}", formatter_err),
)),
}
}
/*
layout.append(&create_label(
"formatted date and time",
dt_fmt.format_to_string(DateTime::try_new_iso_datetime(2023, 10, 13, 3, 28, 0).unwrap()),
))
*/
/*
layout.append(&create_label(
"date and time format",
&provider.date_time_fmt(),
));
*/
layout.append(&create_label(
"codeset",
get_langinfo(libc::CODESET).as_ref(),
));
layout.append(&create_label(
"d_t_fmt",
get_langinfo(libc::D_T_FMT).as_ref(),
));
layout.append(&create_label("d_fmt", get_langinfo(libc::D_FMT).as_ref()));
layout.append(&create_label("t_fmt", get_langinfo(libc::T_FMT).as_ref()));
layout.append(&create_label("am_str", get_langinfo(libc::AM_STR).as_ref()));
layout.append(&create_label("pm_str", get_langinfo(libc::PM_STR).as_ref()));
layout.append(&create_label(
"t_fmt_ampm",
get_langinfo(libc::T_FMT_AMPM).as_ref(),
));
layout.append(&create_label("era", get_langinfo(libc::ERA).as_ref()));
layout.append(&create_label(
"era_d_t_fmt",
get_langinfo(libc::ERA_D_T_FMT).as_ref(),
));
layout.append(&create_label(
"era_d_fmt",
get_langinfo(libc::ERA_D_FMT).as_ref(),
));
layout.append(&create_label(
"era_t_fmt",
get_langinfo(libc::ERA_T_FMT).as_ref(),
));
layout.append(&create_label("%c", &Local::now().format("%c").to_string()));
layout.append(&create_label("%x", &Local::now().format("%c").to_string()));
layout.append(&create_label("%X", &Local::now().format("%c").to_string()));
/*
layout.append(&create_label("day_1", get_langinfo(libc::DAY_1).as_ref()));
layout.append(&create_label(
"abday_1",
get_langinfo(libc::ABDAY_1).as_ref(),
));
layout.append(&create_label("mon_1", get_langinfo(libc::MON_1).as_ref()));
layout.append(&create_label(
"abmon_1",
get_langinfo(libc::ABMON_1).as_ref(),
));
layout.append(&create_label(
"radixchar",
get_langinfo(libc::RADIXCHAR).as_ref(),
));
layout.append(&create_label(
"thousep",
get_langinfo(libc::THOUSEP).as_ref(),
));
layout.append(&create_label(
"yesexpr",
get_langinfo(libc::YESEXPR).as_ref(),
));
layout.append(&create_label("noexpr", get_langinfo(libc::NOEXPR).as_ref()));
layout.append(&create_label(
"crncystr",
get_langinfo(libc::CRNCYSTR).as_ref(),
));
*/
layout
}
fn main() {
gio::resources_register_include!("com.luminescent-dreams.locale.gresource")
.expect("failed to register resources");
let app = gtk::Application::builder()
.application_id("com.luminescent-dreams.locale")
.resource_base_path("/com/luminescent-dreams/dashboard")
.build();
app.connect_activate(move |app| {
let window = gtk::ApplicationWindow::new(app);
let layout = gtk::Box::builder()
.halign(gtk::Align::Start)
.orientation(gtk::Orientation::Vertical)
.spacing(8)
.build();
let menubar = gtk::Box::builder()
.halign(gtk::Align::End)
.orientation(gtk::Orientation::Horizontal)
.spacing(8)
.build();
let refresh_button = gtk::Button::builder().label("Refresh").build();
menubar.append(&refresh_button);
let data_layout = Arc::new(RwLock::new(render_data()));
layout.append(&menubar);
layout.append(&*data_layout.read().unwrap());
refresh_button.connect_clicked({
let layout = layout.clone();
let data_layout = data_layout.clone();
move |_| {
layout.remove(&*data_layout.read().unwrap());
let new_layout = render_data();
*data_layout.write().unwrap() = new_layout;
layout.append(&*data_layout.read().unwrap());
}
});
window.set_child(Some(&layout));
window.present();
});
let args: Vec<String> = env::args().collect();
ApplicationExtManual::run_with_args(&app, &args);
}
/*
println!("locale: {}", get_locale(libc::LC_ALL));
*/