mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2026-08-19 01:14:43 -05:00
feat: groom profiles (#986)
* upd new/rename profile * upd rule window * upd margins * switch activation tab to listboxes * update hooks tab
This commit is contained in:
@@ -240,7 +240,7 @@ impl Component for ProfileSelector {
|
||||
sender.input(ProfileSelectorMsg::ClosePopover);
|
||||
|
||||
let diag_controller = NewProfileDialog::builder()
|
||||
.launch(self.custom_profiles())
|
||||
.launch((self.custom_profiles(), root.clone().upcast::<gtk::Widget>()))
|
||||
.forward(sender.output_sender(), |(name, base)| {
|
||||
AppMsg::CreateProfile(name, base)
|
||||
});
|
||||
@@ -258,10 +258,7 @@ impl Component for ProfileSelector {
|
||||
let sender = sender.clone();
|
||||
if let ProfileRowType::Profile { name, .. } = profile.row.clone() {
|
||||
let stream = ProfileRenameDialog::builder()
|
||||
.launch((
|
||||
name.clone(),
|
||||
root.toplevel_window().expect("Widget not in a window"),
|
||||
))
|
||||
.launch((name.clone(), root.clone().upcast::<gtk::Widget>()))
|
||||
.into_stream();
|
||||
|
||||
sender.clone().oneshot_command(async move {
|
||||
@@ -299,7 +296,7 @@ impl Component for ProfileSelector {
|
||||
rule: rule.clone().unwrap_or_default(),
|
||||
hooks: hooks.clone(),
|
||||
auto_switch: *auto,
|
||||
root_window: root.toplevel_window().expect("Widget not in a window"),
|
||||
parent: root.clone().upcast::<gtk::Widget>(),
|
||||
};
|
||||
let rule_window = ProfileRuleWindow::builder().launch(params).into_stream();
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use crate::I18N;
|
||||
use adw::prelude::{AdwDialogExt, EntryRowExt, PreferencesPageExt, PreferencesRowExt};
|
||||
use gtk::prelude::*;
|
||||
use i18n_embed_fl::fl;
|
||||
use lact_schema::request::ProfileBase;
|
||||
use relm4::{
|
||||
Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmWidgetExt,
|
||||
Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmWidgetExt, css,
|
||||
};
|
||||
use relm4_components::simple_combo_box::SimpleComboBox;
|
||||
|
||||
pub struct NewProfileDialog {
|
||||
name_buffer: gtk::EntryBuffer,
|
||||
base_selector: Controller<SimpleComboBox<ProfileBase>>,
|
||||
parent: gtk::Widget,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -19,49 +20,54 @@ pub enum NewProfileDialogMsg {
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl Component for NewProfileDialog {
|
||||
type Init = Vec<String>;
|
||||
type Init = (Vec<String>, gtk::Widget);
|
||||
type Input = NewProfileDialogMsg;
|
||||
type Output = (String, ProfileBase);
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
gtk::Window {
|
||||
set_default_size: (250, 130),
|
||||
set_title: Some(&fl!(I18N, "create-profile")),
|
||||
set_hide_on_close: true,
|
||||
#[root]
|
||||
adw::Dialog {
|
||||
set_content_width: 420,
|
||||
set_title: &fl!(I18N, "create-profile"),
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
set_margin_all: 10,
|
||||
#[wrap(Some)]
|
||||
set_child = &adw::ToolbarView {
|
||||
add_top_bar = &adw::HeaderBar {},
|
||||
|
||||
gtk::Entry {
|
||||
set_placeholder_text: Some(&fl!(I18N, "name")),
|
||||
set_buffer: &model.name_buffer,
|
||||
},
|
||||
#[wrap(Some)]
|
||||
set_content = &adw::PreferencesPage {
|
||||
add = &adw::PreferencesGroup {
|
||||
#[name = "name_entry"]
|
||||
adw::EntryRow {
|
||||
set_title: &fl!(I18N, "name"),
|
||||
connect_entry_activated => NewProfileDialogMsg::Create,
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
set_margin_top: 10,
|
||||
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "profile-copy-from"),
|
||||
},
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "profile-copy-from"),
|
||||
set_hexpand: true,
|
||||
set_xalign: 0.0,
|
||||
},
|
||||
|
||||
#[local_ref]
|
||||
base_selector -> gtk::ComboBoxText {
|
||||
set_margin_horizontal: 5,
|
||||
set_hexpand: true,
|
||||
set_halign: gtk::Align::End,
|
||||
#[local_ref]
|
||||
base_selector -> gtk::ComboBoxText {
|
||||
set_valign: gtk::Align::Center,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
add_bottom_bar = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
set_hexpand: true,
|
||||
set_vexpand: true,
|
||||
set_valign: gtk::Align::End,
|
||||
set_spacing: 10,
|
||||
set_margin_horizontal: 10,
|
||||
set_margin_bottom: 10,
|
||||
|
||||
gtk::Button {
|
||||
set_label: &fl!(I18N, "cancel"),
|
||||
@@ -77,6 +83,7 @@ impl Component for NewProfileDialog {
|
||||
gtk::Button {
|
||||
set_label: &fl!(I18N, "create"),
|
||||
set_hexpand: true,
|
||||
add_css_class: css::SUGGESTED_ACTION,
|
||||
|
||||
connect_clicked => NewProfileDialogMsg::Create,
|
||||
},
|
||||
@@ -86,7 +93,7 @@ impl Component for NewProfileDialog {
|
||||
}
|
||||
|
||||
fn init(
|
||||
current_profiles: Self::Init,
|
||||
(current_profiles, parent): Self::Init,
|
||||
root: Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
@@ -102,32 +109,41 @@ impl Component for NewProfileDialog {
|
||||
|
||||
let model = Self {
|
||||
base_selector,
|
||||
name_buffer: gtk::EntryBuffer::default(),
|
||||
parent,
|
||||
};
|
||||
|
||||
let base_selector = model.base_selector.widget();
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
root.present();
|
||||
root.set_focus(Some(&widgets.name_entry));
|
||||
root.present(Some(&model.parent));
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>, root: &Self::Root) {
|
||||
fn update_with_view(
|
||||
&mut self,
|
||||
widgets: &mut Self::Widgets,
|
||||
msg: Self::Input,
|
||||
sender: ComponentSender<Self>,
|
||||
root: &Self::Root,
|
||||
) {
|
||||
match msg {
|
||||
NewProfileDialogMsg::Create => {
|
||||
if self.name_buffer.length() != 0
|
||||
if !widgets.name_entry.text().is_empty()
|
||||
&& let Some(selected) = self.base_selector.model().active_index
|
||||
{
|
||||
let base = self.base_selector.model().variants[selected].clone();
|
||||
sender
|
||||
.output((self.name_buffer.text().to_string(), base))
|
||||
.output((widgets.name_entry.text().to_string(), base))
|
||||
.unwrap();
|
||||
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.update_view(widgets, sender);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +1,106 @@
|
||||
use crate::I18N;
|
||||
use gtk::prelude::{
|
||||
BoxExt, DialogExt, DialogExtManual, EditableExt, EntryExt, GtkWindowExt, OrientableExt,
|
||||
WidgetExt,
|
||||
};
|
||||
use adw::prelude::{AdwDialogExt, EntryRowExt, PreferencesPageExt, PreferencesRowExt};
|
||||
use gtk::prelude::{BoxExt, ButtonExt, EditableExt, ObjectExt, OrientableExt, WidgetExt};
|
||||
use i18n_embed_fl::fl;
|
||||
use relm4::{ComponentParts, ComponentSender, RelmWidgetExt};
|
||||
use relm4::{ComponentParts, ComponentSender, RelmWidgetExt, css};
|
||||
|
||||
pub struct ProfileRenameDialog {}
|
||||
pub struct ProfileRenameDialog {
|
||||
old_name: String,
|
||||
parent: gtk::Widget,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ProfileRenameDialogMsg {
|
||||
Save,
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl relm4::SimpleComponent for ProfileRenameDialog {
|
||||
type Init = (String, gtk::Window);
|
||||
type Input = ();
|
||||
impl relm4::Component for ProfileRenameDialog {
|
||||
type Init = (String, gtk::Widget);
|
||||
type Input = ProfileRenameDialogMsg;
|
||||
type Output = String;
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
gtk::Dialog {
|
||||
set_default_size: (400, 50),
|
||||
set_title: Some(&fl!(I18N, "rename-profile")),
|
||||
set_transient_for: Some(&root_window),
|
||||
set_hide_on_close: true,
|
||||
connect_response[root, sender, name_entry] => move |_, response| {
|
||||
match response {
|
||||
gtk::ResponseType::Accept => {
|
||||
sender.output(name_entry.text().to_string()).unwrap();
|
||||
root.close();
|
||||
}
|
||||
gtk::ResponseType::Cancel => root.close(),
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
add_buttons: &[(&fl!(I18N, "cancel"), gtk::ResponseType::Cancel), (&fl!(I18N, "save"), gtk::ResponseType::Accept)],
|
||||
#[root]
|
||||
adw::Dialog {
|
||||
set_content_width: 420,
|
||||
set_title: &fl!(I18N, "rename-profile"),
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_margin_all: 5,
|
||||
set_spacing: 5,
|
||||
#[wrap(Some)]
|
||||
set_child = &adw::ToolbarView {
|
||||
add_top_bar = &adw::HeaderBar {},
|
||||
|
||||
gtk::Label {
|
||||
set_markup: &fl!(I18N, "rename-profile-from", old_name = old_name.clone()),
|
||||
#[wrap(Some)]
|
||||
set_content = &adw::PreferencesPage {
|
||||
add = &adw::PreferencesGroup {
|
||||
#[name = "name_entry"]
|
||||
adw::EntryRow {
|
||||
set_title: &fl!(I18N, "name"),
|
||||
set_text: &model.old_name,
|
||||
connect_entry_activated => ProfileRenameDialogMsg::Save,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
#[name = "name_entry"]
|
||||
gtk::Entry {
|
||||
set_text: &old_name,
|
||||
set_hexpand: true,
|
||||
connect_activate[root] => move |_| {
|
||||
root.response(gtk::ResponseType::Accept);
|
||||
}
|
||||
add_bottom_bar = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
set_margin_horizontal: 10,
|
||||
set_margin_bottom: 10,
|
||||
|
||||
gtk::Button {
|
||||
set_label: &fl!(I18N, "cancel"),
|
||||
set_hexpand: true,
|
||||
connect_clicked[root = root.downgrade()] => move |_| {
|
||||
if let Some(root) = root.upgrade() {
|
||||
root.close();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: &fl!(I18N, "save"),
|
||||
set_hexpand: true,
|
||||
add_css_class: css::SUGGESTED_ACTION,
|
||||
connect_clicked => ProfileRenameDialogMsg::Save,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
fn init(
|
||||
(old_name, root_window): Self::Init,
|
||||
(old_name, parent): Self::Init,
|
||||
root: Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let model = Self {};
|
||||
let model = Self { old_name, parent };
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
root.present();
|
||||
root.set_focus(Some(&widgets.name_entry));
|
||||
root.present(Some(&model.parent));
|
||||
|
||||
ComponentParts { widgets, model }
|
||||
}
|
||||
|
||||
fn update_with_view(
|
||||
&mut self,
|
||||
widgets: &mut Self::Widgets,
|
||||
msg: Self::Input,
|
||||
sender: ComponentSender<Self>,
|
||||
root: &Self::Root,
|
||||
) {
|
||||
match msg {
|
||||
ProfileRenameDialogMsg::Save => {
|
||||
sender
|
||||
.output(widgets.name_entry.text().to_string())
|
||||
.unwrap();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
self.update_view(widgets, sender);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@ pub mod profile_rule_row;
|
||||
|
||||
use crate::I18N;
|
||||
use crate::app::{APP_BROKER, msg::AppMsg};
|
||||
use gtk::prelude::ObjectExt;
|
||||
use adw::prelude::{AdwDialogExt, ObjectExt, PreferencesGroupExt, PreferencesPageExt};
|
||||
use gtk::{
|
||||
pango,
|
||||
prelude::{
|
||||
BoxExt, ButtonExt, CheckButtonExt, DialogExt, DialogExtManual, EntryBufferExtManual,
|
||||
EntryExt, GtkWindowExt, OrientableExt, WidgetExt,
|
||||
BoxExt, ButtonExt, CheckButtonExt, EntryBufferExtManual, EntryExt, OrientableExt, WidgetExt,
|
||||
},
|
||||
};
|
||||
use i18n_embed_fl::fl;
|
||||
@@ -16,6 +15,7 @@ use profile_rule_row::ProfileRuleRow;
|
||||
use relm4::{
|
||||
ComponentParts, ComponentSender, RelmObjectExt, RelmWidgetExt,
|
||||
binding::BoolBinding,
|
||||
css,
|
||||
prelude::{DynamicIndex, FactoryVecDeque},
|
||||
tokio::time::sleep,
|
||||
};
|
||||
@@ -41,7 +41,7 @@ pub struct ProfileEditParams {
|
||||
pub rule: ProfileRule,
|
||||
pub hooks: ProfileHooks,
|
||||
pub auto_switch: bool,
|
||||
pub root_window: gtk::Window,
|
||||
pub parent: gtk::Widget,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -61,59 +61,38 @@ impl relm4::Component for ProfileRuleWindow {
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
gtk::Dialog {
|
||||
set_default_size: (600, 300),
|
||||
set_title: Some(&fl!(I18N, "profile-rules")),
|
||||
set_transient_for: Some(&root_window),
|
||||
connect_response[root = root.downgrade(), sender] => move |_, response| {
|
||||
if let Some(root) = root.upgrade() {
|
||||
match response {
|
||||
gtk::ResponseType::Accept => {
|
||||
sender.input(ProfileRuleWindowMsg::Save);
|
||||
root.close();
|
||||
}
|
||||
gtk::ResponseType::Cancel => root.close(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
},
|
||||
#[root]
|
||||
adw::Dialog {
|
||||
set_content_width: 640,
|
||||
set_follows_content_size: true,
|
||||
set_title: &fl!(I18N, "profile-rules"),
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_margin_all: 5,
|
||||
|
||||
append = >k::StackSwitcher {
|
||||
set_stack: Some(&stack),
|
||||
#[wrap(Some)]
|
||||
set_child = &adw::ToolbarView {
|
||||
add_top_bar = &adw::HeaderBar {
|
||||
#[wrap(Some)]
|
||||
set_title_widget = >k::StackSwitcher {
|
||||
set_stack: Some(&stack),
|
||||
},
|
||||
},
|
||||
|
||||
#[name = "stack"]
|
||||
append = >k::Stack {
|
||||
add_titled[None, &fl!(I18N, "profile-activation")] = >k::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_margin_all: 5,
|
||||
#[wrap(Some)]
|
||||
set_content = >k::Stack {
|
||||
add_titled[None, &fl!(I18N, "profile-activation")] = &adw::PreferencesPage {
|
||||
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_markup: &format!("<span font_desc='11'><b>{}</b></span>", fl!(I18N, "profile-activation-desc", name = model.profile_name.as_str())),
|
||||
set_halign: gtk::Align::Start,
|
||||
set_margin_all: 10,
|
||||
},
|
||||
add = &adw::PreferencesGroup {
|
||||
set_title: &fl!(I18N, "profile-activation-desc", name = model.profile_name.as_str()),
|
||||
|
||||
gtk::Separator {},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_expand: true,
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_margin_all: 10,
|
||||
set_spacing: 10,
|
||||
gtk::ListBox {
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
add_css_class: css::BOXED_LIST,
|
||||
|
||||
#[name = "multi_or_checkbutton"]
|
||||
gtk::CheckButton {
|
||||
set_label: Some(&fl!(I18N, "any-rules-matched")),
|
||||
set_active: !matches!(rule, ProfileRule::And(_)),
|
||||
set_margin_all: 10,
|
||||
connect_toggled => ProfileRuleWindowMsg::Evaluate,
|
||||
},
|
||||
|
||||
@@ -122,133 +101,152 @@ impl relm4::Component for ProfileRuleWindow {
|
||||
set_label: Some(&fl!(I18N, "all-rules-matched")),
|
||||
set_group: Some(&multi_or_checkbutton),
|
||||
set_active: matches!(rule, ProfileRule::And(_)),
|
||||
set_margin_all: 10,
|
||||
connect_toggled => ProfileRuleWindowMsg::Evaluate,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Separator {},
|
||||
add = &adw::PreferencesGroup {
|
||||
set_title: &fl!(I18N, "profile-rules"),
|
||||
|
||||
#[local_ref]
|
||||
sub_rules_listview -> gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
#[wrap(Some)]
|
||||
set_header_suffix = >k::Button {
|
||||
set_icon_name: "list-add-symbolic",
|
||||
set_tooltip: &fl!(I18N, "profile-rules"),
|
||||
add_css_class: "flat",
|
||||
connect_clicked => ProfileRuleWindowMsg::AddSubrule,
|
||||
},
|
||||
|
||||
#[local_ref]
|
||||
sub_rules_listview -> gtk::ListBox {
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
add_css_class: css::BOXED_LIST,
|
||||
},
|
||||
},
|
||||
|
||||
add = &adw::PreferencesGroup {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
|
||||
gtk::Image {
|
||||
#[watch]
|
||||
set_icon_name: match model.currently_matches {
|
||||
true => Some("object-select-symbolic"),
|
||||
false => Some("list-remove-symbolic"),
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_icon_name: "list-add-symbolic",
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_markup: &if model.auto_switch {
|
||||
fl!(I18N, "activation-settings-status", matched = model.currently_matches.to_string())
|
||||
} else {
|
||||
format!(
|
||||
"<b>{}</b>",
|
||||
fl!(I18N, "activation-auto-switching-disabled")
|
||||
)
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
add_titled[None, &fl!(I18N, "profile-hooks")] = &adw::PreferencesPage {
|
||||
|
||||
add = &adw::PreferencesGroup {
|
||||
set_description: Some(&fl!(I18N, "profile-hook-command", cmd = model.profile_name.as_str())),
|
||||
|
||||
gtk::ListBox {
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
add_css_class: css::BOXED_LIST,
|
||||
|
||||
gtk::ListBoxRow {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
set_margin_all: 10,
|
||||
|
||||
gtk::CheckButton {
|
||||
set_label: Some(&fl!(I18N, "profile-hook-activated")),
|
||||
add_binding: (&model.activated_hook_enabled, "active"),
|
||||
set_size_group: &hook_command_size_group,
|
||||
},
|
||||
|
||||
gtk::Entry {
|
||||
add_binding: (&model.activated_hook_enabled, "sensitive"),
|
||||
set_buffer: &model.activated_hook,
|
||||
set_hexpand: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
gtk::ListBoxRow {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
set_margin_all: 10,
|
||||
|
||||
gtk::CheckButton {
|
||||
set_label: Some(&fl!(I18N, "profile-hook-deactivated")),
|
||||
add_binding: (&model.deactivated_hook_enabled, "active"),
|
||||
set_size_group: &hook_command_size_group,
|
||||
},
|
||||
|
||||
gtk::Entry {
|
||||
add_binding: (&model.deactivated_hook_enabled, "sensitive"),
|
||||
set_buffer: &model.deactivated_hook,
|
||||
set_hexpand: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
add = &adw::PreferencesGroup {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
|
||||
gtk::Image {
|
||||
set_icon_name: Some("dialog-warning-symbolic"),
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "profile-hook-note"),
|
||||
set_wrap: true,
|
||||
set_wrap_mode: pango::WrapMode::Word,
|
||||
add_css_class: "caption-heading",
|
||||
set_hexpand: true,
|
||||
set_halign: gtk::Align::End,
|
||||
connect_clicked => ProfileRuleWindowMsg::AddSubrule,
|
||||
},
|
||||
|
||||
gtk::Separator {},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_markup: &if model.auto_switch {
|
||||
fl!(I18N, "activation-settings-status", matched = model.currently_matches.to_string())
|
||||
} else {
|
||||
format!(
|
||||
"<b>{}</b>",
|
||||
fl!(I18N, "activation-auto-switching-disabled")
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Image {
|
||||
#[watch]
|
||||
set_icon_name: match model.currently_matches {
|
||||
true => Some("object-select-symbolic"),
|
||||
false => Some("list-remove-symbolic"),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Separator {},
|
||||
add_bottom_bar = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
set_margin_all: 10,
|
||||
set_halign: gtk::Align::End,
|
||||
|
||||
gtk::Button {
|
||||
set_label: &fl!(I18N, "cancel"),
|
||||
connect_clicked[root = root.downgrade()] => move |_| {
|
||||
if let Some(root) = root.upgrade() {
|
||||
root.close();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
add_titled[None, &fl!(I18N, "profile-hooks")] = >k::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_margin_all: 5,
|
||||
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_markup: &format!("<span font_desc='11'><b>{}</b></span>", fl!(I18N, "profile-hook-command", cmd = model.profile_name.as_str())),
|
||||
set_halign: gtk::Align::Start,
|
||||
set_margin_all: 10,
|
||||
},
|
||||
|
||||
gtk::Separator {},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
set_margin_vertical: 5,
|
||||
set_margin_horizontal: 10,
|
||||
|
||||
|
||||
gtk::CheckButton {
|
||||
set_label: Some(&fl!(I18N, "profile-hook-activated")),
|
||||
add_binding: (&model.activated_hook_enabled, "active"),
|
||||
set_size_group: &hook_command_size_group,
|
||||
},
|
||||
|
||||
gtk::Entry {
|
||||
add_binding: (&model.activated_hook_enabled, "sensitive"),
|
||||
set_buffer: &model.activated_hook,
|
||||
set_hexpand: true,
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
set_margin_vertical: 5,
|
||||
set_margin_horizontal: 10,
|
||||
|
||||
gtk::CheckButton {
|
||||
set_label: Some(&fl!(I18N, "profile-hook-deactivated")),
|
||||
add_binding: (&model.deactivated_hook_enabled, "active"),
|
||||
set_size_group: &hook_command_size_group,
|
||||
},
|
||||
|
||||
gtk::Entry {
|
||||
add_binding: (&model.deactivated_hook_enabled, "sensitive"),
|
||||
set_buffer: &model.deactivated_hook,
|
||||
set_hexpand: true,
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Separator {},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
set_margin_vertical: 5,
|
||||
set_margin_horizontal: 10,
|
||||
|
||||
gtk::Image {
|
||||
set_icon_name: Some("dialog-warning-symbolic"),
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "profile-hook-note"),
|
||||
set_wrap: true,
|
||||
set_wrap_mode: pango::WrapMode::Word,
|
||||
add_css_class: "caption-heading",
|
||||
set_hexpand: true,
|
||||
},
|
||||
}
|
||||
gtk::Button {
|
||||
set_label: &fl!(I18N, "save"),
|
||||
add_css_class: css::SUGGESTED_ACTION,
|
||||
connect_clicked => ProfileRuleWindowMsg::Save,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
add_buttons: &[("Cancel", gtk::ResponseType::Cancel), ("Save", gtk::ResponseType::Accept)],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +261,7 @@ impl relm4::Component for ProfileRuleWindow {
|
||||
rule,
|
||||
hooks,
|
||||
auto_switch,
|
||||
root_window,
|
||||
parent,
|
||||
} = params;
|
||||
|
||||
sender.command(move |_, shutdown| {
|
||||
@@ -307,7 +305,7 @@ impl relm4::Component for ProfileRuleWindow {
|
||||
let hook_command_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
|
||||
let widgets = view_output!();
|
||||
|
||||
root.present();
|
||||
root.present(Some(&parent));
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
@@ -347,6 +345,7 @@ impl relm4::Component for ProfileRuleWindow {
|
||||
self.get_hooks(),
|
||||
))
|
||||
.unwrap();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,167 +49,172 @@ impl relm4::factory::FactoryComponent for ProfileRuleRow {
|
||||
type Input = ProfileRuleRowMsg;
|
||||
type Output = ProfileRuleWindowMsg;
|
||||
type CommandOutput = ();
|
||||
type ParentWidget = gtk::Box;
|
||||
type ParentWidget = gtk::ListBox;
|
||||
|
||||
view! {
|
||||
gtk::Box {
|
||||
gtk::Label {
|
||||
set_hexpand: true,
|
||||
set_halign: gtk::Align::Start,
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
#[watch]
|
||||
set_markup: &format_rule(&self.get_configured_rule()),
|
||||
},
|
||||
gtk::ListBoxRow {
|
||||
gtk::Box {
|
||||
set_spacing: 10,
|
||||
set_margin_all: 12,
|
||||
|
||||
gtk::MenuButton {
|
||||
set_icon_name: "open-menu-symbolic",
|
||||
set_tooltip: &fl!(I18N, "edit-rule"),
|
||||
gtk::Label {
|
||||
set_hexpand: true,
|
||||
set_halign: gtk::Align::Start,
|
||||
set_ellipsize: pango::EllipsizeMode::End,
|
||||
#[watch]
|
||||
set_markup: &format_rule(&self.get_configured_rule()),
|
||||
},
|
||||
|
||||
#[wrap(Some)]
|
||||
set_popover: main_popover = >k::Popover {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
set_size_request: (300, 120),
|
||||
|
||||
gtk::StackSidebar {
|
||||
set_stack: &stack,
|
||||
},
|
||||
gtk::MenuButton {
|
||||
set_icon_name: "open-menu-symbolic",
|
||||
set_tooltip: &fl!(I18N, "edit-rule"),
|
||||
|
||||
#[wrap(Some)]
|
||||
set_popover: main_popover = >k::Popover {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
set_size_request: (300, 120),
|
||||
|
||||
#[name = "stack"]
|
||||
gtk::Stack {
|
||||
connect_visible_child_name_notify => ProfileRuleRowMsg::Changed,
|
||||
gtk::StackSidebar {
|
||||
set_stack: &stack,
|
||||
},
|
||||
|
||||
add_titled[Some(PROCESS_PAGE), &fl!(I18N, "profile-rule-process-tab")] = >k::Grid {
|
||||
set_row_spacing: 5,
|
||||
set_column_spacing: 5,
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
|
||||
attach[0, 0, 1, 1] = >k::Label {
|
||||
set_label: &fl!(I18N, "profile-rule-process-name"),
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
#[name = "stack"]
|
||||
gtk::Stack {
|
||||
connect_visible_child_name_notify => ProfileRuleRowMsg::Changed,
|
||||
|
||||
attach[2, 0, 1, 1] = >k::Entry {
|
||||
set_buffer: &self.process_name_buffer,
|
||||
set_hexpand: true,
|
||||
set_placeholder_text: Some("Cyberpunk2077.exe"),
|
||||
},
|
||||
add_titled[Some(PROCESS_PAGE), &fl!(I18N, "profile-rule-process-tab")] = >k::Grid {
|
||||
set_row_spacing: 5,
|
||||
set_column_spacing: 5,
|
||||
|
||||
attach[3, 0, 1, 1] = >k::MenuButton {
|
||||
set_icon_name: "view-list-symbolic",
|
||||
attach[0, 0, 1, 1] = >k::Label {
|
||||
set_label: &fl!(I18N, "profile-rule-process-name"),
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
|
||||
#[wrap(Some)]
|
||||
set_popover: process_filter_popover = >k::Popover {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
attach[2, 0, 1, 1] = >k::Entry {
|
||||
set_buffer: &self.process_name_buffer,
|
||||
set_hexpand: true,
|
||||
set_placeholder_text: Some("Cyberpunk2077.exe"),
|
||||
},
|
||||
|
||||
gtk::SearchEntry {
|
||||
connect_search_changed[sender] => move |entry| {
|
||||
sender.input(ProfileRuleRowMsg::ProcessFilterChanged(entry.text()));
|
||||
attach[3, 0, 1, 1] = >k::MenuButton {
|
||||
set_icon_name: "view-list-symbolic",
|
||||
|
||||
#[wrap(Some)]
|
||||
set_popover: process_filter_popover = >k::Popover {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
|
||||
gtk::SearchEntry {
|
||||
connect_search_changed[sender] => move |entry| {
|
||||
sender.input(ProfileRuleRowMsg::ProcessFilterChanged(entry.text()));
|
||||
},
|
||||
},
|
||||
|
||||
gtk::ScrolledWindow {
|
||||
set_size_request: (400, 350),
|
||||
|
||||
self.process_listview.view.clone() -> gtk::ListView {
|
||||
set_show_separators: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
gtk::ScrolledWindow {
|
||||
set_size_request: (400, 350),
|
||||
|
||||
self.process_listview.view.clone() -> gtk::ListView {
|
||||
set_show_separators: true,
|
||||
},
|
||||
}
|
||||
connect_visible_notify[sender] => move |_| {
|
||||
debug!("requesting profile watcher state");
|
||||
APP_BROKER.send(AppMsg::ReloadProfiles { state_sender: Some(sender.input_sender().clone())});
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
connect_visible_notify[sender] => move |_| {
|
||||
debug!("requesting profile watcher state");
|
||||
APP_BROKER.send(AppMsg::ReloadProfiles { state_sender: Some(sender.input_sender().clone())});
|
||||
},
|
||||
attach[0, 1, 1, 1] = >k::Label {
|
||||
set_label: &fl!(I18N, "profile-rule-args-contain"),
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
|
||||
|
||||
attach[1, 1, 1, 1]: filter_by_args_checkbutton = >k::CheckButton {
|
||||
connect_toggled => ProfileRuleRowMsg::Changed,
|
||||
add_binding: (&self.filter_by_args, "active"),
|
||||
},
|
||||
|
||||
attach[2, 1, 2, 1]: args_entry = >k::Entry {
|
||||
set_buffer: &self.args_buffer,
|
||||
set_hexpand: true,
|
||||
set_sensitive: false,
|
||||
add_binding: (&self.filter_by_args, "sensitive"),
|
||||
},
|
||||
},
|
||||
|
||||
attach[0, 1, 1, 1] = >k::Label {
|
||||
set_label: &fl!(I18N, "profile-rule-args-contain"),
|
||||
set_halign: gtk::Align::Start,
|
||||
add_titled[Some(GAMEMODE_PAGE), &fl!(I18N, "profile-rule-gamemode-tab")] = >k::Grid {
|
||||
set_row_spacing: 5,
|
||||
set_column_spacing: 10,
|
||||
|
||||
attach[0, 0, 1, 1] = >k::Label {
|
||||
set_label: &fl!(I18N, "profile-rule-specific-process"),
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
|
||||
attach[1, 0, 1, 1]: gamemode_filter_by_process_checkbutton = >k::CheckButton {
|
||||
connect_toggled => ProfileRuleRowMsg::Changed,
|
||||
add_binding: (&self.gamemode_filter_by_process, "active"),
|
||||
},
|
||||
|
||||
attach[2, 0, 1, 1]: gamemode_process_name_entry = >k::Entry {
|
||||
set_buffer: &self.process_name_buffer,
|
||||
set_hexpand: true,
|
||||
set_placeholder_text: Some("Cyberpunk2077.exe"),
|
||||
set_sensitive: false,
|
||||
add_binding: (&self.gamemode_filter_by_process, "sensitive"),
|
||||
},
|
||||
|
||||
attach[0, 1, 1, 1] = >k::Label {
|
||||
set_label: &fl!(I18N, "profile-rule-args-contain"),
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
|
||||
attach[1, 1, 1, 1]: gamemode_filter_by_args_checkbutton = >k::CheckButton {
|
||||
connect_toggled => ProfileRuleRowMsg::Changed,
|
||||
add_binding: (&self.filter_by_args, "active"),
|
||||
},
|
||||
|
||||
attach[2, 1, 1, 1]: gamemode_args_entry = >k::Entry {
|
||||
set_buffer: &self.args_buffer,
|
||||
set_hexpand: true,
|
||||
set_sensitive: false,
|
||||
add_binding: (&self.filter_by_args, "sensitive"),
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
attach[1, 1, 1, 1]: filter_by_args_checkbutton = >k::CheckButton {
|
||||
connect_toggled => ProfileRuleRowMsg::Changed,
|
||||
add_binding: (&self.filter_by_args, "active"),
|
||||
},
|
||||
|
||||
attach[2, 1, 2, 1]: args_entry = >k::Entry {
|
||||
set_buffer: &self.args_buffer,
|
||||
set_hexpand: true,
|
||||
set_sensitive: false,
|
||||
add_binding: (&self.filter_by_args, "sensitive"),
|
||||
},
|
||||
add_binding: (&self.selected_page, "visible-child-name"),
|
||||
},
|
||||
|
||||
add_titled[Some(GAMEMODE_PAGE), &fl!(I18N, "profile-rule-gamemode-tab")] = >k::Grid {
|
||||
set_row_spacing: 5,
|
||||
set_column_spacing: 10,
|
||||
|
||||
attach[0, 0, 1, 1] = >k::Label {
|
||||
set_label: &fl!(I18N, "profile-rule-specific-process"),
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
|
||||
attach[1, 0, 1, 1]: gamemode_filter_by_process_checkbutton = >k::CheckButton {
|
||||
connect_toggled => ProfileRuleRowMsg::Changed,
|
||||
add_binding: (&self.gamemode_filter_by_process, "active"),
|
||||
},
|
||||
|
||||
attach[2, 0, 1, 1]: gamemode_process_name_entry = >k::Entry {
|
||||
set_buffer: &self.process_name_buffer,
|
||||
set_hexpand: true,
|
||||
set_placeholder_text: Some("Cyberpunk2077.exe"),
|
||||
set_sensitive: false,
|
||||
add_binding: (&self.gamemode_filter_by_process, "sensitive"),
|
||||
},
|
||||
|
||||
attach[0, 1, 1, 1] = >k::Label {
|
||||
set_label: &fl!(I18N, "profile-rule-args-contain"),
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
|
||||
attach[1, 1, 1, 1]: gamemode_filter_by_args_checkbutton = >k::CheckButton {
|
||||
connect_toggled => ProfileRuleRowMsg::Changed,
|
||||
add_binding: (&self.filter_by_args, "active"),
|
||||
},
|
||||
|
||||
attach[2, 1, 1, 1]: gamemode_args_entry = >k::Entry {
|
||||
set_buffer: &self.args_buffer,
|
||||
set_hexpand: true,
|
||||
set_sensitive: false,
|
||||
add_binding: (&self.filter_by_args, "sensitive"),
|
||||
},
|
||||
},
|
||||
|
||||
add_binding: (&self.selected_page, "visible-child-name"),
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "OK",
|
||||
set_align: gtk::Align::End,
|
||||
set_expand: true,
|
||||
connect_clicked[main_popover] => move |_| {
|
||||
main_popover.popdown();
|
||||
gtk::Button {
|
||||
set_label: "OK",
|
||||
set_align: gtk::Align::End,
|
||||
set_expand: true,
|
||||
connect_clicked[main_popover] => move |_| {
|
||||
main_popover.popdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_icon_name: "list-remove-symbolic",
|
||||
set_tooltip: &fl!(I18N, "remove-rule"),
|
||||
connect_clicked[sender, index] => move |_| {
|
||||
let _ = sender.output(ProfileRuleWindowMsg::RemoveSubrule(index.clone()));
|
||||
}
|
||||
gtk::Button {
|
||||
set_icon_name: "list-remove-symbolic",
|
||||
set_tooltip: &fl!(I18N, "remove-rule"),
|
||||
connect_clicked[sender, index] => move |_| {
|
||||
let _ = sender.output(ProfileRuleWindowMsg::RemoveSubrule(index.clone()));
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user