mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
Add widgets for translating data
This commit is contained in:
parent
3c8f5b8c31
commit
783d548bc2
10 changed files with 350 additions and 4 deletions
15
data/ui/person_editor.blp
Normal file
15
data/ui/person_editor.blp
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $MusicusPersonEditor : Adw.NavigationPage {
|
||||||
|
title: _("Person");
|
||||||
|
|
||||||
|
Adw.ToolbarView {
|
||||||
|
[top]
|
||||||
|
Adw.HeaderBar header_bar {}
|
||||||
|
|
||||||
|
Adw.Clamp {
|
||||||
|
$MusicusTranslationSection name_section {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
data/ui/translation_entry.blp
Normal file
59
data/ui/translation_entry.blp
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $MusicusTranslationEntry : Adw.EntryRow {
|
||||||
|
title: _("Translated name");
|
||||||
|
|
||||||
|
Gtk.Button {
|
||||||
|
icon-name: "edit-delete-symbolic";
|
||||||
|
valign: center;
|
||||||
|
clicked => $remove() swapped;
|
||||||
|
styles ["flat"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Button {
|
||||||
|
valign: center;
|
||||||
|
clicked => $open_lang_popover() swapped;
|
||||||
|
|
||||||
|
Gtk.Box {
|
||||||
|
spacing: 6;
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: bind lang_entry.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Image {
|
||||||
|
icon-name: "pan-down-symbolic";
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Popover lang_popover {
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: vertical;
|
||||||
|
spacing: 6;
|
||||||
|
margin-start: 6;
|
||||||
|
margin-end: 6;
|
||||||
|
margin-top: 6;
|
||||||
|
margin-bottom: 6;
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: _("Language code");
|
||||||
|
halign: start;
|
||||||
|
styles ["heading"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
width-request: 200;
|
||||||
|
label: _("Enter an <a href=\"https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes\">ISO 639</a> two-letter language code identifying the language this translation uses.");
|
||||||
|
use-markup: true;
|
||||||
|
wrap: true;
|
||||||
|
max-width-chars: 40;
|
||||||
|
halign: start;
|
||||||
|
styles ["dim-label"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Entry lang_entry {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
data/ui/translation_section.blp
Normal file
19
data/ui/translation_section.blp
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $MusicusTranslationSection : Adw.PreferencesGroup {
|
||||||
|
title: _("Name");
|
||||||
|
header-suffix: Gtk.Button add_button {
|
||||||
|
styles ["flat"]
|
||||||
|
clicked => $add_translation() swapped;
|
||||||
|
|
||||||
|
Adw.ButtonContent {
|
||||||
|
icon-name: "list-add-symbolic";
|
||||||
|
label: _("Translate");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Adw.EntryRow entry_row {
|
||||||
|
title: _("Name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,9 +39,9 @@ pub fn connect(file_name: &str) -> Result<SqliteConnection> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single translated string value.
|
/// A single translated string value.
|
||||||
#[derive(Serialize, Deserialize, AsExpression, FromSqlRow, Clone, Debug)]
|
#[derive(Serialize, Deserialize, AsExpression, FromSqlRow, Clone, Default, Debug)]
|
||||||
#[diesel(sql_type = Text)]
|
#[diesel(sql_type = Text)]
|
||||||
pub struct TranslatedString(HashMap<String, String>);
|
pub struct TranslatedString(pub HashMap<String, String>);
|
||||||
|
|
||||||
impl TranslatedString {
|
impl TranslatedString {
|
||||||
/// Get the best translation for the user's current locale.
|
/// Get the best translation for the user's current locale.
|
||||||
|
|
|
||||||
3
src/editor/mod.rs
Normal file
3
src/editor/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod person_editor;
|
||||||
|
pub mod translation_entry;
|
||||||
|
pub mod translation_section;
|
||||||
45
src/editor/person_editor.rs
Normal file
45
src/editor/person_editor.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use adw::{prelude::*, subclass::prelude::*};
|
||||||
|
use gtk::glib;
|
||||||
|
|
||||||
|
use crate::editor::translation_section::MusicusTranslationSection;
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
||||||
|
#[template(file = "data/ui/person_editor.blp")]
|
||||||
|
pub struct MusicusPersonEditor {}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for MusicusPersonEditor {
|
||||||
|
const NAME: &'static str = "MusicusPersonEditor";
|
||||||
|
type Type = super::MusicusPersonEditor;
|
||||||
|
type ParentType = adw::NavigationPage;
|
||||||
|
|
||||||
|
fn class_init(klass: &mut Self::Class) {
|
||||||
|
MusicusTranslationSection::static_type();
|
||||||
|
klass.bind_template();
|
||||||
|
klass.bind_template_instance_callbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
obj.init_template();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for MusicusPersonEditor {}
|
||||||
|
impl WidgetImpl for MusicusPersonEditor {}
|
||||||
|
impl NavigationPageImpl for MusicusPersonEditor {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct MusicusPersonEditor(ObjectSubclass<imp::MusicusPersonEditor>)
|
||||||
|
@extends gtk::Widget, adw::NavigationPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
|
impl MusicusPersonEditor {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
glib::Object::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/editor/translation_entry.rs
Normal file
94
src/editor/translation_entry.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
use adw::{prelude::*, subclass::prelude::*};
|
||||||
|
use gtk::glib::{self, subclass::Signal};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
||||||
|
#[template(file = "data/ui/translation_entry.blp")]
|
||||||
|
pub struct MusicusTranslationEntry {
|
||||||
|
#[template_child]
|
||||||
|
pub lang_popover: TemplateChild<gtk::Popover>,
|
||||||
|
#[template_child]
|
||||||
|
pub lang_entry: TemplateChild<gtk::Entry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for MusicusTranslationEntry {
|
||||||
|
const NAME: &'static str = "MusicusTranslationEntry";
|
||||||
|
type Type = super::MusicusTranslationEntry;
|
||||||
|
type ParentType = adw::EntryRow;
|
||||||
|
|
||||||
|
fn class_init(klass: &mut Self::Class) {
|
||||||
|
klass.bind_template();
|
||||||
|
klass.bind_template_instance_callbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
obj.init_template();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for MusicusTranslationEntry {
|
||||||
|
fn signals() -> &'static [Signal] {
|
||||||
|
static SIGNALS: Lazy<Vec<Signal>> =
|
||||||
|
Lazy::new(|| vec![Signal::builder("remove").build()]);
|
||||||
|
|
||||||
|
SIGNALS.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for MusicusTranslationEntry {}
|
||||||
|
impl ListBoxRowImpl for MusicusTranslationEntry {}
|
||||||
|
impl PreferencesRowImpl for MusicusTranslationEntry {}
|
||||||
|
impl EntryRowImpl for MusicusTranslationEntry {}
|
||||||
|
impl EditableImpl for MusicusTranslationEntry {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct MusicusTranslationEntry(ObjectSubclass<imp::MusicusTranslationEntry>)
|
||||||
|
@extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::EntryRow,
|
||||||
|
@implements gtk::Editable;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
|
impl MusicusTranslationEntry {
|
||||||
|
pub fn new(lang: &str, translation: &str) -> Self {
|
||||||
|
let obj: Self = glib::Object::new();
|
||||||
|
obj.set_text(translation);
|
||||||
|
obj.imp().lang_entry.set_text(lang);
|
||||||
|
obj
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect_remove<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||||
|
self.connect_local("remove", true, move |values| {
|
||||||
|
let obj = values[0].get::<Self>().unwrap();
|
||||||
|
f(&obj);
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lang(&self) -> String {
|
||||||
|
self.imp().lang_entry.text().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translation(&self) -> String {
|
||||||
|
self.imp().text().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn open_lang_popover(&self, _: >k::Button) {
|
||||||
|
self.imp().lang_popover.popup();
|
||||||
|
self.imp().lang_entry.grab_focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn remove(&self, _: >k::Button) {
|
||||||
|
self.emit_by_name::<()>("remove", &[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/editor/translation_section.rs
Normal file
103
src/editor/translation_section.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use adw::{prelude::*, subclass::prelude::*};
|
||||||
|
use gtk::glib;
|
||||||
|
|
||||||
|
use crate::db::TranslatedString;
|
||||||
|
use crate::editor::translation_entry::MusicusTranslationEntry;
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
||||||
|
#[template(file = "data/ui/translation_section.blp")]
|
||||||
|
pub struct MusicusTranslationSection {
|
||||||
|
#[template_child]
|
||||||
|
pub entry_row: TemplateChild<adw::EntryRow>,
|
||||||
|
|
||||||
|
pub translation_entries: RefCell<Vec<MusicusTranslationEntry>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for MusicusTranslationSection {
|
||||||
|
const NAME: &'static str = "MusicusTranslationSection";
|
||||||
|
type Type = super::MusicusTranslationSection;
|
||||||
|
type ParentType = adw::PreferencesGroup;
|
||||||
|
|
||||||
|
fn class_init(klass: &mut Self::Class) {
|
||||||
|
klass.bind_template();
|
||||||
|
klass.bind_template_instance_callbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
obj.init_template();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for MusicusTranslationSection {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for MusicusTranslationSection {}
|
||||||
|
impl PreferencesGroupImpl for MusicusTranslationSection {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct MusicusTranslationSection(ObjectSubclass<imp::MusicusTranslationSection>)
|
||||||
|
@extends gtk::Widget, adw::PreferencesGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
|
impl MusicusTranslationSection {
|
||||||
|
pub fn new(translation: TranslatedString) -> Self {
|
||||||
|
let obj: Self = glib::Object::new();
|
||||||
|
let mut translation = translation.0;
|
||||||
|
|
||||||
|
obj.imp()
|
||||||
|
.entry_row
|
||||||
|
.set_text(&translation.remove("generic").unwrap_or_default());
|
||||||
|
|
||||||
|
for (lang, translation) in translation {
|
||||||
|
obj.add_entry(&lang, &translation);
|
||||||
|
}
|
||||||
|
|
||||||
|
obj
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn add_translation(&self, _: >k::Button) {
|
||||||
|
self.add_entry(&util::LANG, &self.imp().entry_row.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translation(&self) -> TranslatedString {
|
||||||
|
let imp = self.imp();
|
||||||
|
let mut translation = HashMap::<String, String>::new();
|
||||||
|
|
||||||
|
translation.insert(String::from("generic"), imp.entry_row.text().into());
|
||||||
|
for entry in &*imp.translation_entries.borrow() {
|
||||||
|
translation.insert(entry.lang(), entry.translation());
|
||||||
|
}
|
||||||
|
|
||||||
|
TranslatedString(translation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_entry(&self, lang: &str, translation: &str) {
|
||||||
|
let entry = MusicusTranslationEntry::new(lang, translation);
|
||||||
|
|
||||||
|
let obj = self.clone();
|
||||||
|
entry.connect_remove(move |entry| {
|
||||||
|
let mut entries = obj.imp().translation_entries.borrow_mut();
|
||||||
|
if let Some(index) = entries.iter().position(|e| e == entry) {
|
||||||
|
entries.remove(index);
|
||||||
|
}
|
||||||
|
obj.remove(entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.add(&entry);
|
||||||
|
self.imp().translation_entries.borrow_mut().push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::library::MusicusLibrary;
|
|
||||||
use adw::{
|
use adw::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
subclass::{navigation_page::NavigationPageImpl, prelude::*},
|
subclass::{navigation_page::NavigationPageImpl, prelude::*},
|
||||||
|
|
@ -6,6 +5,9 @@ use adw::{
|
||||||
use gtk::glib::{self, Properties};
|
use gtk::glib::{self, Properties};
|
||||||
use std::cell::OnceCell;
|
use std::cell::OnceCell;
|
||||||
|
|
||||||
|
use crate::editor::person_editor::MusicusPersonEditor;
|
||||||
|
use crate::library::MusicusLibrary;
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
@ -34,7 +36,12 @@ mod imp {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::derived_properties]
|
#[glib::derived_properties]
|
||||||
impl ObjectImpl for LibraryManager {}
|
impl ObjectImpl for LibraryManager {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
self.obj().set_child(Some(&MusicusPersonEditor::new()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WidgetImpl for LibraryManager {}
|
impl WidgetImpl for LibraryManager {}
|
||||||
impl NavigationPageImpl for LibraryManager {}
|
impl NavigationPageImpl for LibraryManager {}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
mod application;
|
mod application;
|
||||||
mod config;
|
mod config;
|
||||||
mod db;
|
mod db;
|
||||||
|
mod editor;
|
||||||
mod home_page;
|
mod home_page;
|
||||||
mod library_manager;
|
mod library_manager;
|
||||||
mod library;
|
mod library;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue