Show all applications that handle any mime types in open with dialog, fixes #568, fixes #226, fixes #721

This commit is contained in:
Jeremy Soller 2025-04-29 09:53:51 -06:00
parent d9ffb5d599
commit 8ced8b0551
No known key found for this signature in database
GPG key ID: 670FDFB5428E05CA
3 changed files with 58 additions and 11 deletions

View file

@ -80,6 +80,8 @@ save-file = Save file
## Open With Dialog
open-with-title = How do you want to open "{$name}"?
browse-store = Browse {$store}
other-apps = Other applications
related-apps = Related applications
## Rename Dialog
rename-file = Rename file

View file

@ -505,6 +505,13 @@ pub enum DialogPage {
pub struct FavoriteIndex(usize);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum MimeAppMatch {
Exact,
Related,
Other,
}
pub struct MounterData(MounterKey, MounterItem);
#[derive(Clone, Debug)]
@ -1710,7 +1717,7 @@ impl App {
.into()
}
fn get_programs_for_mime(&self, mime_type: &Mime) -> Vec<&MimeApp> {
fn get_apps_for_mime(&self, mime_type: &Mime) -> Vec<(&MimeApp, MimeAppMatch)> {
let mut results = Vec::new();
let mut dedupe = HashSet::new();
@ -1719,7 +1726,7 @@ impl App {
for mime_app in self.mime_app_cache.get(mime_type) {
let app_id = &mime_app.id;
if !dedupe.contains(app_id) {
results.push(mime_app);
results.push((mime_app, MimeAppMatch::Exact));
dedupe.insert(app_id);
}
}
@ -1730,13 +1737,22 @@ impl App {
for mime_app in self.mime_app_cache.get(&parent_type) {
let app_id = &mime_app.id;
if !dedupe.contains(app_id) {
results.push(mime_app);
results.push((mime_app, MimeAppMatch::Related));
dedupe.insert(app_id);
}
}
}
}
// Add other apps
for mime_app in self.mime_app_cache.apps() {
let app_id = &mime_app.id;
if !dedupe.contains(app_id) {
results.push((mime_app, MimeAppMatch::Other));
dedupe.insert(app_id);
}
}
results
}
@ -2412,9 +2428,9 @@ impl Application for App {
selected,
..
} => {
let all_apps = self.get_programs_for_mime(&mime);
let available_apps = self.get_apps_for_mime(&mime);
if let Some(app) = all_apps.get(selected) {
if let Some((app, _)) = available_apps.get(selected) {
if let Some(mut command) =
app.command(&[&path]).and_then(|v| v.into_iter().next())
{
@ -4555,11 +4571,23 @@ impl Application for App {
};
let mut column = widget::list_column();
let available_programs = self.get_programs_for_mime(mime);
let available_apps = self.get_apps_for_mime(mime);
let item_height = 32.0;
let mut displayed_default = false;
for (i, app) in available_programs.iter().enumerate() {
let mut last_kind = MimeAppMatch::Exact;
for (i, (app, kind)) in available_apps.iter().enumerate() {
if *kind != last_kind {
match kind {
MimeAppMatch::Related => {
column = column.add(widget::text::heading(fl!("related-apps")));
}
MimeAppMatch::Other => {
column = column.add(widget::text::heading(fl!("other-apps")));
}
_ => {}
}
last_kind = *kind;
}
column = column.add(
widget::button::custom(
widget::row::with_children(vec![
@ -4603,13 +4631,13 @@ impl Application for App {
)
.control(
widget::scrollable(column).height(if let Some(size) = self.size {
let max_size = size.height - 256.0;
let max_size = (size.height - 256.0).min(480.0);
// (32 (item_height) + 5.0 (custom button padding)) + (space_xxs (list item spacing) * 2)
let scrollable_height = available_programs.len() as f32
let scrollable_height = available_apps.len() as f32
* (item_height + 5.0 + (2.0 * space_xxs as f32));
if scrollable_height > max_size {
Length::Fill
Length::Fixed(max_size)
} else {
Length::Shrink
}

View file

@ -220,6 +220,7 @@ fn filename_eq(path_opt: &Option<PathBuf>, filename: &str) -> bool {
}
pub struct MimeAppCache {
apps: Vec<MimeApp>,
cache: HashMap<Mime, Vec<MimeApp>>,
icons: HashMap<Mime, Vec<widget::icon::Handle>>,
terminals: Vec<MimeApp>,
@ -228,6 +229,7 @@ pub struct MimeAppCache {
impl MimeAppCache {
pub fn new() -> Self {
let mut mime_app_cache = Self {
apps: Vec::new(),
cache: HashMap::new(),
icons: HashMap::new(),
terminals: Vec::new(),
@ -246,6 +248,7 @@ impl MimeAppCache {
let start = Instant::now();
self.apps.clear();
self.cache.clear();
self.icons.clear();
self.terminals.clear();
@ -257,6 +260,10 @@ impl MimeAppCache {
//TODO: hashmap for all apps by id?
let mut all_apps = desktop::load_applications(locale, false);
for app in &mut all_apps {
//TODO: just collect apps that can be executed with a file argument?
if !app.mime_types.is_empty() {
self.apps.push(MimeApp::from(&app));
}
for mime in app.mime_types.iter() {
let apps = self
.cache
@ -387,6 +394,12 @@ impl MimeAppCache {
}
// Sort apps by name
self.apps
.sort_by(|a, b| match (a.is_default, b.is_default) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => LANGUAGE_SORTER.compare(&a.name, &b.name),
});
for apps in self.cache.values_mut() {
apps.sort_by(|a, b| match (a.is_default, b.is_default) {
(true, false) => Ordering::Less,
@ -408,6 +421,10 @@ impl MimeAppCache {
log::info!("loaded mime app cache in {:?}", elapsed);
}
pub fn apps(&self) -> &[MimeApp] {
&self.apps
}
pub fn get(&self, key: &Mime) -> &[MimeApp] {
static EMPTY: Vec<MimeApp> = Vec::new();
self.cache.get(key).unwrap_or(&EMPTY)