Update window title dynamically for WCAG 2.4.8 Location compliance
This commit is contained in:
272
src/window.rs
272
src/window.rs
@@ -8,13 +8,18 @@ use std::time::Instant;
|
|||||||
use crate::config::APP_ID;
|
use crate::config::APP_ID;
|
||||||
use crate::core::database::Database;
|
use crate::core::database::Database;
|
||||||
use crate::core::discovery;
|
use crate::core::discovery;
|
||||||
|
use crate::core::fuse;
|
||||||
use crate::core::inspector;
|
use crate::core::inspector;
|
||||||
use crate::core::orphan;
|
use crate::core::orphan;
|
||||||
|
use crate::core::wayland;
|
||||||
|
use crate::i18n::{i18n, ni18n_f};
|
||||||
|
use crate::ui::cleanup_wizard;
|
||||||
use crate::ui::dashboard;
|
use crate::ui::dashboard;
|
||||||
use crate::ui::detail_view;
|
use crate::ui::detail_view;
|
||||||
use crate::ui::duplicate_dialog;
|
use crate::ui::duplicate_dialog;
|
||||||
use crate::ui::library_view::{LibraryState, LibraryView};
|
use crate::ui::library_view::{LibraryState, LibraryView};
|
||||||
use crate::ui::preferences;
|
use crate::ui::preferences;
|
||||||
|
use crate::ui::security_report;
|
||||||
use crate::ui::update_dialog;
|
use crate::ui::update_dialog;
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
@@ -78,6 +83,19 @@ glib::wrapper! {
|
|||||||
gtk::Native, gtk::Root, gtk::ShortcutManager;
|
gtk::Native, gtk::Root, gtk::ShortcutManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn shortcut_row(accel: &str, description: &str) -> adw::ActionRow {
|
||||||
|
let row = adw::ActionRow::builder()
|
||||||
|
.title(description)
|
||||||
|
.build();
|
||||||
|
let accel_label = gtk::Label::builder()
|
||||||
|
.label(accel)
|
||||||
|
.css_classes(["monospace", "dimmed"])
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.build();
|
||||||
|
row.add_suffix(&accel_label);
|
||||||
|
row
|
||||||
|
}
|
||||||
|
|
||||||
impl DriftwoodWindow {
|
impl DriftwoodWindow {
|
||||||
pub fn new(app: &crate::application::DriftwoodApplication) -> Self {
|
pub fn new(app: &crate::application::DriftwoodApplication) -> Self {
|
||||||
glib::Object::builder()
|
glib::Object::builder()
|
||||||
@@ -111,17 +129,20 @@ impl DriftwoodWindow {
|
|||||||
fn setup_ui(&self) {
|
fn setup_ui(&self) {
|
||||||
// Build the hamburger menu model
|
// Build the hamburger menu model
|
||||||
let menu = gio::Menu::new();
|
let menu = gio::Menu::new();
|
||||||
menu.append(Some("Dashboard"), Some("win.dashboard"));
|
menu.append(Some(&i18n("Dashboard")), Some("win.dashboard"));
|
||||||
menu.append(Some("Preferences"), Some("win.preferences"));
|
menu.append(Some(&i18n("Preferences")), Some("win.preferences"));
|
||||||
|
|
||||||
let section2 = gio::Menu::new();
|
let section2 = gio::Menu::new();
|
||||||
section2.append(Some("Scan for AppImages"), Some("win.scan"));
|
section2.append(Some(&i18n("Scan for AppImages")), Some("win.scan"));
|
||||||
section2.append(Some("Check for Updates"), Some("win.check-updates"));
|
section2.append(Some(&i18n("Check for Updates")), Some("win.check-updates"));
|
||||||
section2.append(Some("Find Duplicates"), Some("win.find-duplicates"));
|
section2.append(Some(&i18n("Find Duplicates")), Some("win.find-duplicates"));
|
||||||
|
section2.append(Some(&i18n("Security Report")), Some("win.security-report"));
|
||||||
|
section2.append(Some(&i18n("Disk Cleanup")), Some("win.cleanup"));
|
||||||
menu.append_section(None, §ion2);
|
menu.append_section(None, §ion2);
|
||||||
|
|
||||||
let section3 = gio::Menu::new();
|
let section3 = gio::Menu::new();
|
||||||
section3.append(Some("About Driftwood"), Some("app.about"));
|
section3.append(Some(&i18n("Keyboard Shortcuts")), Some("win.show-shortcuts"));
|
||||||
|
section3.append(Some(&i18n("About Driftwood")), Some("app.about"));
|
||||||
menu.append_section(None, §ion3);
|
menu.append_section(None, §ion3);
|
||||||
|
|
||||||
// Library view (contains header bar, search, grid/list, empty state)
|
// Library view (contains header bar, search, grid/list, empty state)
|
||||||
@@ -159,6 +180,42 @@ impl DriftwoodWindow {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh library view when navigating back from detail
|
||||||
|
// (in case integration or other state changed)
|
||||||
|
{
|
||||||
|
let db = self.database().clone();
|
||||||
|
let window_weak = self.downgrade();
|
||||||
|
navigation_view.connect_popped(move |_nav, page| {
|
||||||
|
if page.tag().as_deref() == Some("detail") {
|
||||||
|
if let Some(window) = window_weak.upgrade() {
|
||||||
|
// Update window title for accessibility (WCAG 2.4.8)
|
||||||
|
window.set_title(Some("Driftwood"));
|
||||||
|
|
||||||
|
let lib_view = window.imp().library_view.get().unwrap();
|
||||||
|
match db.get_all_appimages() {
|
||||||
|
Ok(records) => lib_view.populate(records),
|
||||||
|
Err(_) => lib_view.set_state(LibraryState::Empty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update window title when navigating to sub-pages (WCAG 2.4.8 Location)
|
||||||
|
{
|
||||||
|
let window_weak = self.downgrade();
|
||||||
|
navigation_view.connect_pushed(move |nav| {
|
||||||
|
if let Some(window) = window_weak.upgrade() {
|
||||||
|
if let Some(page) = nav.visible_page() {
|
||||||
|
let page_title = page.title();
|
||||||
|
if !page_title.is_empty() {
|
||||||
|
window.set_title(Some(&format!("Driftwood - {}", page_title)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Store references
|
// Store references
|
||||||
self.imp()
|
self.imp()
|
||||||
.toast_overlay
|
.toast_overlay
|
||||||
@@ -214,14 +271,17 @@ impl DriftwoodWindow {
|
|||||||
match result {
|
match result {
|
||||||
Ok(Ok(summary)) => {
|
Ok(Ok(summary)) => {
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"Cleaned {} desktop entries, {} icons",
|
"{} {} {}, {} {}",
|
||||||
|
i18n("Cleaned"),
|
||||||
summary.entries_removed,
|
summary.entries_removed,
|
||||||
|
i18n("desktop entries"),
|
||||||
summary.icons_removed,
|
summary.icons_removed,
|
||||||
|
i18n("icons"),
|
||||||
);
|
);
|
||||||
toast_ref.add_toast(adw::Toast::new(&msg));
|
toast_ref.add_toast(adw::Toast::new(&msg));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
toast_ref.add_toast(adw::Toast::new("Failed to clean orphaned entries"));
|
toast_ref.add_toast(adw::Toast::new(&i18n("Failed to clean orphaned entries")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -239,9 +299,8 @@ impl DriftwoodWindow {
|
|||||||
// Check for updates action
|
// Check for updates action
|
||||||
let updates_toast = self.imp().toast_overlay.get().unwrap().clone();
|
let updates_toast = self.imp().toast_overlay.get().unwrap().clone();
|
||||||
let check_updates_action = gio::ActionEntry::builder("check-updates")
|
let check_updates_action = gio::ActionEntry::builder("check-updates")
|
||||||
.activate(move |window: &Self, _, _| {
|
.activate(move |_window: &Self, _, _| {
|
||||||
let toast_ref = updates_toast.clone();
|
let toast_ref = updates_toast.clone();
|
||||||
let db = window.database().clone();
|
|
||||||
glib::spawn_future_local(async move {
|
glib::spawn_future_local(async move {
|
||||||
let result = gio::spawn_blocking(move || {
|
let result = gio::spawn_blocking(move || {
|
||||||
let bg_db = Database::open().expect("Failed to open database");
|
let bg_db = Database::open().expect("Failed to open database");
|
||||||
@@ -251,14 +310,14 @@ impl DriftwoodWindow {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(0) => {
|
Ok(0) => {
|
||||||
toast_ref.add_toast(adw::Toast::new("All AppImages are up to date"));
|
toast_ref.add_toast(adw::Toast::new(&i18n("All AppImages are up to date")));
|
||||||
}
|
}
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
let msg = format!("{} update{} available", n, if n == 1 { "" } else { "s" });
|
let msg = format!("{} update{} available", n, if n == 1 { "" } else { "s" });
|
||||||
toast_ref.add_toast(adw::Toast::new(&msg));
|
toast_ref.add_toast(adw::Toast::new(&msg));
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
toast_ref.add_toast(adw::Toast::new("Failed to check for updates"));
|
toast_ref.add_toast(adw::Toast::new(&i18n("Failed to check for updates")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -274,6 +333,31 @@ impl DriftwoodWindow {
|
|||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// Security report action
|
||||||
|
let security_report_action = gio::ActionEntry::builder("security-report")
|
||||||
|
.activate(|window: &Self, _, _| {
|
||||||
|
let db = window.database().clone();
|
||||||
|
let nav = window.imp().navigation_view.get().unwrap();
|
||||||
|
let page = security_report::build_security_report_page(&db);
|
||||||
|
nav.push(&page);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Disk cleanup wizard action
|
||||||
|
let cleanup_action = gio::ActionEntry::builder("cleanup")
|
||||||
|
.activate(|window: &Self, _, _| {
|
||||||
|
let db = window.database().clone();
|
||||||
|
cleanup_wizard::show_cleanup_wizard(window, &db);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Show keyboard shortcuts dialog
|
||||||
|
let shortcuts_action = gio::ActionEntry::builder("show-shortcuts")
|
||||||
|
.activate(|window: &Self, _, _| {
|
||||||
|
window.show_shortcuts_dialog();
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
self.add_action_entries([
|
self.add_action_entries([
|
||||||
dashboard_action,
|
dashboard_action,
|
||||||
preferences_action,
|
preferences_action,
|
||||||
@@ -282,6 +366,9 @@ impl DriftwoodWindow {
|
|||||||
search_action,
|
search_action,
|
||||||
check_updates_action,
|
check_updates_action,
|
||||||
find_duplicates_action,
|
find_duplicates_action,
|
||||||
|
security_report_action,
|
||||||
|
cleanup_action,
|
||||||
|
shortcuts_action,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
@@ -290,6 +377,9 @@ impl DriftwoodWindow {
|
|||||||
gtk_app.set_accels_for_action("win.scan", &["<Control>r", "F5"]);
|
gtk_app.set_accels_for_action("win.scan", &["<Control>r", "F5"]);
|
||||||
gtk_app.set_accels_for_action("win.search", &["<Control>f"]);
|
gtk_app.set_accels_for_action("win.search", &["<Control>f"]);
|
||||||
gtk_app.set_accels_for_action("win.preferences", &["<Control>comma"]);
|
gtk_app.set_accels_for_action("win.preferences", &["<Control>comma"]);
|
||||||
|
gtk_app.set_accels_for_action("win.dashboard", &["<Control>d"]);
|
||||||
|
gtk_app.set_accels_for_action("win.check-updates", &["<Control>u"]);
|
||||||
|
gtk_app.set_accels_for_action("win.show-shortcuts", &["<Control>question"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,6 +397,12 @@ impl DriftwoodWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-scan on startup if enabled
|
||||||
|
let settings = self.settings();
|
||||||
|
if settings.boolean("auto-scan-on-startup") {
|
||||||
|
self.trigger_scan();
|
||||||
|
}
|
||||||
|
|
||||||
// Check for orphaned desktop entries in the background
|
// Check for orphaned desktop entries in the background
|
||||||
let toast_overlay = self.imp().toast_overlay.get().unwrap().clone();
|
let toast_overlay = self.imp().toast_overlay.get().unwrap().clone();
|
||||||
glib::spawn_future_local(async move {
|
glib::spawn_future_local(async move {
|
||||||
@@ -317,15 +413,16 @@ impl DriftwoodWindow {
|
|||||||
|
|
||||||
if let Ok(count) = result {
|
if let Ok(count) = result {
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
let msg = if count == 1 {
|
let msg = ni18n_f(
|
||||||
"1 orphaned desktop entry found. Use 'Clean' to remove it.".to_string()
|
"{} orphaned desktop entry found. Use 'Clean' to remove it.",
|
||||||
} else {
|
"{} orphaned desktop entries found. Use 'Clean' to remove them.",
|
||||||
format!("{} orphaned desktop entries found. Use 'Clean' to remove them.", count)
|
count as u32,
|
||||||
};
|
&[("{}", &count.to_string())],
|
||||||
|
);
|
||||||
let toast = adw::Toast::builder()
|
let toast = adw::Toast::builder()
|
||||||
.title(&msg)
|
.title(&msg)
|
||||||
.timeout(5)
|
.timeout(5)
|
||||||
.button_label("Clean")
|
.button_label(&i18n("Clean"))
|
||||||
.action_name("win.clean-orphans")
|
.action_name("win.clean-orphans")
|
||||||
.build();
|
.build();
|
||||||
toast_overlay.add_toast(toast);
|
toast_overlay.add_toast(toast);
|
||||||
@@ -339,12 +436,18 @@ impl DriftwoodWindow {
|
|||||||
library_view.set_state(LibraryState::Loading);
|
library_view.set_state(LibraryState::Loading);
|
||||||
|
|
||||||
let settings = self.settings();
|
let settings = self.settings();
|
||||||
let dirs: Vec<String> = settings
|
let mut dirs: Vec<String> = settings
|
||||||
.strv("scan-directories")
|
.strv("scan-directories")
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Include system-wide AppImage directory if it exists
|
||||||
|
let system_dir = crate::config::SYSTEM_APPIMAGE_DIR;
|
||||||
|
if std::path::Path::new(system_dir).is_dir() && !dirs.iter().any(|d| d == system_dir) {
|
||||||
|
dirs.push(system_dir.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
let toast_overlay = self.imp().toast_overlay.get().unwrap().clone();
|
let toast_overlay = self.imp().toast_overlay.get().unwrap().clone();
|
||||||
let window_weak = self.downgrade();
|
let window_weak = self.downgrade();
|
||||||
|
|
||||||
@@ -356,9 +459,18 @@ impl DriftwoodWindow {
|
|||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let discovered = discovery::scan_directories(&dirs);
|
let discovered = discovery::scan_directories(&dirs);
|
||||||
|
|
||||||
|
// Detect system FUSE status once for all AppImages
|
||||||
|
let fuse_info = fuse::detect_system_fuse();
|
||||||
|
|
||||||
let mut new_count = 0i32;
|
let mut new_count = 0i32;
|
||||||
let total = discovered.len() as i32;
|
let total = discovered.len() as i32;
|
||||||
|
|
||||||
|
// Clean stale DB records for files that no longer exist
|
||||||
|
let removed = bg_db.remove_missing_appimages().unwrap_or_default();
|
||||||
|
let removed_count = removed.len() as i32;
|
||||||
|
|
||||||
|
let mut skipped_count = 0i32;
|
||||||
|
|
||||||
for d in &discovered {
|
for d in &discovered {
|
||||||
let existing = bg_db
|
let existing = bg_db
|
||||||
.get_appimage_by_path(&d.path.to_string_lossy())
|
.get_appimage_by_path(&d.path.to_string_lossy())
|
||||||
@@ -372,6 +484,20 @@ impl DriftwoodWindow {
|
|||||||
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string())
|
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Skip re-processing unchanged files (same size + mtime, and all analysis done)
|
||||||
|
if let Some(ref ex) = existing {
|
||||||
|
let size_unchanged = ex.size_bytes == d.size_bytes as i64;
|
||||||
|
let mtime_unchanged = modified.as_deref() == ex.file_modified.as_deref();
|
||||||
|
let fully_analyzed = ex.app_name.is_some()
|
||||||
|
&& ex.fuse_status.is_some()
|
||||||
|
&& ex.wayland_status.is_some()
|
||||||
|
&& ex.sha256.is_some();
|
||||||
|
if size_unchanged && mtime_unchanged && fully_analyzed {
|
||||||
|
skipped_count += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let id = bg_db.upsert_appimage(
|
let id = bg_db.upsert_appimage(
|
||||||
&d.path.to_string_lossy(),
|
&d.path.to_string_lossy(),
|
||||||
&d.filename,
|
&d.filename,
|
||||||
@@ -410,15 +536,58 @@ impl DriftwoodWindow {
|
|||||||
).ok();
|
).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-AppImage FUSE status
|
||||||
|
let needs_fuse = existing
|
||||||
|
.as_ref()
|
||||||
|
.map(|r| r.fuse_status.is_none())
|
||||||
|
.unwrap_or(true);
|
||||||
|
if needs_fuse {
|
||||||
|
let app_fuse = fuse::determine_app_fuse_status(&fuse_info, &d.path);
|
||||||
|
bg_db.update_fuse_status(id, app_fuse.as_str()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wayland compatibility analysis (slower - only for new/unanalyzed)
|
||||||
|
let needs_wayland = existing
|
||||||
|
.as_ref()
|
||||||
|
.map(|r| r.wayland_status.is_none())
|
||||||
|
.unwrap_or(true);
|
||||||
|
if needs_wayland {
|
||||||
|
let analysis = wayland::analyze_appimage(&d.path);
|
||||||
|
bg_db.update_wayland_status(id, analysis.status.as_str()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHA256 hash (for duplicate detection)
|
||||||
|
let needs_hash = existing
|
||||||
|
.as_ref()
|
||||||
|
.map(|r| r.sha256.is_none())
|
||||||
|
.unwrap_or(true);
|
||||||
|
if needs_hash {
|
||||||
|
if let Ok(hash) = crate::core::discovery::compute_sha256(&d.path) {
|
||||||
|
bg_db.update_sha256(id, &hash).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover config/data paths (only for new AppImages)
|
||||||
|
if existing.is_none() {
|
||||||
|
if let Ok(Some(rec)) = bg_db.get_appimage_by_id(id) {
|
||||||
|
crate::core::footprint::discover_and_store(&bg_db, id, &rec);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"Scan complete: {} files, {} new, {} removed, {} skipped (unchanged), took {}ms",
|
||||||
|
total, new_count, removed_count, skipped_count, start.elapsed().as_millis()
|
||||||
|
);
|
||||||
|
|
||||||
let duration = start.elapsed().as_millis() as i64;
|
let duration = start.elapsed().as_millis() as i64;
|
||||||
bg_db.log_scan(
|
bg_db.log_scan(
|
||||||
"manual",
|
"manual",
|
||||||
&dirs.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
|
&dirs.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
|
||||||
total,
|
total,
|
||||||
new_count,
|
new_count,
|
||||||
0,
|
removed_count,
|
||||||
duration,
|
duration,
|
||||||
).ok();
|
).ok();
|
||||||
|
|
||||||
@@ -438,16 +607,71 @@ impl DriftwoodWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let msg = match new_count {
|
let msg = match new_count {
|
||||||
0 if total == 0 => "No AppImages found".to_string(),
|
0 if total == 0 => i18n("No AppImages found"),
|
||||||
0 => format!("{} AppImages up to date", total),
|
0 => format!("{} {}", total, i18n("AppImages up to date")),
|
||||||
1 => "Found 1 new AppImage".to_string(),
|
1 => i18n("Found 1 new AppImage"),
|
||||||
n => format!("Found {} new AppImages", n),
|
n => format!("{} {} {}", i18n("Found"), n, i18n("new AppImages")),
|
||||||
};
|
};
|
||||||
toast_overlay.add_toast(adw::Toast::new(&msg));
|
toast_overlay.add_toast(adw::Toast::new(&msg));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_shortcuts_dialog(&self) {
|
||||||
|
let dialog = adw::Dialog::builder()
|
||||||
|
.title("Keyboard Shortcuts")
|
||||||
|
.content_width(400)
|
||||||
|
.content_height(420)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let toolbar = adw::ToolbarView::new();
|
||||||
|
let header = adw::HeaderBar::new();
|
||||||
|
toolbar.add_top_bar(&header);
|
||||||
|
|
||||||
|
let scrolled = gtk::ScrolledWindow::builder()
|
||||||
|
.vexpand(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let content = gtk::Box::builder()
|
||||||
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
.spacing(18)
|
||||||
|
.margin_top(18)
|
||||||
|
.margin_bottom(18)
|
||||||
|
.margin_start(18)
|
||||||
|
.margin_end(18)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Navigation group
|
||||||
|
let nav_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Navigation")
|
||||||
|
.build();
|
||||||
|
nav_group.add(&shortcut_row("Ctrl+F", "Search"));
|
||||||
|
nav_group.add(&shortcut_row("Ctrl+D", "Dashboard"));
|
||||||
|
nav_group.add(&shortcut_row("Ctrl+,", "Preferences"));
|
||||||
|
content.append(&nav_group);
|
||||||
|
|
||||||
|
// Actions group
|
||||||
|
let actions_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Actions")
|
||||||
|
.build();
|
||||||
|
actions_group.add(&shortcut_row("Ctrl+R / F5", "Scan for AppImages"));
|
||||||
|
actions_group.add(&shortcut_row("Ctrl+U", "Check for updates"));
|
||||||
|
content.append(&actions_group);
|
||||||
|
|
||||||
|
// Application group
|
||||||
|
let app_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Application")
|
||||||
|
.build();
|
||||||
|
app_group.add(&shortcut_row("Ctrl+?", "Keyboard shortcuts"));
|
||||||
|
app_group.add(&shortcut_row("Ctrl+Q", "Quit"));
|
||||||
|
content.append(&app_group);
|
||||||
|
|
||||||
|
scrolled.set_child(Some(&content));
|
||||||
|
toolbar.set_content(Some(&scrolled));
|
||||||
|
dialog.set_child(Some(&toolbar));
|
||||||
|
dialog.present(Some(self));
|
||||||
|
}
|
||||||
|
|
||||||
fn save_window_state(&self) {
|
fn save_window_state(&self) {
|
||||||
let settings = self.settings();
|
let settings = self.settings();
|
||||||
let (width, height) = self.default_size();
|
let (width, height) = self.default_size();
|
||||||
|
|||||||
Reference in New Issue
Block a user