Respect military time config from time applet

Closes: #775
This commit is contained in:
Josh Megnauth 2025-02-24 00:47:32 -05:00
parent 38829f4c00
commit e34dcf1372
No known key found for this signature in database
GPG key ID: 70813183462EFAD3
4 changed files with 125 additions and 64 deletions

View file

@ -1,6 +1,7 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::iced::mouse::Event::CursorMoved;
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
use cosmic::iced::{ use cosmic::iced::{
event::wayland::{Event as WaylandEvent, OutputEvent, OverlapNotifyEvent}, event::wayland::{Event as WaylandEvent, OutputEvent, OverlapNotifyEvent},
@ -55,7 +56,6 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
time::{self, Instant}, time::{self, Instant},
}; };
use cosmic::iced::mouse::Event::CursorMoved;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use trash::TrashItem; use trash::TrashItem;
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
@ -552,7 +552,7 @@ pub struct App {
tab_dnd_hover: Option<(Entity, Instant)>, tab_dnd_hover: Option<(Entity, Instant)>,
nav_drag_id: DragId, nav_drag_id: DragId,
tab_drag_id: DragId, tab_drag_id: DragId,
auto_scroll_speed: Option<i16> auto_scroll_speed: Option<i16>,
} }
impl App { impl App {
@ -1458,9 +1458,14 @@ impl App {
let mut children = Vec::with_capacity(1); let mut children = Vec::with_capacity(1);
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
let military_time = self.config.tab.military_time;
match kind { match kind {
PreviewKind::Custom(PreviewItem(item)) => { PreviewKind::Custom(PreviewItem(item)) => {
children.push(item.preview_view(Some(&self.mime_app_cache), IconSizes::default())); children.push(item.preview_view(
Some(&self.mime_app_cache),
IconSizes::default(),
military_time,
));
} }
PreviewKind::Location(location) => { PreviewKind::Location(location) => {
if let Some(tab) = self.tab_model.data::<Tab>(entity) { if let Some(tab) = self.tab_model.data::<Tab>(entity) {
@ -1470,6 +1475,7 @@ impl App {
children.push(item.preview_view( children.push(item.preview_view(
Some(&self.mime_app_cache), Some(&self.mime_app_cache),
tab.config.icon_sizes, tab.config.icon_sizes,
military_time,
)); ));
// Only show one property view to avoid issues like hangs when generating // Only show one property view to avoid issues like hangs when generating
// preview images on thousands of files // preview images on thousands of files
@ -1487,6 +1493,7 @@ impl App {
children.push(item.preview_view( children.push(item.preview_view(
Some(&self.mime_app_cache), Some(&self.mime_app_cache),
tab.config.icon_sizes, tab.config.icon_sizes,
military_time,
)); ));
// Only show one property view to avoid issues like hangs when generating // Only show one property view to avoid issues like hangs when generating
// preview images on thousands of files // preview images on thousands of files
@ -1498,6 +1505,7 @@ impl App {
children.push(item.preview_view( children.push(item.preview_view(
Some(&self.mime_app_cache), Some(&self.mime_app_cache),
tab.config.icon_sizes, tab.config.icon_sizes,
military_time,
)); ));
} }
} }
@ -1934,7 +1942,10 @@ impl Application for App {
} }
Message::CursorMoved(pos) => { Message::CursorMoved(pos) => {
let entity = self.tab_model.active(); let entity = self.tab_model.active();
return self.update(Message::TabMessage(Some(entity), tab::Message::CursorMoved(pos))); return self.update(Message::TabMessage(
Some(entity),
tab::Message::CursorMoved(pos),
));
} }
Message::Cut(entity_opt) => { Message::Cut(entity_opt) => {
let paths = self.selected_paths(entity_opt); let paths = self.selected_paths(entity_opt);
@ -2800,7 +2811,10 @@ impl Application for App {
} }
Message::ScrollTab(scroll_speed) => { Message::ScrollTab(scroll_speed) => {
let entity = self.tab_model.active(); let entity = self.tab_model.active();
return self.update(Message::TabMessage(Some(entity), tab::Message::ScrollTab((scroll_speed as f32) / 10.0))); return self.update(Message::TabMessage(
Some(entity),
tab::Message::ScrollTab((scroll_speed as f32) / 10.0),
));
} }
Message::SearchActivate => { Message::SearchActivate => {
return if self.search_get().is_none() { return if self.search_get().is_none() {
@ -2944,11 +2958,9 @@ impl Application for App {
// further resolution isn't necessary // further resolution isn't necessary
if let Some(scroll_speed_float) = scroll_speed { if let Some(scroll_speed_float) = scroll_speed {
self.auto_scroll_speed = Some((scroll_speed_float * 10.0) as i16); self.auto_scroll_speed = Some((scroll_speed_float * 10.0) as i16);
} } else {
else {
self.auto_scroll_speed = None; self.auto_scroll_speed = None;
} }
} }
tab::Command::ChangeLocation(tab_title, tab_path, selection_paths) => { tab::Command::ChangeLocation(tab_title, tab_path, selection_paths) => {
self.activate_nav_model_location(&tab_path); self.activate_nav_model_location(&tab_path);
@ -4160,15 +4172,16 @@ impl Application for App {
apply_to_all, apply_to_all,
tx, tx,
} => { } => {
let military_time = self.config.tab.military_time;
let dialog = widget::dialog() let dialog = widget::dialog()
.title(fl!("replace-title", filename = to.name.as_str())) .title(fl!("replace-title", filename = to.name.as_str()))
.body(fl!("replace-warning-operation")) .body(fl!("replace-warning-operation"))
.control( .control(
to.replace_view(fl!("original-file"), IconSizes::default()) to.replace_view(fl!("original-file"), IconSizes::default(), military_time)
.map(|x| Message::TabMessage(None, x)), .map(|x| Message::TabMessage(None, x)),
) )
.control( .control(
from.replace_view(fl!("replace-with"), IconSizes::default()) from.replace_view(fl!("replace-with"), IconSizes::default(), military_time)
.map(|x| Message::TabMessage(None, x)), .map(|x| Message::TabMessage(None, x)),
) )
.primary_action(widget::button::suggested(fl!("replace")).on_press( .primary_action(widget::button::suggested(fl!("replace")).on_press(
@ -4561,7 +4574,7 @@ impl Application for App {
} }
_ => None, _ => None,
} }
}, }
Event::Mouse(CursorMoved { position: pos }) => Some(Message::CursorMoved(pos)), Event::Mouse(CursorMoved { position: pos }) => Some(Message::CursorMoved(pos)),
_ => None, _ => None,
}), }),
@ -4735,8 +4748,9 @@ impl Application for App {
if let Some(scroll_speed) = self.auto_scroll_speed { if let Some(scroll_speed) = self.auto_scroll_speed {
subscriptions.push( subscriptions.push(
iced::time::every(time::Duration::from_millis(10)).with(scroll_speed) iced::time::every(time::Duration::from_millis(10))
.map(|(scroll_speed, _)| Message::ScrollTab(scroll_speed)) .with(scroll_speed)
.map(|(scroll_speed, _)| Message::ScrollTab(scroll_speed)),
); );
} }

View file

@ -3,7 +3,7 @@
use std::{any::TypeId, num::NonZeroU16, path::PathBuf}; use std::{any::TypeId, num::NonZeroU16, path::PathBuf};
use cosmic::{ use cosmic::{
cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry}, cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, ConfigGet, CosmicConfigEntry},
iced::Subscription, iced::Subscription,
theme, Application, theme, Application,
}; };
@ -197,6 +197,10 @@ pub struct TabConfig {
pub show_hidden: bool, pub show_hidden: bool,
/// Icon zoom /// Icon zoom
pub icon_sizes: IconSizes, pub icon_sizes: IconSizes,
#[serde(skip, default = "military_time_enabled")]
/// 24 hour clock; this is neither serialized nor deserialized because we use the user's global
/// preference rather than save it
pub military_time: bool,
} }
impl Default for TabConfig { impl Default for TabConfig {
@ -206,6 +210,22 @@ impl Default for TabConfig {
folders_first: true, folders_first: true,
show_hidden: false, show_hidden: false,
icon_sizes: IconSizes::default(), icon_sizes: IconSizes::default(),
military_time: military_time_enabled(),
}
}
}
/// Return whether the user enabled military time via the Time applet.
fn military_time_enabled() -> bool {
// Borrowed from COSMIC Greeter
match cosmic_config::Config::new("com.system76.CosmicAppletTime", 1) {
Ok(config_handler) => config_handler.get("military_time").unwrap_or_default(),
Err(err) => {
log::error!(
"failed to create CosmicAppletTime config handler: {:?}",
err
);
false
} }
} }
} }

View file

@ -472,16 +472,17 @@ impl App {
} }
fn preview<'a>(&'a self, kind: &'a PreviewKind) -> Element<'a, tab::Message> { fn preview<'a>(&'a self, kind: &'a PreviewKind) -> Element<'a, tab::Message> {
let military_time = self.tab.config.military_time;
let mut children = Vec::with_capacity(1); let mut children = Vec::with_capacity(1);
match kind { match kind {
PreviewKind::Custom(PreviewItem(item)) => { PreviewKind::Custom(PreviewItem(item)) => {
children.push(item.preview_view(None, IconSizes::default())); children.push(item.preview_view(None, IconSizes::default(), military_time));
} }
PreviewKind::Location(location) => { PreviewKind::Location(location) => {
if let Some(items) = self.tab.items_opt() { if let Some(items) = self.tab.items_opt() {
for item in items.iter() { for item in items.iter() {
if item.location_opt.as_ref() == Some(location) { if item.location_opt.as_ref() == Some(location) {
children.push(item.preview_view(None, self.tab.config.icon_sizes)); children.push(item.preview_view(None, self.tab.config.icon_sizes, military_time));
// Only show one property view to avoid issues like hangs when generating // Only show one property view to avoid issues like hangs when generating
// preview images on thousands of files // preview images on thousands of files
break; break;
@ -493,7 +494,7 @@ impl App {
if let Some(items) = self.tab.items_opt() { if let Some(items) = self.tab.items_opt() {
for item in items.iter() { for item in items.iter() {
if item.selected { if item.selected {
children.push(item.preview_view(None, self.tab.config.icon_sizes)); children.push(item.preview_view(None, self.tab.config.icon_sizes, military_time));
// Only show one property view to avoid issues like hangs when generating // Only show one property view to avoid issues like hangs when generating
// preview images on thousands of files // preview images on thousands of files
break; break;
@ -501,7 +502,7 @@ impl App {
} }
if children.is_empty() { if children.is_empty() {
if let Some(item) = &self.tab.parent_item_opt { if let Some(item) = &self.tab.parent_item_opt {
children.push(item.preview_view(None, self.tab.config.icon_sizes)); children.push(item.preview_view(None, self.tab.config.icon_sizes, military_time));
} }
} }
} }

View file

@ -87,7 +87,9 @@ const DRAG_SCROLL_DISTANCE: f32 = 15.0;
//TODO: adjust for locales? //TODO: adjust for locales?
const DATE_TIME_FORMAT: &str = "%b %-d, %-Y, %-I:%M %p"; const DATE_TIME_FORMAT: &str = "%b %-d, %-Y, %-I:%M %p";
const DATE_TIME_FORMAT_MILITARY: &str = "%b %-d, %-Y, %-H:%M %p";
const TIME_FORMAT: &str = "%-I:%M %p"; const TIME_FORMAT: &str = "%-I:%M %p";
const TIME_FORMAT_MILITARY: &str = "%-H:%M %p";
static SPECIAL_DIRS: Lazy<HashMap<PathBuf, &'static str>> = Lazy::new(|| { static SPECIAL_DIRS: Lazy<HashMap<PathBuf, &'static str>> = Lazy::new(|| {
let mut special_dirs = HashMap::new(); let mut special_dirs = HashMap::new();
if let Some(dir) = dirs::document_dir() { if let Some(dir) = dirs::document_dir() {
@ -380,10 +382,13 @@ fn format_permissions(metadata: &Metadata, owner: PermissionOwner) -> String {
} }
} }
struct FormatTime(SystemTime); struct FormatTime {
pub time: SystemTime,
pub military_time: bool,
}
impl FormatTime { impl FormatTime {
fn from_secs(secs: i64) -> Option<Self> { fn from_secs(secs: i64, military_time: bool) -> Option<Self> {
// This looks convoluted because we need to ensure the units match up // This looks convoluted because we need to ensure the units match up
let secs: u64 = secs.try_into().ok()?; let secs: u64 = secs.try_into().ok()?;
let now = SystemTime::now(); let now = SystemTime::now();
@ -393,31 +398,51 @@ impl FormatTime {
.ok() .ok()
.and_then(|now_secs| now_secs.checked_sub(secs)) .and_then(|now_secs| now_secs.checked_sub(secs))
.map(Duration::from_secs)?; .map(Duration::from_secs)?;
now.checked_add(filetime_diff).map(FormatTime) now.checked_add(filetime_diff).map(|time| Self {
time,
military_time,
})
} }
} }
impl Display for FormatTime { impl Display for FormatTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let date_time = chrono::DateTime::<chrono::Local>::from(self.0); let date_time = chrono::DateTime::<chrono::Local>::from(self.time);
let now = chrono::Local::now(); let now = chrono::Local::now();
if date_time.date_naive() == now.date_naive() { if date_time.date_naive() == now.date_naive() {
write!( write!(
f, f,
"{}, {}", "{}, {}",
fl!("today"), fl!("today"),
date_time.format_localized(TIME_FORMAT, *LANGUAGE_CHRONO) date_time.format_localized(
if self.military_time {
TIME_FORMAT_MILITARY
} else {
TIME_FORMAT
},
*LANGUAGE_CHRONO
)
) )
} else { } else {
date_time date_time
.format_localized(DATE_TIME_FORMAT, *LANGUAGE_CHRONO) .format_localized(
if self.military_time {
DATE_TIME_FORMAT_MILITARY
} else {
DATE_TIME_FORMAT
},
*LANGUAGE_CHRONO,
)
.fmt(f) .fmt(f)
} }
} }
} }
fn format_time(time: SystemTime) -> FormatTime { const fn format_time(time: SystemTime, military_time: bool) -> FormatTime {
FormatTime(time) FormatTime {
time,
military_time,
}
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
@ -1497,6 +1522,7 @@ impl Item {
&'a self, &'a self,
mime_app_cache_opt: Option<&'a mime_app::MimeAppCache>, mime_app_cache_opt: Option<&'a mime_app::MimeAppCache>,
sizes: IconSizes, sizes: IconSizes,
military_time: bool,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
let cosmic_theme::Spacing { let cosmic_theme::Spacing {
space_xxxs, space_xxxs,
@ -1558,21 +1584,21 @@ impl Item {
if let Ok(time) = metadata.created() { if let Ok(time) = metadata.created() {
details = details.push(widget::text::body(fl!( details = details.push(widget::text::body(fl!(
"item-created", "item-created",
created = format_time(time).to_string() created = format_time(time, military_time).to_string()
))); )));
} }
if let Ok(time) = metadata.modified() { if let Ok(time) = metadata.modified() {
details = details.push(widget::text::body(fl!( details = details.push(widget::text::body(fl!(
"item-modified", "item-modified",
modified = format_time(time).to_string() modified = format_time(time, military_time).to_string()
))); )));
} }
if let Ok(time) = metadata.accessed() { if let Ok(time) = metadata.accessed() {
details = details.push(widget::text::body(fl!( details = details.push(widget::text::body(fl!(
"item-accessed", "item-accessed",
accessed = format_time(time).to_string() accessed = format_time(time, military_time).to_string()
))); )));
} }
@ -1638,7 +1664,12 @@ impl Item {
column.into() column.into()
} }
pub fn replace_view(&self, heading: String, sizes: IconSizes) -> Element<'_, Message> { pub fn replace_view(
&self,
heading: String,
sizes: IconSizes,
military_time: bool,
) -> Element<'_, Message> {
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing; let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
let mut row = widget::row().spacing(space_xxxs); let mut row = widget::row().spacing(space_xxxs);
@ -1662,7 +1693,7 @@ impl Item {
if let Ok(time) = metadata.modified() { if let Ok(time) = metadata.modified() {
column = column.push(widget::text::body(format!( column = column.push(widget::text::body(format!(
"Last modified: {}", "Last modified: {}",
format_time(time) format_time(time, military_time)
))); )));
} }
} }
@ -1873,7 +1904,7 @@ impl Tab {
viewport_rect: None, viewport_rect: None,
virtual_cursor_offset: None, virtual_cursor_offset: None,
last_scroll_position: None, last_scroll_position: None,
last_scroll_offset: None last_scroll_offset: None,
} }
} }
@ -2228,7 +2259,7 @@ impl Tab {
// if our mouse is above the scrollable viewport, we want to scroll up // if our mouse is above the scrollable viewport, we want to scroll up
let drag_start_point = Point { let drag_start_point = Point {
x: viewport.x, x: viewport.x,
y: viewport.y y: viewport.y,
}; };
// diff_y should be NEGATIVE here when close to y=0 (above the MouseArea) // diff_y should be NEGATIVE here when close to y=0 (above the MouseArea)
// and positive when below the viewport // and positive when below the viewport
@ -2241,39 +2272,30 @@ impl Tab {
0.0 0.0
}; };
commands.push(Command::AutoScroll(Some(scroll_y))); commands.push(Command::AutoScroll(Some(scroll_y)));
} } else {
else {
if let Some(last_scroll_offset) = self.last_scroll_offset { if let Some(last_scroll_offset) = self.last_scroll_offset {
if let Some(last_scroll_position) = self.last_scroll_position { if let Some(last_scroll_position) = self.last_scroll_position {
self.virtual_cursor_offset = Some(Point { self.virtual_cursor_offset = Some(Point {
x: 0.0, x: 0.0,
y: (pos.y - last_scroll_position.y + last_scroll_offset.y) y: (pos.y - last_scroll_position.y
+ last_scroll_offset.y),
}); });
} }
} else {
}
else {
if let Some(last_scroll_position) = self.last_scroll_position { if let Some(last_scroll_position) = self.last_scroll_position {
self.virtual_cursor_offset = Some(Point { self.virtual_cursor_offset = Some(Point {
x: 0.0, x: 0.0,
y: (pos.y - last_scroll_position.y) y: (pos.y - last_scroll_position.y),
});
}
else {
self.virtual_cursor_offset = Some(Point {
x: 0.0,
y: 0.0
}); });
} else {
self.virtual_cursor_offset = Some(Point { x: 0.0, y: 0.0 });
} }
} }
commands.push(Command::AutoScroll(None)); commands.push(Command::AutoScroll(None));
} }
} } else {
else {
// reset our virtual cursor offset when we're back in bounds // reset our virtual cursor offset when we're back in bounds
self.virtual_cursor_offset = None; self.virtual_cursor_offset = None;
self.last_scroll_position = Some(pos); self.last_scroll_position = Some(pos);
@ -2292,13 +2314,11 @@ impl Tab {
x: viewport.x - scroll_pos.x, x: viewport.x - scroll_pos.x,
y: viewport.y - scroll_pos.y, y: viewport.y - scroll_pos.y,
width: viewport.width, width: viewport.width,
height: viewport.height height: viewport.height,
}); });
} } else {
else {
self.viewport_rect = Some(viewport); self.viewport_rect = Some(viewport);
} }
} }
Message::DragEnd(_) => { Message::DragEnd(_) => {
self.clicked = None; self.clicked = None;
@ -2947,7 +2967,7 @@ impl Tab {
Message::ScrollTab(scroll_speed) => { Message::ScrollTab(scroll_speed) => {
let mut new_offset = Point { let mut new_offset = Point {
x: 0.0, x: 0.0,
y: scroll_speed y: scroll_speed,
}; };
if let Some(virtual_cursor_offset) = self.virtual_cursor_offset { if let Some(virtual_cursor_offset) = self.virtual_cursor_offset {
@ -2961,17 +2981,20 @@ impl Tab {
if let Some(global_cursor_position) = self.global_cursor_position { if let Some(global_cursor_position) = self.global_cursor_position {
new_offset.x = global_cursor_position.x - last_scroll_position.x; new_offset.x = global_cursor_position.x - last_scroll_position.x;
} }
} }
self.virtual_cursor_offset = Some(new_offset); self.virtual_cursor_offset = Some(new_offset);
self.last_scroll_offset = Some(new_offset); self.last_scroll_offset = Some(new_offset);
commands.push(Command::Iced( commands.push(Command::Iced(
scrollable::scroll_by(self.scrollable_id.clone(), AbsoluteOffset { scrollable::scroll_by(
x: 0.0, self.scrollable_id.clone(),
y: scroll_speed AbsoluteOffset {
}).into(), x: 0.0,
y: scroll_speed,
},
)
.into(),
)); ));
} }
Message::ScrollToFocus => { Message::ScrollToFocus => {
@ -4311,14 +4334,17 @@ impl Tab {
y += 1; y += 1;
} }
let military_time = self.config.military_time;
let modified_text = match &item.metadata { let modified_text = match &item.metadata {
ItemMetadata::Path { metadata, .. } => match metadata.modified() { ItemMetadata::Path { metadata, .. } => match metadata.modified() {
Ok(time) => format_time(time).to_string(), Ok(time) => format_time(time, military_time).to_string(),
Err(_) => String::new(), Err(_) => String::new(),
}, },
ItemMetadata::Trash { entry, .. } => FormatTime::from_secs(entry.time_deleted) ItemMetadata::Trash { entry, .. } => {
.map(|t| t.to_string()) FormatTime::from_secs(entry.time_deleted, military_time)
.unwrap_or_default(), .map(|t| t.to_string())
.unwrap_or_default()
}
_ => String::new(), _ => String::new(),
}; };