diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index 7ba34a6..7b6409b 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -29,7 +29,8 @@ size = Size # Progress footer details = Details dismiss = Dismiss message -operations-in-progress = {$count} actions in progress ({$percent}%)... +operations-running = {$running} operations running ({$percent}%)... +operations-running-finished = {$running} operations running ({$percent}%), {$finished} finished... pause = Pause resume = Resume @@ -138,12 +139,15 @@ edit-history = Edit history history = History no-history = No items in history. pending = Pending +progress = {$percent}% +progress-cancelled = {$percent}%, cancelled +progress-paused = {$percent}%, paused failed = Failed complete = Complete compressing = Compressing {$items} {$items -> [one] item *[other] items - } from "{$from}" to "{$to}" ({$percent}%)... + } from "{$from}" to "{$to}" ({$progress})... compressed = Compressed {$items} {$items -> [one] item *[other] items @@ -154,17 +158,17 @@ created = Created "{$name}" in "{$parent}" copying = Copying {$items} {$items -> [one] item *[other] items - } from "{$from}" to "{$to}" ({$percent}%)... + } from "{$from}" to "{$to}" ({$progress})... copied = Copied {$items} {$items -> [one] item *[other] items } from "{$from}" to "{$to}" -emptying-trash = Emptying {trash} ({$percent}%)... +emptying-trash = Emptying {trash} ({$progress})... emptied-trash = Emptied {trash} extracting = Extracting {$items} {$items -> [one] item *[other] items - } from "{$from}" to "{$to}" ({$percent}%)... + } from "{$from}" to "{$to}" ({$progress})... extracted = Extracted {$items} {$items -> [one] item *[other] items @@ -174,7 +178,7 @@ set-executable-and-launched = Set "{$name}" as executable and launched moving = Moving {$items} {$items -> [one] item *[other] items - } from "{$from}" to "{$to}" ({$percent}%)... + } from "{$from}" to "{$to}" ({$progress})... moved = Moved {$items} {$items -> [one] item *[other] items @@ -184,7 +188,7 @@ renamed = Renamed "{$from}" to "{$to}" restoring = Restoring {$items} {$items -> [one] item *[other] items - } from {trash} ({$percent})... + } from {trash} ({$progress})... restored = Restored {$items} {$items -> [one] item *[other] items diff --git a/src/app.rs b/src/app.rs index 55be07c..9ec2b8d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -524,7 +524,7 @@ pub struct App { pending_operations: BTreeMap, progress_operations: BTreeSet, complete_operations: BTreeMap, - failed_operations: BTreeMap, + failed_operations: BTreeMap, search_id: widget::Id, #[cfg(feature = "wayland")] surface_ids: HashMap, @@ -1218,6 +1218,8 @@ impl App { } fn edit_history(&self) -> Element { + let cosmic_theme::Spacing { space_m, .. } = theme::active().cosmic().spacing; + let mut children = Vec::new(); //TODO: get height from theme? @@ -1265,7 +1267,7 @@ impl App { ]) .align_y(Alignment::Center) .into(), - widget::text(op.pending_text(*progress as i32)).into(), + widget::text(op.pending_text(*progress as i32, controller.state())).into(), ])); } children.push(section.into()); @@ -1273,9 +1275,9 @@ impl App { if !self.failed_operations.is_empty() { let mut section = widget::settings::section().title(fl!("failed")); - for (_id, (op, progress, error)) in self.failed_operations.iter().rev() { + for (_id, (op, progress, controller, error)) in self.failed_operations.iter().rev() { section = section.add(widget::column::with_children(vec![ - widget::text(op.pending_text(*progress as i32)).into(), + widget::text(op.pending_text(*progress as i32, controller.state())).into(), widget::text(error).into(), ])); } @@ -1294,7 +1296,9 @@ impl App { children.push(widget::text::body(fl!("no-history")).into()); } - widget::column::with_children(children).into() + widget::column::with_children(children) + .spacing(space_m) + .into() } fn preview<'a>( @@ -2419,12 +2423,14 @@ impl Application for App { } Message::PendingError(id, err) => { if let Some((op, progress, controller)) = self.pending_operations.remove(&id) { - self.failed_operations.insert(id, (op, progress, err)); // Only show dialog if not cancelled if !controller.is_cancelled() { self.dialog_pages.push_back(DialogPage::FailedOperation(id)); } + // Remove from progress self.progress_operations.remove(&id); + self.failed_operations + .insert(id, (op, progress, controller, err)); } // Close progress notification if all relavent operations are finished if !self @@ -3421,7 +3427,7 @@ impl Application for App { ), DialogPage::FailedOperation(id) => { //TODO: try next dialog page (making sure index is used by Dialog messages)? - let (operation, _, err) = self.failed_operations.get(id)?; + let (operation, _, _, err) = self.failed_operations.get(id)?; //TODO: nice description of error widget::dialog() @@ -3874,25 +3880,37 @@ impl Application for App { } if op.show_progress_notification() { if title.is_empty() { - title = op.pending_text(*progress as i32); + title = op.pending_text(*progress as i32, controller.state()); } total_progress += progress; count += 1; } } - let in_progress_count = count; + let running = count; // Adjust the progress bar so it does not jump around when operations finish - while count < self.progress_operations.len() { - total_progress += 100.0; - count += 1; + for id in self.progress_operations.iter() { + if self.complete_operations.contains_key(&id) { + total_progress += 100.0; + count += 1; + } } + let finished = count - running; total_progress /= count as f32; - if in_progress_count > 1 { - title = fl!( - "operations-in-progress", - count = in_progress_count, - percent = (total_progress as i32) - ); + if running > 1 { + if finished > 0 { + title = fl!( + "operations-running-finished", + running = running, + finished = finished, + percent = (total_progress as i32) + ); + } else { + title = fl!( + "operations-running", + running = running, + percent = (total_progress as i32) + ); + } } //TODO: get height from theme? diff --git a/src/operation/mod.rs b/src/operation/mod.rs index f9c7b75..2431c27 100644 --- a/src/operation/mod.rs +++ b/src/operation/mod.rs @@ -224,8 +224,15 @@ fn zip_extract>( Ok(()) } +#[derive(Clone, Copy, Debug)] +pub enum ControllerState { + Cancelled, + Paused, + Running, +} + struct ControllerInner { - state: Mutex, + state: Mutex, condvar: Condvar, } @@ -235,14 +242,10 @@ pub struct Controller { } impl Controller { - const RUNNING: u32 = 0; - const PAUSED: u32 = 1; - const CANCELLED: u32 = 2; - pub fn new() -> Self { Self { inner: Arc::new(ControllerInner { - state: Mutex::new(Self::RUNNING), + state: Mutex::new(ControllerState::Running), condvar: Condvar::new(), }), } @@ -251,42 +254,44 @@ impl Controller { pub fn check(&self) -> Result<(), String> { let mut state = self.inner.state.lock().unwrap(); loop { - if *state == Self::CANCELLED { - return Err(fl!("cancelled")); - } else if *state == Self::PAUSED { - state = self.inner.condvar.wait(state).unwrap(); - } else { - return Ok(()); + match *state { + ControllerState::Cancelled => return Err(fl!("cancelled")), + ControllerState::Paused => { + state = self.inner.condvar.wait(state).unwrap(); + } + ControllerState::Running => return Ok(()), } } } + pub fn state(&self) -> ControllerState { + *self.inner.state.lock().unwrap() + } + + pub fn set_state(&self, state: ControllerState) { + *self.inner.state.lock().unwrap() = state; + self.inner.condvar.notify_all(); + } + pub fn is_cancelled(&self) -> bool { - let state = self.inner.state.lock().unwrap(); - *state == Self::CANCELLED + matches!(self.state(), ControllerState::Cancelled) } pub fn cancel(&self) { - let mut state = self.inner.state.lock().unwrap(); - *state = Self::CANCELLED; - self.inner.condvar.notify_all(); + self.set_state(ControllerState::Cancelled); } pub fn is_paused(&self) -> bool { - let state = self.inner.state.lock().unwrap(); - *state == Self::PAUSED + matches!(self.state(), ControllerState::Paused) } pub fn pause(&self) { - let mut state = self.inner.state.lock().unwrap(); - *state = Self::PAUSED; - self.inner.condvar.notify_all(); + self.set_state(ControllerState::Paused); } pub fn unpause(&self) { - let mut state = self.inner.state.lock().unwrap(); - *state = Self::RUNNING; - self.inner.condvar.notify_all(); + //TODO: ensure this does not override Cancel? + self.set_state(ControllerState::Running); } } @@ -528,43 +533,48 @@ fn paths_parent_name<'a>(paths: &'a Vec) -> Cow<'a, str> { } impl Operation { - pub fn pending_text(&self, percent: i32) -> String { + pub fn pending_text(&self, percent: i32, state: ControllerState) -> String { + let progress = || match state { + ControllerState::Running => fl!("progress", percent = percent), + ControllerState::Paused => fl!("progress-paused", percent = percent), + ControllerState::Cancelled => fl!("progress-cancelled", percent = percent), + }; match self { Self::Compress { paths, to, .. } => fl!( "compressing", items = paths.len(), from = paths_parent_name(paths), to = file_name(to), - percent = percent + progress = progress() ), Self::Copy { paths, to } => fl!( "copying", items = paths.len(), from = paths_parent_name(paths), to = file_name(to), - percent = percent + progress = progress() ), Self::Delete { paths } => fl!( "moving", items = paths.len(), from = paths_parent_name(paths), to = fl!("trash"), - percent = percent + progress = progress() ), - Self::EmptyTrash => fl!("emptying-trash", percent = percent), + Self::EmptyTrash => fl!("emptying-trash", progress = progress()), Self::Extract { paths, to } => fl!( "extracting", items = paths.len(), from = paths_parent_name(paths), to = file_name(to), - percent = percent + progress = progress() ), Self::Move { paths, to } => fl!( "moving", items = paths.len(), from = paths_parent_name(paths), to = file_name(to), - percent = percent + progress = progress() ), Self::NewFile { path } => fl!( "creating", @@ -579,7 +589,7 @@ impl Operation { Self::Rename { from, to } => { fl!("renaming", from = file_name(from), to = file_name(to)) } - Self::Restore { paths } => fl!("restoring", items = paths.len(), percent = percent), + Self::Restore { paths } => fl!("restoring", items = paths.len(), progress = progress()), Self::SetExecutableAndLaunch { path } => { fl!("setting-executable-and-launching", name = file_name(path)) }