diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index a8dc296..9139d5d 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -181,6 +181,14 @@ copied = Copied {$items} {$items -> [one] item *[other] items } from "{$from}" to "{$to}" +deleting = Deleting {$items} {$items -> + [one] item + *[other] items + } from {trash} ({$progress})... +deleted = Deleted {$items} {$items -> + [one] item + *[other] items + } from {trash} emptying-trash = Emptying {trash} ({$progress})... emptied-trash = Emptied {trash} extracting = Extracting {$items} {$items -> @@ -240,6 +248,7 @@ light = Light # Context menu add-to-sidebar = Add to sidebar compress = Compress +delete-permanently = Delete permanently extract-here = Extract new-file = New file... new-folder = New folder... diff --git a/src/app.rs b/src/app.rs index f866686..c99aed4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -100,6 +100,7 @@ pub enum Action { CosmicSettingsDisplays, CosmicSettingsWallpaper, DesktopViewOptions, + Delete, EditHistory, EditLocation, EmptyTrash, @@ -114,7 +115,6 @@ pub enum Action { ItemRight, ItemUp, LocationUp, - MoveToTrash, NewFile, NewFolder, Open, @@ -161,6 +161,7 @@ impl Action { Action::CosmicSettingsAppearance => Message::CosmicSettings("appearance"), Action::CosmicSettingsDisplays => Message::CosmicSettings("displays"), Action::CosmicSettingsWallpaper => Message::CosmicSettings("wallpaper"), + Action::Delete => Message::Delete(entity_opt), Action::DesktopViewOptions => Message::DesktopViewOptions, Action::EditHistory => Message::ToggleContextPage(ContextPage::EditHistory), Action::EditLocation => { @@ -180,7 +181,6 @@ impl Action { Action::ItemRight => Message::TabMessage(entity_opt, tab::Message::ItemRight), Action::ItemUp => Message::TabMessage(entity_opt, tab::Message::ItemUp), Action::LocationUp => Message::TabMessage(entity_opt, tab::Message::LocationUp), - Action::MoveToTrash => Message::MoveToTrash(entity_opt), Action::NewFile => Message::NewItem(entity_opt, false), Action::NewFolder => Message::NewItem(entity_opt, true), Action::Open => Message::TabMessage(entity_opt, tab::Message::Open(None)), @@ -281,6 +281,7 @@ pub enum Message { CosmicSettings(&'static str), CursorMoved(Point), Cut(Option), + Delete(Option), DesktopConfig(DesktopConfig), DesktopViewOptions, DialogCancel, @@ -295,7 +296,6 @@ pub enum Message { LaunchUrl(String), MaybeExit, Modifiers(Modifiers), - MoveToTrash(Option), MounterItems(MounterKey, MounterItems), MountResult(MounterKey, MounterItem, Result), NavBarClose(Entity), @@ -1966,6 +1966,39 @@ impl Application for App { } } } + Message::Delete(entity_opt) => { + let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); + if let Some(tab) = self.tab_model.data::(entity) { + match &tab.location { + Location::Trash => { + if let Some(items) = tab.items_opt() { + let mut trash_items = Vec::new(); + for item in items.iter() { + if item.selected { + match &item.metadata { + ItemMetadata::Trash { entry, .. } => { + trash_items.push(entry.clone()); + } + _ => { + //TODO: error on trying to permanently delete non-trash file? + } + } + } + } + if !trash_items.is_empty() { + self.operation(Operation::DeleteTrash { items: trash_items }); + } + } + } + _ => { + let paths = dbg!(self.selected_paths(entity_opt)); + if !paths.is_empty() { + self.operation(Operation::Delete { paths }); + } + } + } + } + } Message::DesktopConfig(config) => { if config != self.config.desktop { config_set!(desktop, config); @@ -2177,12 +2210,6 @@ impl Application for App { Message::Modifiers(modifiers) => { self.modifiers = modifiers; } - Message::MoveToTrash(entity_opt) => { - let paths = self.selected_paths(entity_opt); - if !paths.is_empty() { - self.operation(Operation::Delete { paths }); - } - } Message::MounterItems(mounter_key, mounter_items) => { // Check for unmounted folders let mut unmounted = Vec::new(); @@ -2972,6 +2999,9 @@ impl Application for App { self.update_tab(entity, tab_path, selection_paths), ])); } + tab::Command::Delete(paths) => { + self.operation(Operation::Delete { paths }); + } tab::Command::DropFiles(to, from) => { commands.push(self.update(Message::PasteContents(to, from))); } @@ -2989,9 +3019,6 @@ impl Application for App { }), ); } - tab::Command::MoveToTrash(paths) => { - self.operation(Operation::Delete { paths }); - } tab::Command::OpenFile(path) => self.open_file(&path), tab::Command::OpenInNewTab(path) => { commands.push(self.open_tab(Location::Path(path.clone()), false, None)); diff --git a/src/key_bind.rs b/src/key_bind.rs index 164a8c1..2a60495 100644 --- a/src/key_bind.rs +++ b/src/key_bind.rs @@ -64,7 +64,7 @@ pub fn key_binds(mode: &tab::Mode) -> HashMap { if matches!(mode, tab::Mode::App | tab::Mode::Desktop) { bind!([Ctrl], Key::Character("c".into()), Copy); bind!([Ctrl], Key::Character("x".into()), Cut); - bind!([], Key::Named(Named::Delete), MoveToTrash); + bind!([], Key::Named(Named::Delete), Delete); bind!([Shift], Key::Named(Named::Enter), OpenInNewWindow); bind!([Ctrl], Key::Character("v".into()), Paste); bind!([], Key::Named(Named::F2), Rename); diff --git a/src/menu.rs b/src/menu.rs index c65b26e..dfd59db 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -151,7 +151,7 @@ pub fn context_menu<'a>( children.push(menu_item(fl!("cut"), Action::Cut).into()); children.push(menu_item(fl!("copy"), Action::Copy).into()); // Should this simply bypass trash and remove the shortcut? - children.push(menu_item(fl!("move-to-trash"), Action::MoveToTrash).into()); + children.push(menu_item(fl!("move-to-trash"), Action::Delete).into()); } else if selected > 0 { if selected_dir == 1 && selected == 1 || selected_dir == 0 { children.push(menu_item(fl!("open"), Action::Open).into()); @@ -211,7 +211,7 @@ pub fn context_menu<'a>( children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into()); } children.push(divider::horizontal::light().into()); - children.push(menu_item(fl!("move-to-trash"), Action::MoveToTrash).into()); + children.push(menu_item(fl!("move-to-trash"), Action::Delete).into()); } else { //TODO: need better designs for menu with no selection //TODO: have things like properties but they apply to the folder? @@ -311,6 +311,8 @@ pub fn context_menu<'a>( children.push(divider::horizontal::light().into()); children .push(menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into()); + children.push(divider::horizontal::light().into()); + children.push(menu_item(fl!("delete-permanently"), Action::Delete).into()); } else { // TODO: Nested menu children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name)); @@ -537,7 +539,15 @@ pub fn menu_bar<'a>( menu::Item::Divider, menu_button_optional(fl!("add-to-sidebar"), Action::AddToSidebar, selected > 0), menu::Item::Divider, - menu_button_optional(fl!("move-to-trash"), Action::MoveToTrash, selected > 0), + menu_button_optional( + if in_trash { + fl!("delete-permanently") + } else { + fl!("move-to-trash") + }, + Action::Delete, + selected > 0, + ), menu::Item::Divider, menu::Item::Button(fl!("close-tab"), None, Action::TabClose), menu::Item::Button(fl!("quit"), None, Action::WindowClose), diff --git a/src/operation/mod.rs b/src/operation/mod.rs index 43adf07..68026e7 100644 --- a/src/operation/mod.rs +++ b/src/operation/mod.rs @@ -460,6 +460,10 @@ pub enum Operation { Delete { paths: Vec, }, + /// Delete a path from the trash + DeleteTrash { + items: Vec, + }, /// Empty the trash EmptyTrash, /// Uncompress files @@ -550,6 +554,9 @@ impl Operation { to = fl!("trash"), progress = progress() ), + Self::DeleteTrash { items } => { + fl!("deleting", items = items.len(), progress = progress()) + } Self::EmptyTrash => fl!("emptying-trash", progress = progress()), Self::Extract { paths, @@ -609,6 +616,7 @@ impl Operation { from = paths_parent_name(paths), to = fl!("trash") ), + Self::DeleteTrash { items } => fl!("deleted", items = items.len()), Self::EmptyTrash => fl!("emptied-trash"), Self::Extract { paths, @@ -650,6 +658,7 @@ impl Operation { Self::Compress { .. } | Self::Copy { .. } | Self::Delete { .. } + | Self::DeleteTrash { .. } | Self::EmptyTrash | Self::Extract { .. } | Self::Move { .. } @@ -846,6 +855,34 @@ impl Operation { } Ok(OperationSelection::default()) } + Self::DeleteTrash { items } => { + #[cfg(any( + target_os = "windows", + all( + unix, + not(target_os = "macos"), + not(target_os = "ios"), + not(target_os = "android") + ) + ))] + { + tokio::task::spawn_blocking(move || -> Result<(), OperationError> { + let count = items.len(); + for (i, item) in items.into_iter().enumerate() { + controller.check().map_err(OperationError::from_str)?; + + controller.set_progress(i as f32 / count as f32); + + trash::os_limited::purge_all([item]) + .map_err(OperationError::from_str)?; + } + Ok(()) + }) + .await + .map_err(OperationError::from_str)??; + } + Ok(OperationSelection::default()) + } Self::EmptyTrash => { #[cfg(any( target_os = "windows", diff --git a/src/tab.rs b/src/tab.rs index 2b09f38..f6d3f58 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -1127,12 +1127,12 @@ pub enum Command { AddToSidebar(PathBuf), AutoScroll(Option), ChangeLocation(String, Location, Option>), + Delete(Vec), DropFiles(PathBuf, ClipboardPaste), EmptyTrash, #[cfg(feature = "desktop")] ExecEntryAction(cosmic::desktop::DesktopEntryData, usize), Iced(TaskWrapper), - MoveToTrash(Vec), OpenFile(PathBuf), OpenInNewTab(PathBuf), OpenInNewWindow(PathBuf), @@ -3188,7 +3188,7 @@ impl Tab { commands.push(Command::DropFiles(to, from)) } Location::Trash if matches!(from.kind, ClipboardKind::Cut) => { - commands.push(Command::MoveToTrash(from.paths)) + commands.push(Command::Delete(from.paths)) } _ => { log::warn!("{:?} to {:?} is not supported.", from.kind, to);