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.
|
||||
#[derive(Serialize, Deserialize, AsExpression, FromSqlRow, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, AsExpression, FromSqlRow, Clone, Default, Debug)]
|
||||
#[diesel(sql_type = Text)]
|
||||
pub struct TranslatedString(HashMap<String, String>);
|
||||
pub struct TranslatedString(pub HashMap<String, String>);
|
||||
|
||||
impl TranslatedString {
|
||||
/// 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::{
|
||||
prelude::*,
|
||||
subclass::{navigation_page::NavigationPageImpl, prelude::*},
|
||||
|
|
@ -6,6 +5,9 @@ use adw::{
|
|||
use gtk::glib::{self, Properties};
|
||||
use std::cell::OnceCell;
|
||||
|
||||
use crate::editor::person_editor::MusicusPersonEditor;
|
||||
use crate::library::MusicusLibrary;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
|
|
@ -34,7 +36,12 @@ mod imp {
|
|||
}
|
||||
|
||||
#[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 NavigationPageImpl for LibraryManager {}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
mod application;
|
||||
mod config;
|
||||
mod db;
|
||||
mod editor;
|
||||
mod home_page;
|
||||
mod library_manager;
|
||||
mod library;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue