popover: Move Emoji and Terminal to ui file
This allows them to be translated via po so we don't have to maintain the translation list by hand or care about empty translation files. The only downside is that the "overlay layouts" in OVERLAY_NAMES need to match the ones in the ui file but since a missing one is a clean crash when selected there's little potential for subtle breackage. We could even ensure consistency by comparing the two at run time but they change only slowly this is not much of an issue (compared to how e.g. new languages can be added now).
This commit is contained in:
@ -1,7 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- Currently only serves translations until we make popover.rs use it -->
|
|
||||||
<interface>
|
<interface>
|
||||||
<menu>
|
<menu id="app-menu">
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Emoji</attribute>
|
||||||
|
<attribute name="action">layout</attribute>
|
||||||
|
<attribute name="target">emoji</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Terminal</attribute>
|
||||||
|
<attribute name="action">layout</attribute>
|
||||||
|
<attribute name="target">terminal</attribute>
|
||||||
|
</item>
|
||||||
<section>
|
<section>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">Keyboard Settings</attribute>
|
<attribute name="label" translatable="yes">Keyboard Settings</attribute>
|
||||||
|
|||||||
119
src/popover.rs
119
src/popover.rs
@ -20,10 +20,9 @@ use gio::SimpleActionExt;
|
|||||||
use glib::translate::FromGlibPtrNone;
|
use glib::translate::FromGlibPtrNone;
|
||||||
use glib::variant::ToVariant;
|
use glib::variant::ToVariant;
|
||||||
#[cfg(not(feature = "gtk_v0_5"))]
|
#[cfg(not(feature = "gtk_v0_5"))]
|
||||||
use gtk::BuilderExtManual;
|
use gtk::prelude::*;
|
||||||
use gtk::PopoverExt;
|
use gtk::PopoverExt;
|
||||||
use gtk::WidgetExt;
|
use gtk::WidgetExt;
|
||||||
use std::io::Write;
|
|
||||||
use ::logging::Warn;
|
use ::logging::Warn;
|
||||||
|
|
||||||
mod c {
|
mod c {
|
||||||
@ -111,46 +110,6 @@ mod variants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_menu_builder(inputs: Vec<(&str, OwnedTranslation)>) -> gtk::Builder {
|
|
||||||
let mut xml: Vec<u8> = Vec::new();
|
|
||||||
writeln!(
|
|
||||||
xml,
|
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
|
||||||
<interface>
|
|
||||||
<menu id=\"app-menu\">
|
|
||||||
<section>"
|
|
||||||
).unwrap();
|
|
||||||
for (input_name, translation) in inputs {
|
|
||||||
writeln!(
|
|
||||||
xml,
|
|
||||||
"
|
|
||||||
<item>
|
|
||||||
<attribute name=\"label\" translatable=\"yes\">{}</attribute>
|
|
||||||
<attribute name=\"action\">layout</attribute>
|
|
||||||
<attribute name=\"target\">{}</attribute>
|
|
||||||
</item>",
|
|
||||||
translation.0,
|
|
||||||
input_name,
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
writeln!(
|
|
||||||
xml,
|
|
||||||
"
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name=\"label\" translatable=\"yes\">Keyboard Settings</attribute>
|
|
||||||
<attribute name=\"action\">settings</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
</menu>
|
|
||||||
</interface>"
|
|
||||||
).unwrap();
|
|
||||||
gtk::Builder::new_from_string(
|
|
||||||
&String::from_utf8(xml).expect("Bad menu definition")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_settings(schema_name: &str) -> Option<gio::Settings> {
|
fn get_settings(schema_name: &str) -> Option<gio::Settings> {
|
||||||
let mut error_handler = logging::Print{};
|
let mut error_handler = logging::Print{};
|
||||||
gio::SettingsSchemaSource::get_default()
|
gio::SettingsSchemaSource::get_default()
|
||||||
@ -350,12 +309,12 @@ pub fn show(
|
|||||||
.chain(overlay_layouts)
|
.chain(overlay_layouts)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let translated_names = translate_layout_names(&all_layouts);
|
let translated_names = translate_layout_names(&system_layouts);
|
||||||
|
|
||||||
// sorted collection of human and machine names
|
// sorted collection of language layouts
|
||||||
let mut human_names: Vec<(OwnedTranslation, LayoutId)> = translated_names
|
let mut human_names: Vec<(OwnedTranslation, LayoutId)> = translated_names
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(all_layouts.clone().into_iter())
|
.zip(system_layouts.clone().into_iter())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
human_names.sort_unstable_by(|(tr_a, layout_a), (tr_b, layout_b)| {
|
human_names.sort_unstable_by(|(tr_a, layout_a), (tr_b, layout_b)| {
|
||||||
@ -367,32 +326,14 @@ pub fn show(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// GVariant doesn't natively support `enum`s,
|
let builder = gtk::Builder::new_from_resource("/sm/puri/squeekboard/popup.ui");
|
||||||
// so the `choices` vector will serve as a lookup table.
|
let model: gio::Menu = builder.get_object("app-menu").unwrap();
|
||||||
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();
|
|
||||||
|
|
||||||
|
for (tr, l) in human_names.iter().rev() {
|
||||||
let builder = make_menu_builder(
|
let detailed_action = format!("layout::{}", l.get_name());
|
||||||
choices_with_translations.iter()
|
let item = gio::MenuItem::new(Some(&tr.0), Some(detailed_action.as_str()));
|
||||||
.map(|(id, (translation, _))| (id.as_str(), (*translation).clone()))
|
model.prepend_item (&item);
|
||||||
.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();
|
|
||||||
|
|
||||||
let menu = gtk::Popover::new_from_model(Some(&window), &model);
|
let menu = gtk::Popover::new_from_model(Some(&window), &model);
|
||||||
menu.set_pointing_to(>k::Rectangle {
|
menu.set_pointing_to(>k::Rectangle {
|
||||||
@ -403,32 +344,36 @@ pub fn show(
|
|||||||
});
|
});
|
||||||
menu.set_constrain_to(gtk::PopoverConstraint::None);
|
menu.set_constrain_to(gtk::PopoverConstraint::None);
|
||||||
|
|
||||||
|
let action_group = gio::SimpleActionGroup::new();
|
||||||
|
|
||||||
if let Some(current_layout) = get_current_layout(manager, &system_layouts) {
|
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(
|
.find(
|
||||||
|(_id, layout)| layout == ¤t_layout
|
|l| l.get_name() == current_layout.get_name()
|
||||||
).unwrap()
|
).unwrap()
|
||||||
.0.to_variant();
|
.get_name();
|
||||||
|
log_print!(logging::Level::Debug, "Current Layout {}", current_layout_name);
|
||||||
|
|
||||||
let layout_action = gio::SimpleAction::new_stateful(
|
let layout_action = gio::SimpleAction::new_stateful(
|
||||||
"layout",
|
"layout",
|
||||||
Some(current_name_variant.type_()),
|
Some(current_layout_name.to_variant().type_()),
|
||||||
¤t_name_variant,
|
¤t_layout_name.to_variant()
|
||||||
);
|
);
|
||||||
|
|
||||||
let menu_inner = menu.clone();
|
let menu_inner = menu.clone();
|
||||||
layout_action.connect_change_state(move |_action, state| {
|
layout_action.connect_change_state(move |_action, state| {
|
||||||
match state {
|
match state {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
|
log_print!(logging::Level::Debug, "Selected layout {}", v);
|
||||||
v.get::<String>()
|
v.get::<String>()
|
||||||
.or_print(
|
.or_print(
|
||||||
logging::Problem::Bug,
|
logging::Problem::Bug,
|
||||||
&format!("Variant is not string: {:?}", v)
|
&format!("Variant is not string: {:?}", v)
|
||||||
)
|
)
|
||||||
.map(|state| {
|
.map(|state| {
|
||||||
let (_id, layout) = choices.iter()
|
let layout = all_layouts.iter()
|
||||||
.find(
|
.find(
|
||||||
|choices| state == choices.0
|
|choices| state == choices.get_name()
|
||||||
).unwrap();
|
).unwrap();
|
||||||
set_visible_layout(
|
set_visible_layout(
|
||||||
manager,
|
manager,
|
||||||
@ -443,20 +388,18 @@ pub fn show(
|
|||||||
};
|
};
|
||||||
menu_inner.popdown();
|
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(&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.bind_model(Some(&model), Some("popup"));
|
||||||
menu.popup();
|
menu.popup();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user