Compare commits

..

No commits in common. "local/libcosmic-path-patch" and "pre-1.6-merge-20260819" have entirely different histories.

55 changed files with 1886 additions and 3290 deletions

1785
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "cosmic-files" name = "cosmic-files"
version = "1.9.0" version = "1.3.0"
authors = ["Jeremy Soller <jeremy@system76.com>"] authors = ["Jeremy Soller <jeremy@system76.com>"]
edition = "2024" edition = "2024"
license = "GPL-3.0-only" license = "GPL-3.0-only"
@ -11,16 +11,16 @@ anyhow = "1"
jiff = "0.2" jiff = "0.2"
jiff-icu = "0.2" jiff-icu = "0.2"
icu = { version = "2.2.0", features = ["compiled_data"] } icu = { version = "2.2.0", features = ["compiled_data"] }
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "c0cff4d", optional = true } cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "c253ec1", optional = true }
cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true } cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true }
dirs = "6.0.0" dirs = "6.0.0"
gio = { version = "0.21", optional = true } gio = { version = "0.21", optional = true }
glib = { version = "0.21", optional = true } glib = { version = "0.21", optional = true }
glob = "0.3" glob = "0.3"
ignore = "0.4" ignore = "0.4"
image.workspace = true image = "0.25"
libc = "0.2" libc = "0.2"
log.workspace = true log = "0.4"
mime_guess = "2" mime_guess = "2"
notify-debouncer-full = "0.7" notify-debouncer-full = "0.7"
notify-rust = { version = "4", optional = true } notify-rust = { version = "4", optional = true }
@ -166,8 +166,4 @@ mime = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch =
cosmic-text = { path = "../cosmic-text" } cosmic-text = { path = "../cosmic-text" }
[workspace] [workspace]
members = ["cosmic-files-applet", "cosmic-files-thumbnailer"] members = ["cosmic-files-applet"]
[workspace.dependencies]
image = "0.25"
log = "0.4"

View file

@ -1,10 +1,10 @@
[package] [package]
name = "cosmic-files-applet" name = "cosmic-files-applet"
version = "1.9.0" version = "1.2.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
log.workspace = true log = "0.4"
zbus = "4" # Blocking connection in zbus 5 hangs zbus = "4" # Blocking connection in zbus 5 hangs
[dependencies.cosmic-files] [dependencies.cosmic-files]

View file

@ -1,8 +0,0 @@
[package]
name = "cosmic-files-thumbnailer"
version = "1.9.0"
edition = "2024"
[dependencies]
image.workspace = true
pelite = "0.10"

View file

@ -1,254 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
use image::{DynamicImage, ImageFormat, RgbaImage};
use std::error::Error;
use std::ffi::{OsStr, OsString};
use std::io;
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
thumbnail_from_args(std::env::args_os().skip(1))
}
#[allow(dead_code)]
pub fn thumbnail_from_args(mut args: impl Iterator<Item = OsString>) -> Result<(), Box<dyn Error>> {
let usage = || {
io::Error::new(
io::ErrorKind::InvalidInput,
"usage: cosmic-files-thumbnailer OUTPUT --size SIZE INPUT",
)
};
let output = args.next().ok_or_else(usage)?;
if args.next().as_deref() != Some(OsStr::new("--size")) {
return Err(usage().into());
}
let size = args
.next()
.and_then(|arg| arg.into_string().ok())
.and_then(|arg| arg.parse::<u32>().ok())
.filter(|size| *size > 0)
.ok_or_else(usage)?;
let input = args.next().ok_or_else(usage)?;
if args.next().is_some() {
return Err(usage().into());
}
thumbnail(Path::new(&input), Path::new(&output), size)
}
#[allow(dead_code)]
pub fn thumbnail(input: &Path, output: &Path, size: u32) -> Result<(), Box<dyn Error>> {
let icon = thumbnail_image(input, size)?;
DynamicImage::ImageRgba8(icon).save_with_format(output, ImageFormat::Png)?;
Ok(())
}
pub fn thumbnail_image(input: &Path, size: u32) -> Result<RgbaImage, Box<dyn Error>> {
if size == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"thumbnail size must be positive",
)
.into());
}
// Map the PE so only the headers and resource pages touched by pelite are read.
let data = pelite::FileMap::open(input)?;
let bytes = data.as_ref();
if bytes.len() < 64 || !bytes.starts_with(b"MZ") {
return Err(io::Error::new(io::ErrorKind::InvalidData, "input is not a PE file").into());
}
let file = pelite::PeFile::from_bytes(bytes).map_err(invalid_data)?;
let resources = file.resources().map_err(invalid_data)?;
let (_name, group) = resources
.icons()
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "PE file has no icon group"))?
.map_err(invalid_data)?;
let mut ico = Vec::new();
group.write(&mut ico).map_err(invalid_data)?;
Ok(
DynamicImage::ImageRgba8(largest_icon(&ico).map_err(invalid_data)?)
.thumbnail(size, size)
.into_rgba8(),
)
}
fn invalid_data(error: impl std::fmt::Display) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, error.to_string())
}
fn largest_icon(ico: &[u8]) -> Result<RgbaImage, String> {
if ico.len() < 6 {
return Err("ICO header is truncated".to_string());
}
let count = usize::from(u16::from_le_bytes([ico[4], ico[5]]));
let mut best: Option<(u64, RgbaImage)> = None;
let mut errors = Vec::new();
for i in 0..count.min(32) {
let entry = 6 + i * 16;
if ico.len() < entry + 16 {
break;
}
let size = u32::from_le_bytes([
ico[entry + 8],
ico[entry + 9],
ico[entry + 10],
ico[entry + 11],
]) as usize;
let offset = u32::from_le_bytes([
ico[entry + 12],
ico[entry + 13],
ico[entry + 14],
ico[entry + 15],
]) as usize;
let Some(end) = offset.checked_add(size) else {
continue;
};
let Some(data) = ico.get(offset..end) else {
continue;
};
match decode_icon(&ico[entry..entry + 16], data) {
Ok(image) => {
let pixels = u64::from(image.width()) * u64::from(image.height());
if best
.as_ref()
.is_none_or(|(best_pixels, _)| pixels > *best_pixels)
{
best = Some((pixels, image));
}
}
Err(err) => errors.push(format!("frame {i}: {err}")),
}
}
best.map(|(_, image)| image).ok_or_else(|| {
if errors.is_empty() {
"PE icon contains no complete frames".to_string()
} else {
format!("PE icon could not be decoded ({})", errors.join("; "))
}
})
}
fn decode_icon(entry: &[u8], data: &[u8]) -> Result<RgbaImage, String> {
if data.starts_with(b"\x89PNG\r\n\x1a\n") {
return image::ImageReader::with_format(io::Cursor::new(data), ImageFormat::Png)
.decode()
.map(DynamicImage::into_rgba8)
.map_err(|err| err.to_string());
}
let capacity = 22usize
.checked_add(data.len())
.ok_or_else(|| "frame is too large".to_string())?;
let data_len = u32::try_from(data.len()).map_err(|_| "frame is too large".to_string())?;
let mut ico = Vec::with_capacity(capacity);
ico.extend_from_slice(&[0, 0, 1, 0, 1, 0]);
ico.extend_from_slice(entry);
ico[14..18].copy_from_slice(&data_len.to_le_bytes());
ico[18..22].copy_from_slice(&22u32.to_le_bytes());
ico.extend_from_slice(data);
image::ImageReader::with_format(io::Cursor::new(ico), ImageFormat::Ico)
.decode()
.map(DynamicImage::into_rgba8)
.map_err(|err| err.to_string())
}
#[cfg(test)]
mod tests {
use super::{largest_icon, thumbnail_from_args};
use image::{DynamicImage, ImageFormat, Rgb, RgbImage, Rgba, RgbaImage};
use std::ffi::OsString;
use std::io::Cursor;
#[test]
fn malformed_thumbnail_request_returns_an_error() {
let args = [OsString::from("output.png")];
assert!(thumbnail_from_args(args.into_iter()).is_err());
}
#[test]
fn largest_icon_skips_invalid_entries() {
let image = RgbaImage::from_pixel(2, 2, Rgba([1, 2, 3, 255]));
let mut png = Cursor::new(Vec::new());
DynamicImage::ImageRgba8(image)
.write_to(&mut png, ImageFormat::Png)
.unwrap();
let png = png.into_inner();
let mut ico = vec![0, 0, 1, 0, 2, 0];
let mut invalid_entry = [0; 16];
invalid_entry[8..12].copy_from_slice(&4u32.to_le_bytes());
invalid_entry[12..16].copy_from_slice(&u32::MAX.to_le_bytes());
ico.extend_from_slice(&invalid_entry);
let mut valid_entry = [0; 16];
valid_entry[..8].copy_from_slice(&[2, 2, 0, 0, 1, 0, 32, 0]);
valid_entry[8..12].copy_from_slice(&u32::try_from(png.len()).unwrap().to_le_bytes());
valid_entry[12..16].copy_from_slice(&38u32.to_le_bytes());
ico.extend_from_slice(&valid_entry);
ico.extend_from_slice(&png);
let decoded = largest_icon(&ico).expect("valid icon after malformed entry");
assert_eq!(decoded.dimensions(), (2, 2));
assert_eq!(decoded.get_pixel(0, 0), &Rgba([1, 2, 3, 255]));
}
#[test]
fn dib_and_mask_is_applied() {
let mut dib = vec![0; 40];
dib[0..4].copy_from_slice(&40u32.to_le_bytes());
dib[4..8].copy_from_slice(&2i32.to_le_bytes());
dib[8..12].copy_from_slice(&4i32.to_le_bytes());
dib[12..14].copy_from_slice(&1u16.to_le_bytes());
dib[14..16].copy_from_slice(&24u16.to_le_bytes());
// Two bottom-up BGR rows, padded to four-byte boundaries.
dib.extend_from_slice(&[0, 0, 255, 0, 0, 255, 0, 0]);
dib.extend_from_slice(&[0, 0, 255, 0, 0, 255, 0, 0]);
// The first mask row is the bottom image row; its first pixel is transparent.
dib.extend_from_slice(&[0b1000_0000, 0, 0, 0]);
dib.extend_from_slice(&[0, 0, 0, 0]);
let mut ico = vec![0, 0, 1, 0, 1, 0];
let mut entry = [0; 16];
entry[..8].copy_from_slice(&[2, 2, 0, 0, 1, 0, 24, 0]);
entry[8..12].copy_from_slice(&u32::try_from(dib.len()).unwrap().to_le_bytes());
entry[12..16].copy_from_slice(&22u32.to_le_bytes());
ico.extend_from_slice(&entry);
ico.extend_from_slice(&dib);
let decoded = largest_icon(&ico).expect("valid DIB icon");
assert_eq!(decoded.get_pixel(0, 0), &Rgba([255, 0, 0, 255]));
assert_eq!(decoded.get_pixel(0, 1), &Rgba([255, 0, 0, 0]));
}
#[test]
fn rgb_png_icon_is_decoded() {
let image = RgbImage::from_pixel(2, 2, Rgb([1, 2, 3]));
let mut png = Cursor::new(Vec::new());
DynamicImage::ImageRgb8(image)
.write_to(&mut png, ImageFormat::Png)
.unwrap();
let png = png.into_inner();
let mut ico = vec![0, 0, 1, 0, 1, 0];
let mut entry = [0; 16];
entry[..8].copy_from_slice(&[2, 2, 0, 0, 1, 0, 24, 0]);
entry[8..12].copy_from_slice(&u32::try_from(png.len()).unwrap().to_le_bytes());
entry[12..16].copy_from_slice(&22u32.to_le_bytes());
ico.extend_from_slice(&entry);
ico.extend_from_slice(&png);
let decoded = largest_icon(&ico).expect("valid RGB PNG icon");
assert_eq!(decoded.get_pixel(0, 0), &Rgba([1, 2, 3, 255]));
}
}

42
debian/changelog vendored
View file

@ -1,45 +1,3 @@
cosmic-files (1.9.0) noble; urgency=medium
* Epoch 1.9.0 version update
-- Michael Murphy <michael@mmurphy.dev> Mon, 14 Sep 2026 20:35:19 +0200
cosmic-files (1.8.0) noble; urgency=medium
* Epoch 1.8.0 version update
-- Jeremy Soller <jeremy@system76.com> Wed, 02 Sep 2026 08:14:53 -0600
cosmic-files (1.7.0) noble; urgency=medium
* Epoch 1.7.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 25 Aug 2026 09:20:22 -0600
cosmic-files (1.6.0) noble; urgency=medium
* Epoch 1.6.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 18 Aug 2026 07:13:48 -0600
cosmic-files (1.5.0) noble; urgency=medium
* Epoch 1.5.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 28 Jul 2026 09:14:09 -0600
cosmic-files (1.4.0) noble; urgency=medium
* Epoch 1.4.0 version update
-- Jeremy Soller <jeremy@system76.com> Mon, 20 Jul 2026 14:36:06 -0600
cosmic-files (1.3.0) noble; urgency=medium
* Epoch 1.3.0 version update
-- Jeremy Soller <jeremy@system76.com> Sun, 12 Jul 2026 13:21:18 -0600
cosmic-files (1.2.0) noble; urgency=medium cosmic-files (1.2.0) noble; urgency=medium
* Epoch 1.2.0 version update * Epoch 1.2.0 version update

View file

@ -1,6 +1,4 @@
tar-ignore=.github tar-ignore=.github
tar-ignore=target/.rustc_info.json tar-ignore=.vscode
tar-ignore=target/CACHEDIR.TAG
tar-ignore=target/debug
tar-ignore=target/release
tar-ignore=vendor tar-ignore=vendor
tar-ignore=target

View file

@ -84,7 +84,7 @@ settings = الإعدادات
### Appearance ### Appearance
appearance = المظهر appearance = المظهر
theme = السمة theme = النسق
match-desktop = طابق سطح المكتب match-desktop = طابق سطح المكتب
dark = داكن dark = داكن
light = فاتح light = فاتح
@ -110,7 +110,7 @@ new-tab = لسان جديد
new-window = نافذة جديدة new-window = نافذة جديدة
rename = غيِّر الاسم... rename = غيِّر الاسم...
close-tab = أغلق اللسان close-tab = أغلق اللسان
quit = إنهِ quit = أنهِ
## Edit ## Edit
@ -147,7 +147,7 @@ icon-size-and-spacing = حجم الأيقونة والتباعد
icon-size = حجم الأيقونة icon-size = حجم الأيقونة
grid-spacing = تباعد شبكي grid-spacing = تباعد شبكي
trashed-on = مهمل trashed-on = مهمل
details = تفاصيل details = التفاصيل
pause = ألبث pause = ألبث
resume = استئناف resume = استئناف
create-archive = أنشئ أرشيف create-archive = أنشئ أرشيف
@ -383,7 +383,3 @@ context-action-confirm-warning =
*[other] عنصر *[other] عنصر
}. }.
run = شغِّل run = شغِّل
checksum = تدقيق المجموع { $kind }
calculate = احسب
error = خطأ
rename-confirm = أعد التسمية

View file

@ -77,7 +77,7 @@ name-no-slashes = El nom no pot contenir barres.
cancel = Cancel·lar cancel = Cancel·lar
create = Crea create = Crea
open = Obre open = Obre
open-file = Obre el fitxer open-file = Obre el fixer
open-folder = Obre la carpeta open-folder = Obre la carpeta
open-in-new-tab = Obre en una pestanya nova open-in-new-tab = Obre en una pestanya nova
open-in-new-window = Obre en una finestra nova open-in-new-window = Obre en una finestra nova

View file

@ -428,9 +428,3 @@ context-action-confirm-warning =
}. }.
run = Spustit run = Spustit
rename-confirm = Přejmenovat rename-confirm = Přejmenovat
checksum = Kontrolní součet { $kind }
calculate = Vypočítat
error = Chyba
search-application = Hledat podle názvu aplikace
sidebar-label = Popisek v postranním panelu
change-sidebar-label = Změnit popisek

View file

@ -1,43 +1,35 @@
cosmic-files = COSMIC Filhåndtering cosmic-files = COSMIC Filer
empty-folder = Tom mappe empty-folder = Tom mappe
empty-folder-hidden = Tom mappe (har skjulte emner) empty-folder-hidden = Tom mappe (har skjulte filer)
no-results = Fandt ingen resultater no-results = Ingen resultater
filesystem = Filsystem filesystem = Filsystem
home = Hjem home = Hjem
networks = Netværk networks = Netværk
notification-in-progress = Fil-operationer er igang notification-in-progress = Filoperationer er igangværende.
trash = Papirkurv trash = Skraldespand
recents = Seneste recents = Seneste
undo = Fortryd undo = Fortryd
today = I dag today = I dag
# Desktop view options # Desktop view options
desktop-view-options = Valgmuligheder for skrivebordsvisning... desktop-view-options = Indstillinger for skrivebordsudseende...
show-on-desktop = Vis på Skrivebord show-on-desktop = Vis på Skrivebordet
desktop-folder-content = Skrivebords-mappeindhold desktop-folder-content = Indhold på Skrivebordet
mounted-drives = Monterede drev mounted-drives = Monterede drev
trash-folder-icon = Papirkurv-ikon trash-folder-icon = Skraldespandsikon
icon-size-and-spacing = Ikonstørrelse og -afstand icon-size-and-spacing = Ikonstørrelse og afstand
icon-size = Ikonstørrelse icon-size = Ikonstørrelse
# List view # List view
name = Navn name = Navn
modified = Ændret modified = Ændret
trashed-on = Kasseret trashed-on = Smidt ud
size = Størrelse size = Størrelse
# Progress footer # Progress footer
details = Detaljer details = Detaljer
dismiss = Luk meddelelse dismiss = Afvis besked
operations-running = operations-running = { $running } operationer er i gang ({ $percent }%)...
{ $running } { $running -> operations-running-finished = { $running } operationer er i gang ({ $percent }%), { $finished } færdiggjort...
[one] operation
*[other] operationer
} kører ({ $percent }%)...
operations-running-finished =
{ $running } { $running ->
[one] operation
*[other] operationer
} kører ({ $percent }%), { $finished } færdig...
pause = Pause pause = Pause
resume = Genoptag resume = Fortsæt
# Dialogs # Dialogs
@ -48,12 +40,12 @@ create-archive = Opret arkiv
## Empty Trash Dialog ## Empty Trash Dialog
empty-trash = Tøm papirkurv empty-trash = Tøm skraldespand
empty-trash-warning = Emner i Papirkurv-mappen vil blive slettet permanent empty-trash-warning = Er du sikker på du vil slette alle objekter i Skraldespanden permanent?
## Mount Error Dialog ## Mount Error Dialog
mount-error = Ude af stand til at tilgå drev mount-error = Kan ikke tilgå drev
## New File/Folder Dialog ## New File/Folder Dialog
@ -61,22 +53,22 @@ create-new-file = Opret ny fil
create-new-folder = Opret ny mappe create-new-folder = Opret ny mappe
file-name = Filnavn file-name = Filnavn
folder-name = Mappenavn folder-name = Mappenavn
file-already-exists = En fil med det navn eksisterer allerede file-already-exists = En fil med det navn eksisterer allerede.
folder-already-exists = En mappe med det navn eksisterer allerede folder-already-exists = En mappe med det navn eksisterer allerede.
name-hidden = Navne begyndende med "." vil være skjulte name-hidden = Navne begyndende med "." vil blive skjult.
name-invalid = Navn må ikke være "{ $filename }" name-invalid = Navn kan ikke være "{ $filename }".
name-no-slashes = Navn må ikke indeholde skråstreger name-no-slashes = Navn kan ikke indeholde skråstreg.
## Open/Save Dialog ## Open/Save Dialog
cancel = Afbryd cancel = Annullér
create = Opret create = Opret
open = Åbn open = Åbn
open-file = Åbn fil open-file = Åbn fil
open-folder = Åbn mappe open-folder = Åbn mappe
open-in-new-tab = Åbn i nyt faneblad open-in-new-tab = Åbn i ny fane
open-in-new-window = Åbn i nyt vindue open-in-new-window = Åbn i nyt vindue
open-item-location = Åbn emne-placering open-item-location = Åbn placering for objekt
open-multiple-files = Åbn flere filer open-multiple-files = Åbn flere filer
open-multiple-folders = Åbn flere mapper open-multiple-folders = Åbn flere mapper
save = Gem save = Gem
@ -95,20 +87,20 @@ rename-folder = Omdøb mappe
## Replace Dialog ## Replace Dialog
replace = Erstat replace = Erstat
replace-title = "{ $filename }" eksisterer allerede i denne placering replace-title = "{ $filename }" eksisterer allerede på denne placering.
replace-warning = Ønsker du at erstatte den med den, du er ved at gemme? Erstatning af den vil overskrive dens indhold. replace-warning = Vil du erstatte den med den du er ved at gemme? Hvis du erstatter den, overskriver du dens indhold.
replace-warning-operation = Ønsker du at erstatte den? Erstatning af den vil overskrive dens indhold. replace-warning-operation = Vil du erstatte den? Hvis du erstatter den, overskriver du dens indhold.
original-file = Original fil original-file = Original fil
replace-with = Erstat med replace-with = Erstat med
apply-to-all = Anvend på alle apply-to-all = Anvend for alle
keep-both = Behold begge keep-both = Behold begge
skip = Spring over skip = Spring over
## Set as Executable and Launch Dialog ## Set as Executable and Launch Dialog
set-executable-and-launch = Angiv som eksekverbar og start set-executable-and-launch = Indstil som eksekverbar fil og start
set-executable-and-launch-description = Vil du angive "{ $name }" som eksekverbar og starte den? set-executable-and-launch-description = Vil du indstille "{ $name }" som en eksekverbar fil og starte den?
set-and-launch = Angiv og start set-and-launch = Indstil og start
## Metadata Dialog ## Metadata Dialog
@ -247,13 +239,13 @@ settings = Indstillinger
appearance = Udseende appearance = Udseende
theme = Tema theme = Tema
match-desktop = Følg skrivebordets indstillinger match-desktop = Match skrivebord
dark = Mørkt dark = Mørk
light = Lyst light = Lys
# Context menu # Context menu
add-to-sidebar = Tilføj til sidebjælke add-to-sidebar = Tilføj til sidebjælke
compress = Komprimer… compress = Komprimér
extract-here = Uddrag extract-here = Extract
new-file = Ny fil... new-file = Ny fil...
new-folder = Ny mappe... new-folder = Ny mappe...
open-in-terminal = Åbn i terminal open-in-terminal = Åbn i terminal
@ -288,15 +280,15 @@ quit = Afslut
edit = Redigér edit = Redigér
cut = Klip cut = Klip
copy = Kopiér copy = Kopiér
paste = Indsæt paste = Sæt ind
select-all = Vælg alle select-all = Vælg alt
## View ## View
zoom-in = Zoom ind zoom-in = Zoom ind
default-size = Standardstørrelse default-size = Standardstørrelse
zoom-out = Zoom ud zoom-out = Zoom ud
view = Vis view = Visning
grid-view = Gitter-visning grid-view = Gitter-visning
list-view = Liste-visning list-view = Liste-visning
show-hidden-files = Vis skjulte filer show-hidden-files = Vis skjulte filer
@ -314,102 +306,3 @@ sort-newest-first = Nyeste først
sort-oldest-first = Ældste først sort-oldest-first = Ældste først
sort-smallest-to-largest = Mindste til største sort-smallest-to-largest = Mindste til største
sort-largest-to-smallest = Største til mindste sort-largest-to-smallest = Største til mindste
delete = Slet
run = Kør
repository = Pakkearkiv
grid-spacing = Gitter-afstand
extract-password-required = Adgangskode påkrævet
extract-to = Udpak Til...
extract-to-title = Udpak til mappe
empty-trash-title = Tøm papirkurv?
keywords = Mappe;Håndtering;
comment = Filhåndteringsprogram til COSMIC-skrivebordet
copy-to-title = Vælg kopi destination
copy-to-button-label = Kopier
move-to-title = Vælg flytte destination
move-to-button-label = Flyt
deleted =
Slettede { $items } { $items ->
[one] element
*[other] elementer
} fra { trash }
removing-from-recents =
Fjerner { $items } { $items ->
[one] element
*[other] elementer
} fra { recents }
type-to-search-enter-path = Indtast stien til mappen eller filen
permanently-deleting =
Sletter permanent { $items } { $items ->
[one] element
*[other] elementer
}
execute-only = Kun kørsel
removed-from-recents =
Fjernede { $items } { $items ->
[one] element
*[other] elementer
} fra { recents }
permanently-deleted =
Slettede permanent { $items } { $items ->
[one] element
*[other] elementer
}
write-only = Kun skrivning
remove = Fjern
context-action = Konteksthandling
context-action-confirm-title = Kør “{ $name }”?
context-action-confirm-warning =
Dette vil køre på { $items } { $items ->
[one] item
*[other] items
}.
selected-items = De { $items } valgte elementer
mixed = Blandet
none = Ingen
write-execute = Skriv og kør
read-only = Kun læsning
read-execute = Læs og kør
read-write = Læs og skriv
read-write-execute = Læs, skriv og kør
keep = Behold
support = Support
progress-failed = { $percent } %, mislykkedes
pasted-image = Indsat billede
pasted-text = Indsat tekst
pasted-video = Indsat video
deleting =
Sletter { $items } { $items ->
[one] element
*[other] elementer
} fra { trash } ({ $progress })...
setting-permissions = Indstiller tilladelser for “{ $name }” til { $mode }
set-permissions = Sæt tilladelser for “{ $name }” til { $mode }
show-recents = Seneste-mappe i sidepanelet
type-to-search = Skriv for at søge
type-to-search-recursive = Søger i den aktuelle mappe og alle undermapper
type-to-search-select = Vælger den første matchende fil eller mappe
clear-recents-history = Ryd seneste historik
copy-to = Kopiér til…
delete-permanently = Slet permanent
eject = Skub ud
move-to = Flyt til…
remove-from-recents = Fjern fra seneste
reload-folder = Genindlæs mappe
copy-path = Kopiér sti
rename-confirm = Omdøb
calculate = Udregn
error = Fejl
checksum = { $kind }-kontrolsum
other-apps = Andre applikationer
related-apps = Relaterede applikationer
permanently-delete-question = Slet permanent?
permanently-delete-warning = { $target } vil blive slettet permanent. Denne handling kan ikke fortrydes.
open-with = Åbn med
favorite-path-error = Fejl ved åbning af sti
favorite-path-error-description =
Ude af stand til at åbne "{ $path }"
"{ $path }" Eksisterer muligvis ikke, eller du har måske ikketilladelse til at åbne den
Ønsker du at fjerne den fra sidebjælken?
single-click = Enkelt klik for at åbne

View file

@ -11,7 +11,7 @@ recents = Zuletzt verwendet
undo = Rückgängig undo = Rückgängig
today = Heute today = Heute
# Optionen für die Desktop-Ansicht # Optionen für die Desktop-Ansicht
desktop-view-options = Optionen für die Desktop-Ansicht ... desktop-view-options = Optionen für die Desktop-Ansicht…
show-on-desktop = Auf Desktop anzeigen show-on-desktop = Auf Desktop anzeigen
desktop-folder-content = Inhalt des Desktop-Ordners desktop-folder-content = Inhalt des Desktop-Ordners
mounted-drives = Eingehängte Laufwerke mounted-drives = Eingehängte Laufwerke
@ -31,12 +31,12 @@ operations-running =
{ $running } { $running -> { $running } { $running ->
[one] laufender Vorgang [one] laufender Vorgang
*[other] laufende Vorgänge *[other] laufende Vorgänge
} ({ $percent } %) ... } ({ $percent } %)...
operations-running-finished = operations-running-finished =
{ $running } { $running -> { $running } { $running ->
[one] laufender Vorgang [one] laufender Vorgang
*[other] laufende Vorgänge *[other] laufende Vorgänge
} ({ $percent } %), { $finished } abgeschlossen ... } ({ $percent } %), { $finished } abgeschlossen...
pause = Pause pause = Pause
resume = Fortsetzen resume = Fortsetzen
@ -50,7 +50,7 @@ create-archive = Archiv erstellen
## Entpacken-Dialog ## Entpacken-Dialog
extract-password-required = Passwort erforderlich extract-password-required = Passwort erforderlich
extract-to = Entpacken nach ... extract-to = Entpacken nach...
extract-to-title = In Ordner entpacken extract-to-title = In Ordner entpacken
## Dialog zum Leeren des Papierkorbs ## Dialog zum Leeren des Papierkorbs
@ -179,7 +179,7 @@ keep = Behalten
add-network-drive = Netzlaufwerk hinzufügen add-network-drive = Netzlaufwerk hinzufügen
connect = Verbinden connect = Verbinden
connect-anonymously = Anonym verbinden connect-anonymously = Anonym verbinden
connecting = Wird verbunden ... connecting = Wird verbunden...
domain = Domain domain = Domain
enter-server-address = Serveradresse eingeben enter-server-address = Serveradresse eingeben
network-drive-description = network-drive-description =
@ -218,7 +218,7 @@ compressing =
{ $items } { $items -> { $items } { $items ->
[one] Element wird [one] Element wird
*[other] Elemente werden *[other] Elemente werden
} von „{ $from }“ nach „{ $to }“ komprimiert ({ $progress }) ... } von „{ $from }“ nach „{ $to }“ komprimiert ({ $progress })...
compressed = compressed =
{ $items } { $items -> { $items } { $items ->
[one] Element wurde [one] Element wurde
@ -231,7 +231,7 @@ copying =
{ $items } { $items -> { $items } { $items ->
[one] Element wird [one] Element wird
*[other] Elemente werden *[other] Elemente werden
} von „{ $from }“ nach „{ $to }“ kopiert ({ $progress }) ... } von „{ $from }“ nach „{ $to }“ kopiert ({ $progress })...
copied = copied =
{ $items } { $items -> { $items } { $items ->
[one] Element wurde [one] Element wurde
@ -241,19 +241,19 @@ deleting =
{ $items } { $items -> { $items } { $items ->
[one] Element wird [one] Element wird
*[other] Elemente werden *[other] Elemente werden
} aus dem { trash } gelöscht ({ $progress }) ... } aus dem { trash } gelöscht ({ $progress })...
deleted = deleted =
{ $items } { $items -> { $items } { $items ->
[one] Element wurde [one] Element wurde
*[other] Elemente wurden *[other] Elemente wurden
} aus dem { trash } gelöscht } aus dem { trash } gelöscht
emptying-trash = { trash } wird geleert ({ $progress }) ... emptying-trash = { trash } wird geleert ({ $progress })...
emptied-trash = { trash } geleert emptied-trash = { trash } geleert
extracting = extracting =
{ $items } { $items -> { $items } { $items ->
[one] Element wird [one] Element wird
*[other] Elemente werden *[other] Elemente werden
} von „{ $from }“ nach „{ $to }“ entpackt ({ $progress }) ... } von „{ $from }“ nach „{ $to }“ entpackt ({ $progress })...
extracted = extracted =
{ $items } { $items -> { $items } { $items ->
[one] Element wurde [one] Element wurde
@ -267,7 +267,7 @@ moving =
{ $items } { $items -> { $items } { $items ->
[one] Element wird [one] Element wird
*[other] Elemente werden *[other] Elemente werden
} von „{ $from }“ nach „{ $to }“ verschoben ({ $progress }) ... } von „{ $from }“ nach „{ $to }“ verschoben ({ $progress })...
moved = moved =
{ $items } { $items -> { $items } { $items ->
[one] Element wurde [one] Element wurde
@ -289,7 +289,7 @@ restoring =
{ $items } { $items -> { $items } { $items ->
[one] Element wird [one] Element wird
*[other] Elemente werden *[other] Elemente werden
} aus dem { trash } wiederhergestellt ({ $progress }) ... } aus dem { trash } wiederhergestellt ({ $progress })...
restored = restored =
{ $items } { $items -> { $items } { $items ->
[one] Element wurde [one] Element wurde
@ -299,7 +299,7 @@ unknown-folder = unbekannter Ordner
## Öffnen mit ## Öffnen mit
menu-open-with = Öffnen mit ... menu-open-with = Öffnen mit...
default-app = { $name } (Standard) default-app = { $name } (Standard)
## Details anzeigen ## Details anzeigen
@ -311,7 +311,7 @@ item-size = Größe: { $size }
item-created = Erstellt: { $created } item-created = Erstellt: { $created }
item-modified = Geändert: { $modified } item-modified = Geändert: { $modified }
item-accessed = Zugegriffen: { $accessed } item-accessed = Zugegriffen: { $accessed }
calculating = Wird berechnet ... calculating = Wird berechnet...
## Einstellungen ## Einstellungen
@ -333,15 +333,15 @@ type-to-search-recursive = Durchsucht den aktuellen Ordner und alle Unterordner
type-to-search-enter-path = Gib den Pfad zum Verzeichnis oder zur Datei ein type-to-search-enter-path = Gib den Pfad zum Verzeichnis oder zur Datei ein
# Kontextmenü # Kontextmenü
add-to-sidebar = Zur Seitenleiste hinzufügen add-to-sidebar = Zur Seitenleiste hinzufügen
compress = Komprimieren ... compress = Komprimieren...
delete-permanently = Endgültig löschen delete-permanently = Endgültig löschen
extract-here = Entpacken extract-here = Entpacken
new-file = Neue Datei ... new-file = Neue Datei...
new-folder = Neuer Ordner ... new-folder = Neuer Ordner...
open-in-terminal = Im Terminal öffnen open-in-terminal = Im Terminal öffnen
move-to-trash = In den Papierkorb verschieben move-to-trash = In den Papierkorb verschieben
restore-from-trash = Aus dem Papierkorb wiederherstellen restore-from-trash = Aus dem Papierkorb wiederherstellen
remove-from-sidebar = Aus der Seitenleiste entfernen remove-from-sidebar = Von der Seitenleiste entfernen
sort-by-name = Nach Name sortieren sort-by-name = Nach Name sortieren
sort-by-modified = Nach Änderung sortieren sort-by-modified = Nach Änderung sortieren
sort-by-size = Nach Größe sortieren sort-by-size = Nach Größe sortieren
@ -349,9 +349,9 @@ sort-by-trashed = Nach Löschzeitpunkt sortieren
## Desktop ## Desktop
change-wallpaper = Hintergrundbild ändern ... change-wallpaper = Hintergrundbild ändern...
desktop-appearance = Desktop-Aussehen ... desktop-appearance = Desktop-Aussehen...
display-settings = Anzeigeeinstellungen ... display-settings = Anzeigeeinstellungen...
# Menü # Menü
@ -362,7 +362,7 @@ file = Datei
new-tab = Neuer Tab new-tab = Neuer Tab
new-window = Neues Fenster new-window = Neues Fenster
reload-folder = Ordner neu laden reload-folder = Ordner neu laden
rename = Umbenennen ... rename = Umbenennen...
close-tab = Tab schließen close-tab = Tab schließen
quit = Beenden quit = Beenden
@ -385,8 +385,8 @@ list-view = Listenansicht
show-hidden-files = Versteckte Dateien anzeigen show-hidden-files = Versteckte Dateien anzeigen
list-directories-first = Verzeichnisse zuerst auflisten list-directories-first = Verzeichnisse zuerst auflisten
gallery-preview = Galerie-Vorschau gallery-preview = Galerie-Vorschau
menu-settings = Einstellungen ... menu-settings = Einstellungen...
menu-about = Über COSMIC Dateien ... menu-about = Über COSMIC Dateien...
## Sortieren ## Sortieren
@ -397,7 +397,7 @@ sort-newest-first = Neueste zuerst
sort-oldest-first = Älteste zuerst sort-oldest-first = Älteste zuerst
sort-smallest-to-largest = Von klein nach groß sort-smallest-to-largest = Von klein nach groß
sort-largest-to-smallest = Von groß nach klein sort-largest-to-smallest = Von groß nach klein
repository = Repositorium repository = Repository
empty-trash-title = Papierkorb leeren? empty-trash-title = Papierkorb leeren?
copy-to-title = Kopierziel auswählen copy-to-title = Kopierziel auswählen
copy-to-button-label = Kopieren copy-to-button-label = Kopieren
@ -418,7 +418,7 @@ type-to-search-select = Wählt die erste übereinstimmende Datei oder den ersten
pasted-image = Eingefügtes Bild pasted-image = Eingefügtes Bild
pasted-text = Eingefügter Text pasted-text = Eingefügter Text
pasted-video = Eingefügtes Video pasted-video = Eingefügtes Video
copy-to = Kopieren nach ... copy-to = Kopieren nach...
show-recents = Ordner zuletzt verwendeter Elemente in der Seitenleiste show-recents = Ordner zuletzt verwendeter Elemente in der Seitenleiste
clear-recents-history = Verlauf zuletzt verwendeter Elemente leeren clear-recents-history = Verlauf zuletzt verwendeter Elemente leeren
comment = Dateimanager für den COSMIC Desktop comment = Dateimanager für den COSMIC Desktop
@ -426,7 +426,7 @@ keywords = Ordner;Manager;
move-to-button-label = Verschieben move-to-button-label = Verschieben
move-to-title = Verschiebeziel auswählen move-to-title = Verschiebeziel auswählen
remove-from-recents = Aus den zuletzt verwendeten Elementen entfernen remove-from-recents = Aus den zuletzt verwendeten Elementen entfernen
move-to = Verschieben nach ... move-to = Verschieben nach...
copy-path = Pfad kopieren copy-path = Pfad kopieren
mixed = Gemischt mixed = Gemischt
context-action = Kontextaktion context-action = Kontextaktion
@ -438,8 +438,3 @@ context-action-confirm-warning =
}. }.
run = Ausführen run = Ausführen
rename-confirm = Umbenennen rename-confirm = Umbenennen
checksum = { $kind } Prüfsumme
calculate = Berechnen
error = Fehler
sidebar-label = Bezeichnung in der Seitenleiste
search-application = Nach App-Name suchen

View file

@ -22,7 +22,7 @@ password = Κωδικός πρόσβασης
remove = Αφαίρεση remove = Αφαίρεση
create = Δημιουργία create = Δημιουργία
pause = Παύση pause = Παύση
quit = Τερματισμός quit = Έξοδος
calculating = Υπολογισμός... calculating = Υπολογισμός...
keep = Διατήρηση keep = Διατήρηση
edit = Επεξεργασία edit = Επεξεργασία
@ -328,9 +328,3 @@ context-action-confirm-warning =
*[other] στοιχεία *[other] στοιχεία
}. }.
rename-confirm = Μετονομασία rename-confirm = Μετονομασία
checksum = Άθροισμα ελέγχου { $kind }
calculate = Υπολογισμός
error = Σφάλμα
sidebar-label = Ετικέτα πλαϊνής στήλης
search-application = Αναζήτηση με όνομα εφαρμογής
change-sidebar-label = Αλλαγή ετικέτας πλαϊνής στήλης

View file

@ -1,100 +0,0 @@
save = Save
quit = Quit
name = Name
delete = Delete
cancel = Cancel
run = Run
new-window = New window
theme = Theme
appearance = Appearance
light = Light
repository = Repository
support = Support
dark = Dark
match-desktop = Match desktop
settings = Settings
replace = Replace
file = File
zoom-in = Zoom in
select-all = Select all
zoom-out = Zoom out
default-size = Default size
edit = Edit
copy = Copy
paste = Paste
view = View
undo = Undo
cut = Cut
icon-size-and-spacing = Icon size and spacing
cosmic-files = COSMIC Files
trash = Rubbish
empty-folder-hidden = Empty folder (has hidden items)
no-results = No results found
empty-folder = Empty folder
menu-settings = Settings...
recents = Recents
mounted-drives = Mounted drives
filesystem = Filesystem
home = Home
desktop-view-options = Desktop view options...
show-on-desktop = Show on Desktop
trash-folder-icon = Bin folder icon
desktop-folder-content = Desktop folder content
networks = Networks
today = Today
notification-in-progress = File operations are in progress
comment = File manager for the COSMIC desktop
keywords = Folder;Manager;
size = Size
dismiss = Dismiss message
trashed-on = Binned
create-archive = Create archive
pause = Pause
resume = Resume
grid-spacing = Grid spacing
details = Details
operations-running-finished =
{ $running } { $running ->
[one] operation
*[other] operations
} running ({ $percent }%), { $finished } finished...
icon-size = Icon size
modified = Modified
operations-running =
{ $running } { $running ->
[one] operation
*[other] operations
} running ({ $percent }%)...
copy-to-title = Select copy destination
open-file = Open file
open-multiple-files = Open multiple files
create-new-folder = Create new folder
create = Create
other-apps = Other applications
open-multiple-folders = Open multiple folders
extract-to = Extract To...
create-new-file = Create new file
open-in-new-window = Open in new window
folder-name = Folder name
browse-store = Browse { $store }
open-item-location = Open item location
mount-error = Unable to access drive
open = Open
extract-to-title = Extract to folder
open-with-title = How do you want to open "{ $name }"?
extract-password-required = Password required
open-folder = Open folder
open-in-new-tab = Open in new tab
file-name = File name
save-file = Save file
file-already-exists = A file with that name already exists
name-hidden = Names starting with "." will be hidden
folder-already-exists = A folder with that name already exists
empty-trash = Empty bin
empty-trash-title = Empty bin?
name-no-slashes = Name cannot contain slashes
name-invalid = Name cannot be "{ $filename }"
copy-to-button-label = Copy
move-to-title = Select move destination
move-to-button-label = Move
context-action = Context action

View file

@ -119,9 +119,6 @@ rename-file = Rename file
rename-folder = Rename folder rename-folder = Rename folder
rename-confirm = Rename rename-confirm = Rename
## Change Sidebar Label Dialog
sidebar-label = Sidebar label
## Replace Dialog ## Replace Dialog
replace = Replace replace = Replace
replace-title = "{$filename}" already exists in this location replace-title = "{$filename}" already exists in this location
@ -302,7 +299,6 @@ unknown-folder = unknown folder
## Open with ## Open with
menu-open-with = Open with... menu-open-with = Open with...
default-app = {$name} (default) default-app = {$name} (default)
search-application = Search by app name
## Show details ## Show details
show-details = Show details show-details = Show details
@ -351,7 +347,6 @@ move-to = Move to...
move-to-trash = Move to trash move-to-trash = Move to trash
restore-from-trash = Restore from trash restore-from-trash = Restore from trash
remove-from-sidebar = Remove from sidebar remove-from-sidebar = Remove from sidebar
change-sidebar-label = Change sidebar label
sort-by-name = Sort by name sort-by-name = Sort by name
sort-by-modified = Sort by modified sort-by-modified = Sort by modified
sort-by-size = Sort by size sort-by-size = Sort by size

View file

@ -345,4 +345,3 @@ sort-newest-first = جدیدترین در ابتدا
sort-oldest-first = قدیمی‌ترین در ابتدا sort-oldest-first = قدیمی‌ترین در ابتدا
sort-smallest-to-largest = کوچک‌ترین به بزرگ‌ترین sort-smallest-to-largest = کوچک‌ترین به بزرگ‌ترین
sort-largest-to-smallest = بزرگ‌ترین به کوچک‌ترین sort-largest-to-smallest = بزرگ‌ترین به کوچک‌ترین
run = اجرا

View file

@ -225,7 +225,7 @@ settings = Asetukset
appearance = Ulkoasu appearance = Ulkoasu
theme = Teema theme = Teema
match-desktop = Sama kuin työpöytä match-desktop = Sovita työpöytään
dark = Tumma dark = Tumma
light = Vaalea light = Vaalea
@ -415,9 +415,3 @@ context-action-confirm-warning =
}. }.
run = Suorita run = Suorita
rename-confirm = Nimeä uudelleen rename-confirm = Nimeä uudelleen
checksum = { $kind }-tarkistussumma
calculate = Laske
error = Virhe
search-application = Hae sovelluksen nimellä
sidebar-label = Sivupalkin nimike
change-sidebar-label = Muuta sivupalkin nimikettä

View file

@ -80,14 +80,14 @@ cancel = Annuler
create = Créer create = Créer
open = Ouvrir open = Ouvrir
open-file = Ouvrir le fichier open-file = Ouvrir le fichier
open-folder = Ouvrir le dossier open-folder = Ouvrir dossier
open-in-new-tab = Ouvrir dans un nouvel onglet open-in-new-tab = Ouvrir dans un nouvel onglet
open-in-new-window = Ouvrir dans une nouvelle fenêtre open-in-new-window = Ouvrir dans une nouvelle fenêtre
open-item-location = Ouvrir l'emplacement de l'élément open-item-location = Ouvrir l'emplacement de l'élément
open-multiple-files = Ouvrir plusieurs fichiers open-multiple-files = Ouvrir plusieurs fichiers
open-multiple-folders = Ouvrir plusieurs dossiers open-multiple-folders = Ouvrir plusieurs dossiers
save = Enregistrer save = Enregistrer
save-file = Enregistrer le fichier save-file = Enregistrer fichier
## Open With Dialog ## Open With Dialog
@ -189,7 +189,7 @@ keep = Conserver
## Add Network Drive ## Add Network Drive
add-network-drive = Ajouter un lecteur réseau add-network-drive = Ajouter un lecteur réseau
connect = Se connecter connect = Connecter
connect-anonymously = Se connecter anonymement connect-anonymously = Se connecter anonymement
connecting = Connexion... connecting = Connexion...
domain = Domaine domain = Domaine
@ -206,8 +206,8 @@ network-drive-schemes =
File Transfer Protocol,ftp:// or ftps:// File Transfer Protocol,ftp:// or ftps://
Network File System,nfs:// Network File System,nfs://
Server Message Block,smb:// Server Message Block,smb://
SSH File Transfer Protocol,sftp:// ou ssh:// SSH File Transfer Protocol,sftp:// or ssh://
WebDAV,dav:// ou davs:// WebDav,dav:// or davs://
network-drive-error = Impossible d'accéder au lecteur réseau network-drive-error = Impossible d'accéder au lecteur réseau
password = Mot de passe password = Mot de passe
remember-password = Se souvenir du mot de passe remember-password = Se souvenir du mot de passe
@ -222,7 +222,7 @@ history = Historique
no-history = Aucun élément dans l'historique. no-history = Aucun élément dans l'historique.
pending = En attente pending = En attente
progress = { $percent }% progress = { $percent }%
progress-cancelled = { $percent }%, annulé progress-cancelled = { $percent }%, annulation
progress-paused = { $percent }%, en pause progress-paused = { $percent }%, en pause
failed = Échoué failed = Échoué
complete = Terminé complete = Terminé
@ -259,7 +259,7 @@ deleted =
[one] élément supprimé [one] élément supprimé
*[other] éléments supprimés *[other] éléments supprimés
} depuis { trash } } depuis { trash }
emptying-trash = Nettoyage en cours de { trash } ({ $progress })... emptying-trash = { trash } en cours de nettoyage ({ $progress })...
emptied-trash = { trash } vidée emptied-trash = { trash } vidée
extracting = extracting =
Extraction de { $items } { $items -> Extraction de { $items } { $items ->
@ -271,7 +271,7 @@ extracted =
[one] élément extrait [one] élément extrait
*[other] éléments extraits *[other] éléments extraits
} depuis "{ $from }" vers "{ $to }" } depuis "{ $from }" vers "{ $to }"
setting-executable-and-launching = Définition de "{ $name }" comme exécutable et lancement en cours setting-executable-and-launching = Paramétrage de "{ $name }" comme exécutable et prêt à être lancé
set-executable-and-launched = Défini "{ $name }" comme exécutable et lancé set-executable-and-launched = Défini "{ $name }" comme exécutable et lancé
moving = moving =
Déplacement de { $items } { $items -> Déplacement de { $items } { $items ->
@ -282,7 +282,7 @@ moved =
{ $items } { $items -> { $items } { $items ->
[one] élément déplacé [one] élément déplacé
*[other] éléments déplacés *[other] éléments déplacés
} de "{ $from }" vers "{ $to }" } de "{ $from }" à "{ $to }"
permanently-deleting = permanently-deleting =
Suppression définitive de { $items } { $items -> Suppression définitive de { $items } { $items ->
[one] élément [one] élément
@ -310,7 +310,7 @@ unknown-folder = Dossier inconnu
## Open with ## Open with
menu-open-with = Ouvrir avec... menu-open-with = Ouvrir avec...
default-app = { $name } (par défaut) default-app = { $name } (défaut)
## Show details ## Show details
@ -352,7 +352,7 @@ open-in-terminal = Ouvrir dans le terminal
edit-desktop-entry = Modifier le fichier .desktop edit-desktop-entry = Modifier le fichier .desktop
move-to-trash = Déplacer vers la corbeille move-to-trash = Déplacer vers la corbeille
restore-from-trash = Restaurer depuis la corbeille restore-from-trash = Restaurer depuis la corbeille
remove-from-sidebar = Retirer de la barre latérale remove-from-sidebar = Supprimer de la barre latérale
sort-by-name = Trier par nom sort-by-name = Trier par nom
sort-by-modified = Trier par date de modification sort-by-modified = Trier par date de modification
sort-by-size = Trier par taille sort-by-size = Trier par taille
@ -415,11 +415,11 @@ removing-from-recents =
*[other] éléments *[other] éléments
} de { recents } } de { recents }
other-apps = Autres applications other-apps = Autres applications
set-permissions = Permissions définies pour "{ $name }" à { $mode } set-permissions = Définir les permissions pour "{ $name }" à { $mode }
repository = Dépôt repository = Dépôt
support = Support support = Support
eject = Éjecter eject = Éjecter
remove-from-recents = Retirer des récents remove-from-recents = Supprimer des récents
empty-trash-title = Vider la corbeille ? empty-trash-title = Vider la corbeille ?
setting-permissions = Définition des permissions pour "{ $name }" à { $mode } setting-permissions = Définition des permissions pour "{ $name }" à { $mode }
removed-from-recents = removed-from-recents =
@ -428,9 +428,9 @@ removed-from-recents =
*[other] éléments supprimés *[other] éléments supprimés
} de { recents } } de { recents }
progress-failed = { $percent }%, échec progress-failed = { $percent }%, échec
type-to-search-select = Sélectionne le premier fichier ou dossier correspondant type-to-search-select = Sélectionne le premier fichier ou dossier qui convient
pasted-image = Image collée pasted-image = Image collée
pasted-text = Texte collé pasted-text = Text collé
pasted-video = Vidéo collée pasted-video = Vidéo collée
copy-to-title = Sélectionner la destination de la copie copy-to-title = Sélectionner la destination de la copie
copy-to-button-label = Copier copy-to-button-label = Copier
@ -453,9 +453,3 @@ context-action-confirm-warning =
run = Exécuter run = Exécuter
context-action = Action contextuelle context-action = Action contextuelle
rename-confirm = Renommer rename-confirm = Renommer
calculate = Calculer
error = Erreur
checksum = Somme de contrôle { $kind }
sidebar-label = Libellé de la barre latérale
search-application = Chercher par nom d'appli
change-sidebar-label = Changer le libellé de la barre latérale

View file

@ -307,7 +307,7 @@ single-click = Cliceáil amháin le hoscailt
### Appearance ### Appearance
appearance = Dealramh appearance = Cuma
theme = Téama theme = Téama
match-desktop = Meaitseáil deasc match-desktop = Meaitseáil deasc
dark = Dorcha dark = Dorcha
@ -443,6 +443,3 @@ context-action-confirm-warning =
}. }.
run = Rith run = Rith
rename-confirm = Athainmnigh rename-confirm = Athainmnigh
checksum = { $kind } suim sheiceála
calculate = Ríomh
error = Earráid

View file

@ -1,333 +0,0 @@
name = Nome
save = Gardar
cancel = Cancelar
run = Executar
delete = Eliminar
repository = Repositorio
support = Soporte
settings = Axustes
appearance = Apariencia
theme = Tema
match-desktop = Axustar ao escritorio
dark = Escuro
light = Claro
replace = Remplazar
file = Ficheiro
new-window = Nova xanela
quit = Pechar
edit = Editar
undo = Desfacer
cut = Cortar
copy = Copiar
paste = Pegar
select-all = Seleccionar todo
view = Ver
zoom-in = Aumentar Zoom
default-size = Tamaño por defecto
zoom-out = Afastar Zoom
menu-settings = Axustes...
cosmic-files = Ficheiros COSMIC
comment = Xestor de ficheiros para o escritorio COSMIC
keywords = Cartafol;Xestor;
empty-folder = Cartafol baleiro
empty-folder-hidden = Cartafol baleiro (contén elementos agochados)
no-results = Non se atoparon resultados
filesystem = Sistema de ficheiros
home = Inicio
networks = Redes
notification-in-progress = Operacións de ficheiros en progreso
trash = Lixo
recents = Recentes
today = Hoxe
desktop-view-options = Axustes da vista do escritorio…
show-on-desktop = Amosar no Escritorio
desktop-folder-content = Contido do cartafol Escritorio
mounted-drives = Unidades montadas
trash-folder-icon = Icona do cartafol do Lixo
icon-size-and-spacing = Tamaño e espazado das iconas
icon-size = Tamaño de icona
grid-spacing = Espazado da grella
modified = Modificado
trashed-on = No lixo
size = Tamaño
details = Detalles
dismiss = Descartar a mensaxe
operations-running =
{ $running } { $executandose ->
[unha] operacion
*[outra] operacions
} executandose ({ $percent }%)...
operations-running-finished =
{ $running } { $executandose ->
[unha] operacion
*[outra] operacions
} executandose ({ $percent }%), { $finished } rematadas...
pause = Pausa
resume = Retomar
create-archive = Crear arquivo comprimido
copy-to-title = Seleccionar o destino da copia
copy-to-button-label = Copiar
extract-password-required = Contrasinal necesario
extract-to = Extraer en…
extract-to-title = Extraer a cartafol
empty-trash = Baleirar Lixo
empty-trash-title = Baleirar o lixo?
empty-trash-warning = Os elementos do cartafol do lixo eliminaranse de forma permanente
mount-error = Non é posible acceder a unidade
move-to-title = Seleccionar o destino do movemento
move-to-button-label = Mover
create-new-file = Crear un novo ficheiro
create-new-folder = Crear novo cartafol
file-name = Nome do ficheiro
folder-name = Nome do cartafol
file-already-exists = Existe un ficheiro co mesmo nome
folder-already-exists = Existe un cartafol co mesmo nome
name-hidden = Os nomes que comecen por "." agocharanse
name-invalid = O nome non pode ser "{ $filename }"
name-no-slashes = O nome non pode conter barras
create = Crear
open = Abrir
open-file = Abrir ficheiro
open-folder = Abrir cartafol
open-in-new-tab = Abrir nunha nova lapela
open-in-new-window = Abrir nunha nova xanela
open-item-location = Abrir ubicación do ítem
open-multiple-files = Abrir multiples ficheiros
open-multiple-folders = Abrir multiples cartafoles
save-file = Gardar ficheiro
open-with-title = Como queres abrir «{ $name }»?
browse-store = Atopar { $store }
other-apps = Outras aplicacións
related-apps = Aplicacións relacionadas
context-action = Acción contextual
context-action-confirm-title = Executar "{ $name }"?
context-action-confirm-warning =
Esto vaise executar en { $items } { $items ->
[un] item
*[outro] items
}.
selected-items = Os { $items } elementos seleccionados
permanently-delete-question = Eliminar permanentemente?
permanently-delete-warning = { $target } será eliminado permanentemente. Esta acción non se poderá desfacer.
rename-file = Renomear ficheiro
rename-folder = Renomear cartafol
rename-confirm = Renomear
replace-title = "{ $filename }" xa existe nesta ubicación
replace-warning = Queres substituílo polo que estás a gardar? Ao substituílo sobrescribirase o seu contido.
replace-warning-operation = Queres substituílo? Ao substituílo sobrescribirase o seu contido.
original-file = Ficheiro orixinal
replace-with = Remplazar con
apply-to-all = Aplicar a todo
keep-both = Manter ambos
skip = Saltar
set-executable-and-launch = Marcar como executable e lanzalo
set-executable-and-launch-description = Queres marcar «{ $name }» como executábel e lanzalo?
set-and-launch = Marcar e executar
open-with = Abrir con
owner = Propietario
group = Grupo
other = Outro
mixed = Mesturar
none = Ningún
execute-only = Só execución
write-only = Só escritura
write-execute = Escritura e execución
read-only = Só lectura
read-execute = Lectura e execución
read-write = Lectura e escritura
read-write-execute = Lectura, escritura e execución
favorite-path-error = Erro abrindo o directorio
favorite-path-error-description =
Non foi posíbel abrir «{ $path }».
É posíbel que «{ $path }» non exista ou que non teñas permisos para abrilo.
Queres retiralo da barra lateral?
remove = Eliminar
keep = Manter
add-network-drive = Engadir unidade de rede
connect = Conectar
connect-anonymously = Conectar de xeito anonimo
connecting = Conectando...
domain = Dominio
enter-server-address = Introducir a dirección do servidor
network-drive-description =
Os enderezos dos servidores inclúen un prefixo de protocolo e un enderezo.
Exemplos: ssh://192.168.0.1, ftp://[2001:db8::1]
network-drive-schemes =
Protocolos dispoñíbeis,Prefixo
AppleTalk,afp://
Protocolo de transferencia de ficheiros,ftp:// ou ftps://
Sistema de ficheiros en rede,nfs://
Server Message Block,smb://
Protocolo de transferencia de ficheiros por SSH,sftp:// ou ssh://
WebDAV,dav:// ou davs://
network-drive-error = Non foi posible acceder a unidade de rede
password = Contrasinal
remember-password = Recordar o contrasinal
try-again = Voltar a intentar
username = Nome de usuario
cancelled = Cancelado
edit-history = Editar historial
history = Historial
no-history = Non hai elementos no historial.
pending = Pendente
progress = { $percent }%
progress-cancelled = { $percent }%, cancelado
progress-failed = { $percent }%, fallou
progress-paused = { $percent }%, pausado
failed = Fallou
complete = Completado
compressing =
Comprimindo { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }» ({ $progress })...
compressed =
Comprimidos { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }»
copy_noun = Copiar
pasted-image = Imaxe pegada
pasted-text = Texto Pegado
pasted-video = Video pegado
creating = Creando "{ $name }" en "{ $parent }"
created = Creado "{ $name }" en "{ $parent }"
copying =
Copiando { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }» ({ $progress })...
copied =
Copiados { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }»
deleted =
Eliminando { $items } { $items ->
[one] elemento
*[other] elementos
} do lixo ({ $progress })
emptying-trash = Baleirando { trash } ({ $progress })...
emptied-trash = Baleirado o { trash }
extracting =
Extraendo { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }» ({ $progress })...
extracted =
Extraídos { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }»
setting-executable-and-launching = Marcando «{ $name }» como executábel e executándoo
set-executable-and-launched = «{ $name }» marcouse como executábel e executouse
setting-permissions = Establecendo os permisos de «{ $name }» en { $mode }
set-permissions = Establecéronse os permisos de «{ $name }» en { $mode }
moving =
Movendo { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }» ({ $progress })...
moved =
Movidos { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }»
permanently-deleting =
Eliminando permanentemente { $items } { $items ->
[one] elemento
*[other] elementos
}
permanently-deleted =
Eliminados permanentemente { $items } { $items ->
[one] elemento
*[other] elementos
}
removing-from-recents =
Retirando { $items } { $items ->
[one] elemento
*[other] elementos
} dos { recents }
removed-from-recents =
Retirado { $items } { $items ->
[one] elemento
*[other] elementos
} de { recents }
renaming = Renomeando "{ $from }" a "{ $to }"
renamed = Renomeado "{ $from }" a "{ $to }"
restoring =
Restaurando { $items } { $items ->
[one] elemento
*[other] elementos
} de { trash } ({ $progress })...
restored =
Restaurado { $items } { $items ->
[one] elemento
*[other] elementos
} de { trash }
unknown-folder = cartafol descoñecido
menu-open-with = Abrir con...
default-app = { $name } (por defecto)
show-details = Amosar detalles
type = Tipo: { $mime }
items = Elementos: { $items }
item-size = Tamaño: { $size }
item-created = Creado: { $created }
item-modified = Modificado: { $modified }
item-accessed = Accedido: { $accessed }
calculating = Calculando...
checksum = { $kind } checksum
calculate = Calculado
error = Erro
single-click = Clic único para abrir
show-recents = Cartafol de recentes no panel lateral
type-to-search = Escribe para atopar
type-to-search-recursive = Busca no cartafol actual e en todos os subcartafoles
type-to-search-enter-path = Introduce a ruta do cartafol ou ficheiro
type-to-search-select = Selecciona o primeiro ficheiro ou cartafol coincidente
add-to-sidebar = Engadir o panel lateral
clear-recents-history = Limpar historial de recentes
compress = Comprimir...
copy-to = Copiar a...
delete-permanently = Eliminar permanentemente
eject = Expulsar
extract-here = Extraer
new-file = Novo ficheiro...
new-folder = Novo cartafol...
open-in-terminal = Abrir no terminal
move-to = Mover a...
move-to-trash = Mover ao lixo
restore-from-trash = Recuperar do lixo
remove-from-sidebar = Eliminar do panel lateral
sort-by-name = Ordenar por nome
sort-by-modified = Ordenar por modificación
sort-by-size = Ordenar por tamaño
sort-by-trashed = Ordenar pola data de eliminación
remove-from-recents = Eliminar de recentes
change-wallpaper = Cambiar fondo de pantalla...
desktop-appearance = Apariencia do Escritorio...
display-settings = Axustes de pantalla...
new-tab = Nova lapela
reload-folder = Recargar cartafol
rename = Renomear...
close-tab = Pechar lapela
copy-path = Copiar ruta
grid-view = Vista de grella
list-view = Vista de lista
show-hidden-files = Amosar ficheiros ocultos
list-directories-first = Listar primeiro os directorios
gallery-preview = Vista previa da galería
menu-about = Sobre Ficheiros COSMIC...
sort = Ordenar
sort-a-z = A-Z
sort-z-a = Z-A
sort-newest-first = Novos primeiro
sort-oldest-first = Antigos primeiro
sort-smallest-to-largest = Do máis pequeno ao máis grande
sort-largest-to-smallest = Do máis grande ao máis pequeno
deleting =
Eliminando { $items } { $items ->
[one] elemento
*[other] elementos
} da { trash } ({ $progress })...

View file

@ -5,7 +5,7 @@ no-results = कोई परिणाम नहीं
filesystem = फाइल सिस्टम filesystem = फाइल सिस्टम
home = होम home = होम
networks = नेटवर्क्स networks = नेटवर्क्स
notification-in-progress = फ़ाइल संचालन प्रगति पर हैं notification-in-progress = फाइल संचालन प्रगति पर है।
trash = कचरा trash = कचरा
recents = हाल के recents = हाल के
undo = पूर्ववत करें undo = पूर्ववत करें
@ -33,8 +33,8 @@ create-archive = संग्रह बनाएँ
## Empty Trash Dialog ## Empty Trash Dialog
empty-trash = रद्दी साफ़ करें empty-trash = कचरा खाली करें
empty-trash-warning = रद्दी फ़ोल्डर में मौजूद आइटम स्थायी रूप से हटा दिए जाएंगे empty-trash-warning = क्या आप वाकई कचरे में सभी आइटमों को स्थायी रूप से हटाना चाहते हैं?
## New File/Folder Dialog ## New File/Folder Dialog
@ -42,11 +42,11 @@ create-new-file = नई फाइल बनाएँ
create-new-folder = नया फ़ोल्डर बनाएँ create-new-folder = नया फ़ोल्डर बनाएँ
file-name = फाइल का नाम file-name = फाइल का नाम
folder-name = फ़ोल्डर का नाम folder-name = फ़ोल्डर का नाम
file-already-exists = उस नाम की फ़ाइल पहले से मौजूद है file-already-exists = इसी नाम की फाइल पहले से मौजूद है।
folder-already-exists = उस नाम का फ़ोल्डर पहले से मौजूद है folder-already-exists = इसी नाम का फ़ोल्डर पहले से मौजूद है।
name-hidden = "." से शुरू होने वाले नाम छिपे होंगे name-hidden = "." से शुरू होने वाले नाम छिपे रहेंगे।
name-invalid = नाम "{ $filename }" नहीं हो सकता name-invalid = नाम "{ $filename }" मान्य नहीं है।
name-no-slashes = नाम में स्लैश नहीं हो सकते name-no-slashes = नाम में स्लैश का उपयोग नहीं किया जा सकता है।
## Open/Save Dialog ## Open/Save Dialog
@ -75,8 +75,8 @@ rename-folder = फ़ोल्डर का नाम बदलें
## Replace Dialog ## Replace Dialog
replace = बदलें replace = प्रतिस्थापित करें
replace-title = { $filename } पहले से इस स्थान पर मौजूद है replace-title = { $filename } पहले से इस स्थान पर मौजूद है।
replace-warning = क्या आप इसे प्रतिस्थापित करना चाहते हैं? यदि प्रतिस्थापित किया गया, तो मौजूदा फ़ाइल को ओवरराइट किया जाएगा। replace-warning = क्या आप इसे प्रतिस्थापित करना चाहते हैं? यदि प्रतिस्थापित किया गया, तो मौजूदा फ़ाइल को ओवरराइट किया जाएगा।
replace-warning-operation = क्या आप इसे बदलना चाहते हैं? प्रतिस्थापित करने पर मौजूदा फ़ाइल ओवरराइट हो जाएगी। replace-warning-operation = क्या आप इसे बदलना चाहते हैं? प्रतिस्थापित करने पर मौजूदा फ़ाइल ओवरराइट हो जाएगी।
original-file = मूल फ़ाइल original-file = मूल फ़ाइल
@ -211,13 +211,13 @@ show-details = विवरण दिखाएँ
## Settings ## Settings
settings = सेटिंग्स settings = सेटिंग
### Appearance ### Appearance
appearance = रुप-रंग appearance = रूप
theme = प्रसंग theme = थीम
match-desktop = डेस्कटॉप से मेल खाएँ match-desktop = सिस्टम सेटिंग्स से मेल खाएँ
dark = डार्क dark = डार्क
light = लाइट light = लाइट
# Context menu # Context menu
@ -251,27 +251,27 @@ new-tab = नया टैब
new-window = नई विंडो new-window = नई विंडो
rename = नाम बदलें... rename = नाम बदलें...
close-tab = टैब बंद करें close-tab = टैब बंद करें
quit = बाहर जाएँ quit = बंद करें
## Edit ## Edit
edit = संपादन edit = संपादित करें
cut = काटें cut = कट करें
copy = कॉपी करें copy = नकल करें
paste = चिपकाएँ paste = चिपकाएँ
select-all = सभी चुनें select-all = सभी चुनें
## View ## View
zoom-in = बड़ा करें zoom-in = ज़ूम इन करें
default-size = मूल आकार default-size = डिफ़ॉल्ट आकार
zoom-out = छोटा करें zoom-out = ज़ूम आउट करें
view = दृश्य view = दृश्य
grid-view = ग्रिड दृश्य grid-view = ग्रिड दृश्य
list-view = सूची दृश्य list-view = सूची दृश्य
show-hidden-files = छिपी हुई फाइलें दिखाएँ show-hidden-files = छिपी हुई फाइलें दिखाएँ
list-directories-first = सबसे पहले डाइरेक्ट्री दिखाएँ list-directories-first = सबसे पहले डाइरेक्ट्री दिखाएँ
menu-settings = सेटिंग्स..। menu-settings = सेटिंग्स...
menu-about = कॉस्मिक फाइल्स के बारे में... menu-about = कॉस्मिक फाइल्स के बारे में...
## Sort ## Sort
@ -284,98 +284,4 @@ sort-oldest-first = पुराने से नए
sort-smallest-to-largest = छोटे से बड़े sort-smallest-to-largest = छोटे से बड़े
sort-largest-to-smallest = बड़े से छोटे sort-largest-to-smallest = बड़े से छोटे
repository = रिपॉजिटरी repository = रिपॉजिटरी
support = समर्थन support = सहायता
read-execute = पढ़ें और निष्पादित करें
deleted =
{ trash } से { $items } { $items ->
[one] आइटम मिटाया गया
*[other] आइटम मिटाए गए
}
dismiss = संदेश खारिज करें
favorite-path-error = निर्देशिका खोलने में त्रुटि
progress = { $percent }%
related-apps = संबंधित ऐप्स
removing-from-recents =
{ recents } से { $items } { $items ->
[one] आइटम हटाया जा रहा है
*[other] आइटम हटाए जा रहे हैं
}
remove = हटाएँ
read-write-execute = पढ़ें, लिखें और निष्पादित करें
other-apps = अन्य ऐप्स
pause = विराम
keep = रखें
permanently-deleting =
{ $items } { $items ->
[one] आइटम स्थायी रूप से मिटाया जा रहा है
*[other] आइटम स्थायी रूप से मिटाए जा रहे हैं
}
read-write = पढ़ें और लिखें
none = कोई नहीं
resume = फिर से शुरू करें
grid-spacing = ग्रिड स्पेसिंग
extract-to = इस रूप में निकालें..।
delete = हटाएं
read-only = केवल पढ़ने के लिए
deleting =
{ trash } से { $items } { $items ->
[one] आइटम मिटाया जा रहा है
*[other] आइटम मिटाए जा रहे हैं
} ({ $progress })..।
execute-only = केवल निष्पादित करें
details = विवरण
mount-error = ड्राइव तक पहुँचने में असमर्थ
removed-from-recents =
{ recents } से { $items } { $items ->
[one] आइटम हटाया गया
*[other] आइटम हटाए गए
}
progress-paused = { $percent }%, रुका हुआ
cancelled = रद्द किया गया
operations-running-finished =
{ $running } { $running ->
[one] ऑपरेशन
*[other] ऑपरेशन
} चल रहा है ({ $percent }%), { $finished } समाप्त..।
progress-failed = { $percent }%, विफल
extract-to-title = फ़ोल्डर में निकालें
open-with = इससे खोलें
permanently-deleted =
{ $items } { $items ->
[one] आइटम स्थायी रूप से मिटाया गया
*[other] आइटम स्थायी रूप से मिटाए गए
}
write-execute = लिखें और निष्पादित करें
extract-password-required = पासवर्ड आवश्यक है
progress-cancelled = { $percent }%, रद्द किया गया
write-only = केवल लिखने के लिए
permanently-delete-warning = { $target } को स्थाई रूप से हटा दिया जाएगा। इस कार्रवाई को पूर्ववत नहीं किया जा सकता है।
favorite-path-error-description =
"{ $path }" को खोला नहीं जा सकता
"{ $path }" मौजूद नहीं हो सकता है या आपके पास इसे खोलने की अनुमति नहीं हो सकती है
क्या आप इसे साइडबार से हटाना चाहते हैं?
empty-trash-title = रद्दी साफ़ करें?
permanently-delete-question = स्थाई रूप से हटाएं?
operations-running =
{ $running } { $running ->
[one] ऑपरेशन
*[other] ऑपरेशन
} चल रहा है ({ $percent }%)..।
copy-to-title = कॉपी गंतव्य चुनें
copy-to-button-label = कॉपी
move-to-title = मूव गंतव्य चुनें
move-to-button-label = मूव
context-action = संदर्भ क्रिया
context-action-confirm-title = "{ $name }" चलाएँ?
context-action-confirm-warning =
यह { $items } { $items ->
[one] आइटम
*[other] आइटम
} पर चलेगा।
run = चलाएँ
rename-confirm = नाम बदलें
mixed = मिश्रित
pasted-image = चिपकाई गई छवि
pasted-text = चिपकाया गया पाठ
pasted-video = चिपकाया गया वीडियो

View file

@ -326,7 +326,7 @@ default-app = { $name } (alapértelmezett)
show-details = Részletek megjelenítése show-details = Részletek megjelenítése
type = Típus: { $mime } type = Típus: { $mime }
items = Elemek száma: { $items } items = Elemek: { $items }
item-size = Méret: { $size } item-size = Méret: { $size }
item-created = Létrehozva: { $created } item-created = Létrehozva: { $created }
item-modified = Módosítva: { $modified } item-modified = Módosítva: { $modified }
@ -446,9 +446,3 @@ context-action-confirm-warning =
} fog lefutni. } fog lefutni.
run = Futtatás run = Futtatás
rename-confirm = Átnevezés rename-confirm = Átnevezés
checksum = { $kind }-ellenőrzőösszeg
calculate = Számítás
error = Hiba
sidebar-label = Oldalsávcímke
search-application = Keresés alkalmazásnév alapján
change-sidebar-label = Oldalsávcímke módosítása

View file

@ -249,7 +249,7 @@ settings = Pengaturan
single-click = Klik sekali untuk membuka single-click = Klik sekali untuk membuka
appearance = Tampilan appearance = Tampilan
theme = Tema theme = Tema
match-desktop = Cocok desktop match-desktop = Cocokkan desktop
dark = Gelap dark = Gelap
light = Terang light = Terang
type-to-search = Ketik untuk mencari type-to-search = Ketik untuk mencari
@ -328,9 +328,3 @@ context-action-confirm-warning =
*[other] item *[other] item
}. }.
rename-confirm = Ganti nama rename-confirm = Ganti nama
checksum = { $kind } checksum
calculate = Hitung
error = Galat
sidebar-label = Label bilah sisi
search-application = Cari berdasarkan nama aplikasi
change-sidebar-label = Ubah label bilah sisi

View file

@ -1,5 +1,5 @@
cosmic-files = COSMIC Files cosmic-files = COSMIC Files
comment = File manager per il desktop COSMIC comment = File manager di COSMIC
keywords = File;Archivi;Cartelle;Explorer; keywords = File;Archivi;Cartelle;Explorer;
empty-folder = Cartella vuota empty-folder = Cartella vuota
empty-folder-hidden = Cartella vuota (con elementi nascosti) empty-folder-hidden = Cartella vuota (con elementi nascosti)
@ -7,7 +7,7 @@ no-results = Nessun risultato trovato
filesystem = Filesystem filesystem = Filesystem
home = Home home = Home
networks = Reti networks = Reti
notification-in-progress = Operazioni sui file in corso notification-in-progress = Operazioni sui file in corso.
trash = Cestino trash = Cestino
recents = Recenti recents = Recenti
undo = Annulla undo = Annulla
@ -322,7 +322,7 @@ single-click = Click singolo per aprire
appearance = Aspetto appearance = Aspetto
theme = Tema theme = Tema
match-desktop = Abbina al desktop match-desktop = Sistema
dark = Scuro dark = Scuro
light = Chiaro light = Chiaro
@ -422,4 +422,3 @@ removed-from-recents =
[one] elemento [one] elemento
*[other] elementi *[other] elementi
} da { recents } } da { recents }
run = Esegui

View file

@ -20,7 +20,7 @@ name = Isem
modified = Ittusnifel modified = Ittusnifel
trashed-on = Yettwakkes ɣer tqecwalt n yiḍumman trashed-on = Yettwakkes ɣer tqecwalt n yiḍumman
size = Tiddi size = Tiddi
details = Ifatusen details = Talqayt
pause = Serǧu pause = Serǧu
resume = Kemmel resume = Kemmel
create-archive = Snulfu-d aɣbaṛ create-archive = Snulfu-d aɣbaṛ
@ -40,7 +40,7 @@ file-already-exists = Afaylu s yisem-agi yella yakan
folder-already-exists = Akaram s yisem-agi yella yakan folder-already-exists = Akaram s yisem-agi yella yakan
name-hidden = Ismawen ibeddun s "." ad ttwaffren name-hidden = Ismawen ibeddun s "." ad ttwaffren
name-invalid = Isem ur yezmir ara ad yili "{ $filename }" name-invalid = Isem ur yezmir ara ad yili "{ $filename }"
cancel = Semmet cancel = Sefsex
create = Snulfu-d create = Snulfu-d
open = Ldi open = Ldi
open-file = Ldi afaylu open-file = Ldi afaylu
@ -72,7 +72,7 @@ comment = Amsefrak n yifuyla i tnarit COSMIC
keywords = Akaram;Amsefrak; keywords = Akaram;Amsefrak;
delete = kkes delete = kkes
replace = Semselsi replace = Semselsi
support = Tallelt support = Tallalt
settings = Iɣewwaṛen settings = Iɣewwaṛen
appearance = Timeẓri appearance = Timeẓri
theme = Asentel theme = Asentel
@ -86,10 +86,10 @@ zoom-out = Asemẓi
view = Wali view = Wali
menu-settings = Iɣewwaṛen… menu-settings = Iɣewwaṛen…
save = Sekles save = Sekles
match-desktop = Semṣada akked tnarit match-desktop = Amṣada d tnarit
file = Afaylu file = Afaylu
new-window = Asfaylu amaynut new-window = Asfaylu amaynut
quit = Ffeɣ quit = Tuffɣa
edit = Ẓreg edit = Ẓreg
cut = Gzem cut = Gzem
copy = Nɣel copy = Nɣel

View file

@ -22,7 +22,7 @@ name = Аты
modified = Өзгертілген modified = Өзгертілген
trashed-on = Қоқыс шелегіне тасталған trashed-on = Қоқыс шелегіне тасталған
size = Өлшемі size = Өлшемі
details = Толық ақпарат details = Ақпараты
dismiss = Хабарламаны елемеу dismiss = Хабарламаны елемеу
operations-running = operations-running =
{ $running } { $running -> { $running } { $running ->
@ -246,10 +246,10 @@ item-accessed = Қол жеткізілген: { $accessed }
calculating = Есептеу... calculating = Есептеу...
settings = Баптаулар settings = Баптаулар
single-click = Ашу үшін бір рет шерту single-click = Ашу үшін бір рет шерту
appearance = Сыртқы көрініс appearance = Сыртқы түрі
theme = Тема theme = Тақырып
match-desktop = Жұмыс үстеліне сәйкес болу match-desktop = Жұмыс үстеліне сәйкес келу
dark = Қою dark = Күңгірт
light = Ашық light = Ашық
type-to-search = Іздеу үшін теру type-to-search = Іздеу үшін теру
type-to-search-recursive = Ағымдағы бума мен барлық ішкі бумаларды іздейді type-to-search-recursive = Ағымдағы бума мен барлық ішкі бумаларды іздейді
@ -328,6 +328,3 @@ context-action-confirm-warning =
}. }.
run = Орындау run = Орындау
rename-confirm = Атын өзгерту rename-confirm = Атын өзгерту
checksum = { $kind } бақылау сомасы
calculate = Есептеу
error = Қате

View file

@ -30,10 +30,10 @@ new-window = Çarçoveya nû
remove = Rake remove = Rake
zoom-out = Dûr bike zoom-out = Dûr bike
compressing = compressing =
{ $items } { $items -> { $items } tê guvaştin { $items ->
[one] hêman [one] hêman
*[other] hêman *[other] hêman
} tê guvaştin ji "{ $from }" bo "{ $to }" ({ $progress })... } ji "{ $from }" bo "{ $to }" ({ $progress })...
move-to-trash = Bilivîne bo jêbirdankê move-to-trash = Bilivîne bo jêbirdankê
menu-about = Derbarê Pelên COSMIC... menu-about = Derbarê Pelên COSMIC...
file-already-exists = Pelek bi heman navî jixwe heye file-already-exists = Pelek bi heman navî jixwe heye
@ -42,10 +42,10 @@ open-multiple-files = Gelek pelan veke
default-size = Mezinahiya berdest default-size = Mezinahiya berdest
name-hidden = Navên ku bi "." tê destpêkirin wê werin veşartin name-hidden = Navên ku bi "." tê destpêkirin wê werin veşartin
extracted = extracted =
{ $items } { $items -> { $items } hate derxistin { $items ->
[one] hêman [one] hêman
*[other] hêman *[other] hêman
} hate derxistin ji "{ $from }" bo "{ $to }" } ji "{ $from }" bo "{ $to }"
create-new-folder = Peldanka nû biafrîne create-new-folder = Peldanka nû biafrîne
folder-already-exists = Peldankek bi heman navî jixwe heye folder-already-exists = Peldankek bi heman navî jixwe heye
create-archive = Erşîvê biafrîne create-archive = Erşîvê biafrîne
@ -79,10 +79,10 @@ none = Ne yek
no-results = Tu encam nehatin dîtin no-results = Tu encam nehatin dîtin
theme = Rûkar theme = Rûkar
compressed = compressed =
{ $items } { $items -> { $items } hate guvaştin{ $items ->
[one] hêman [one] hêman
*[other] hêman *[other] hêman
} hate guvaştin ji "{ $from }" bo "{ $to }" } ji "{ $from }" bo "{ $to }"
appearance = Xuyang appearance = Xuyang
rename-folder = Navê peldankê biguherîne rename-folder = Navê peldankê biguherîne
new-file = Pela nû... new-file = Pela nû...
@ -95,20 +95,20 @@ light = Ronî
extract-to = Derxîne bo... extract-to = Derxîne bo...
add-network-drive = Ajokarê torê tevlî bike add-network-drive = Ajokarê torê tevlî bike
copying = copying =
{ $items } { $items -> { $items } tê jêgirtin{ $items ->
[one] hêman [one] hêman
*[other] hêman *[other] hêman
} tê jêgirtin ji "{ $from }" bo "{ $to }" ({ $progress })... } ji "{ $from }" bo "{ $to }" ({ $progress })...
create-new-file = Pelê nû biafrîne create-new-file = Pelê nû biafrîne
sort-by-trashed = Li gorî demê rêz bike sort-by-trashed = Li gorî demê rêz bike
support = Piştgirî support = Piştgirî
try-again = Dîsa hewl bide try-again = Dîsa hewl bide
eject = Biavêje eject = Biavêje
copied = copied =
{ $items } { $items -> { $items } hate jêgirtin { $items ->
[one] hêman [one] hêman
*[other] hêman *[other] hêman
} hate jêgirtin ji "{ $from }" bo "{ $to }" } ji "{ $from }" bo "{ $to }"
open-in-new-window = Di çarçoveyeke nû de veke open-in-new-window = Di çarçoveyeke nû de veke
empty-folder = Peldanka vala empty-folder = Peldanka vala
empty-trash = Jêbirdankê vala bike empty-trash = Jêbirdankê vala bike
@ -117,14 +117,14 @@ list-directories-first = Pêşî rêgehan lîste bike
folder-name = Navê peldankê folder-name = Navê peldankê
browse-store = { $store } bigere browse-store = { $store } bigere
remove-from-recents = Ji dawiyê rake remove-from-recents = Ji dawiyê rake
paste = Pê ve bike paste = Pêve bike
empty-trash-title = Jêbirdankê vala bike? empty-trash-title = Jêbirdankê vala bike?
menu-settings = Sazkarî... menu-settings = Sazkarî...
moving = moving =
{ $items } { $items -> { $items } tê livandin{ $items ->
[one] hêman [one] hêman
*[other] hêman *[other] hêman
} tê livandin ji "{ $from }" bo "{ $to }" ({ $progress })... } ji "{ $from }" bo "{ $to }" ({ $progress })...
change-wallpaper = Wêneyê dîwêr biguhêrîne... change-wallpaper = Wêneyê dîwêr biguhêrîne...
network-drive-description = network-drive-description =
Di nav navnîşanên rajekarê de pêşgira protokolekê û navnîşanek heye. Di nav navnîşanên rajekarê de pêşgira protokolekê û navnîşanek heye.
@ -158,8 +158,8 @@ progress-paused = { $percent }%, hate rawestandin
desktop-view-options = Vebijêrkên dîtina sermaseyê... desktop-view-options = Vebijêrkên dîtina sermaseyê...
show-on-desktop = Li ser sermaseyê nîşan bide show-on-desktop = Li ser sermaseyê nîşan bide
cancelled = Têkbirî cancelled = Têkbirî
new-folder = Peldanka nû... new-folder = Peldankan nû...
match-desktop = Bi sermaseyê re li hev bîne match-desktop = Li gorî sermaseyê
name-no-slashes = Nav nabe ku "/" tê de hebe name-no-slashes = Nav nabe ku "/" tê de hebe
operations-running-finished = operations-running-finished =
{ $running } { $running -> { $running } { $running ->
@ -175,10 +175,10 @@ extract-to-title = Derxîne bo peldankê
icon-size = Mezinahiya îkonê icon-size = Mezinahiya îkonê
open-with-title = Tu çawa dixwazî "{ $name }" vekî? open-with-title = Tu çawa dixwazî "{ $name }" vekî?
extracting = extracting =
{ $items } { $items -> { $items } tê derxistin { $items ->
[one] hêman [one] hêman
*[other] hêman *[other] hêman
} tê derxistin ji "{ $from }" bo"{ $to }" ({ $progress })... } ji "{ $from }" bo"{ $to }" ({ $progress })...
settings = Sazkarî settings = Sazkarî
complete = Qediya complete = Qediya
permanently-delete-question = Bi mayînde jê bibe? permanently-delete-question = Bi mayînde jê bibe?
@ -193,10 +193,10 @@ sort-largest-to-smallest = Mezintir bo biçûktir
restore-from-trash = Ji jêbirdankê vegerîne restore-from-trash = Ji jêbirdankê vegerîne
cut = Jê bike cut = Jê bike
moved = moved =
{ $items } { $items -> { $items } hate livandin { $items ->
[one] hêman [one] hêman
*[other] hêman *[other] hêman
} hate livandin ji "{ $from }" bo "{ $to }" } ji "{ $from }" bo "{ $to }"
progress-cancelled = { $percent }%, hat têkbirin progress-cancelled = { $percent }%, hat têkbirin
open-in-new-tab = Di rûgerê nû de veke open-in-new-tab = Di rûgerê nû de veke
file = Pel file = Pel
@ -220,7 +220,7 @@ trash = Jêbirdank
undo = Vegerîne undo = Vegerîne
today = Îro today = Îro
name = Nav name = Nav
modified = Guhertin modified = Guhertî
size = Mezinahî size = Mezinahî
details = Hûrgilî details = Hûrgilî
pause = Rawestîne pause = Rawestîne
@ -229,7 +229,7 @@ copy-to-title = Rêgeha jêgirtinê hilbijêre
copy-to-button-label = Jê bigire copy-to-button-label = Jê bigire
move-to-title = Rêgeha livandinê hilbijêre move-to-title = Rêgeha livandinê hilbijêre
move-to-button-label = Bilivîne move-to-button-label = Bilivîne
create = Afirandin create = Biafrîne
save = Tomar bike save = Tomar bike
context-action = Çalakiya naverokê context-action = Çalakiya naverokê
context-action-confirm-title = "{ $name }" bixebtîne? context-action-confirm-title = "{ $name }" bixebtîne?
@ -263,7 +263,7 @@ restoring =
} ji { trash } ({ $progress })... } ji { trash } ({ $progress })...
icon-size-and-spacing = Mezinahiya îkonan û valahiya di navbera wan de icon-size-and-spacing = Mezinahiya îkonan û valahiya di navbera wan de
type-to-search-enter-path = Riya rêgehê yan jî pelê têdixîne type-to-search-enter-path = Riya rêgehê yan jî pelê têdixîne
menu-open-with = Veke bi riya... menu-open-with = Veke bi...
original-file = Pelê resen original-file = Pelê resen
read-write-execute = Xwendin, nivîsandin û xebitandin read-write-execute = Xwendin, nivîsandin û xebitandin
calculating = Tê jimartin... calculating = Tê jimartin...
@ -311,7 +311,7 @@ restored =
domain = Navper domain = Navper
sort = Rêz bike sort = Rêz bike
item-accessed = Gihîştin: { $accessed } item-accessed = Gihîştin: { $accessed }
open-with = Veke bi riya open-with = Veke bi
keep-both = Herduyan bihêle keep-both = Herduyan bihêle
permanently-deleted = permanently-deleted =
{ $items } bi mayînde hate jêbirin{ $items -> { $items } bi mayînde hate jêbirin{ $items ->
@ -325,12 +325,6 @@ replace = Biguhêrîne
replace-title = "{ $filename }" jixwe di vê cihê de heye replace-title = "{ $filename }" jixwe di vê cihê de heye
unknown-folder = Peldanka nenas unknown-folder = Peldanka nenas
write-only = Tenê nivîsandin write-only = Tenê nivîsandin
trashed-on = Jêbirî trashed-on = Li Jêbirdankê hatiye avêtin
mixed = Tevlihev mixed = Tevlihev
type-to-search-select = Pel an peldanka yekem a lihevhatî hildibijêre type-to-search-select = Pel an peldanka yekem a lihevhatî hildibijêre
calculate = Bijmêre
error = Şaşetî
checksum = Tevahiya { $kind } kontrol bike
sidebar-label = Nîşanka benda kêlekê
search-application = Li gorî navê sepanê bigere
change-sidebar-label = Nîşanka benda kêlekê biguhêrîne

View file

@ -278,12 +278,3 @@ move-to-button-label = 이동
clear-recents-history = 최근 기록 비우기 clear-recents-history = 최근 기록 비우기
copy-path = 복사 경로 copy-path = 복사 경로
move-to-title = 이동 위치 선택 move-to-title = 이동 위치 선택
copy-to-title = 복사 경로 선택
run = 실행
rename-confirm = 이름 변경
mixed = 혼합됨
pasted-image = 붙여넣은 이미지
pasted-text = 붙여넣은 텍스트
pasted-video = 붙여넣은 영상
copy-to = 복사하기...
move-to = 이동하기...

View file

@ -244,7 +244,7 @@ item-created = Sukurtas: { $created }
item-modified = Modifikuota: { $modified } item-modified = Modifikuota: { $modified }
item-accessed = Paskutinė prieiga: { $accessed } item-accessed = Paskutinė prieiga: { $accessed }
calculating = Skaičiuojama... calculating = Skaičiuojama...
settings = Nuostatai settings = Nuostatos
single-click = Vieno paspaudimo atidarymas single-click = Vieno paspaudimo atidarymas
appearance = Išvaizda appearance = Išvaizda
match-desktop = Pagal darbalaukio temą match-desktop = Pagal darbalaukio temą
@ -276,7 +276,7 @@ new-window = Naujas langas
reload-folder = Perkrauti aplanką reload-folder = Perkrauti aplanką
rename = Pervadinti... rename = Pervadinti...
close-tab = Uždaryti skirtuką close-tab = Uždaryti skirtuką
quit = Išjungti quit = Išeiti
edit = Redaguoti edit = Redaguoti
cut = Iškirpti cut = Iškirpti
copy = Kopijuoti copy = Kopijuoti
@ -291,7 +291,7 @@ list-view = Sąrašo išdėstymas
show-hidden-files = Rodyti paslėptus failus show-hidden-files = Rodyti paslėptus failus
list-directories-first = Pirmiau pateikti aplankus list-directories-first = Pirmiau pateikti aplankus
gallery-preview = Galerijos peržiūra gallery-preview = Galerijos peržiūra
menu-settings = Nuostatai... menu-settings = Nuostatos...
menu-about = Apie COSMIC Files... menu-about = Apie COSMIC Files...
sort = Rikiuoti sort = Rikiuoti
sort-a-z = A-Ž sort-a-z = A-Ž
@ -318,9 +318,3 @@ copy-to = Kopijuoti į...
move-to = Perkeltiį į... move-to = Perkeltiį į...
copy-path = Kopijuoti kelią copy-path = Kopijuoti kelią
theme = Stilius theme = Stilius
context-action = Konteksto veiksmas
context-action-confirm-title = Paleisti „{ $name }“?
run = Paleisti
rename-confirm = Pervadinti
calculate = Skaičiuoti
error = Klaida

View file

@ -425,7 +425,7 @@ removed-from-recents =
} uit { recents } verwijderd } uit { recents } verwijderd
remove-from-recents = Uit recente verwijderen remove-from-recents = Uit recente verwijderen
type-to-search-select = Dit selecteert het eerst overeenkomende bestand of map type-to-search-select = Dit selecteert het eerst overeenkomende bestand of map
comment = Bestandsbeheerder voor COSMIC comment = Bestandsbeheerder voor COSMIC desktop
copy-to-title = Kopieerbestemming aanwijzen copy-to-title = Kopieerbestemming aanwijzen
copy-to-button-label = Kopiëren copy-to-button-label = Kopiëren
move-to-button-label = Verplaatsen move-to-button-label = Verplaatsen

View file

@ -1,33 +0,0 @@
appearance = Aparéncia
theme = Tèma
match-desktop = Assortir al burèu
dark = Escur
light = Clar
file = Fichièr
details = Detalhs
name = Nom
cancel = Anullar
repository = Depaus
settings = Configuracion
menu-settings = Configuracion...
quit = Quitar
cosmic-files = Fichièrs de COSMIC
comment = Gestionari de fichièrs pel burèu COSMIC
save-file = Enregistrar lo fichièr
open-with-title = Cossí volètz dobrir « { $name } » ?
other-apps = Autras aplicacions
context-action-confirm-title = Executar « { $name } » ?
run = Executar
delete = Suprimir
replace = Remplaçar
owner = Propietari
group = Grop
write-execute = Escritura e execucion
read-only = Lectura sola
read-execute = Lectura e execucion
read-write = Lectura e execucion
read-write-execute = Lectura, escritura e execucion
remove = Suprimir
connecting = Connexion...
domain = Domeni
password = Senhal

View file

@ -146,7 +146,7 @@ new-tab = ਨਵੀਂ ਟੈਬ
new-window = ਨਵੀਂ ਵਿੰਡੋ new-window = ਨਵੀਂ ਵਿੰਡੋ
rename = ਨਾਂ ਨੂੰ ਬਦਲੋ... rename = ਨਾਂ ਨੂੰ ਬਦਲੋ...
close-tab = ਟੈਬ ਨੂੰ ਬੰਦ ਕਰੋ close-tab = ਟੈਬ ਨੂੰ ਬੰਦ ਕਰੋ
quit = ਸਮਾਪਤ quit = ਬਾਹਰ
edit = ਸੋਧੋ edit = ਸੋਧੋ
cut = ਕੱਟੋ cut = ਕੱਟੋ
copy = ਕਾਪੀ ਕਰੋ copy = ਕਾਪੀ ਕਰੋ
@ -188,9 +188,9 @@ item-accessed = ਪਹੁੰਚ: { $accessed }
calculating = ਗਿਣਤੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ... calculating = ਗਿਣਤੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ...
single-click = ਇੱਕ ਵਾਰ ਕਲਿੱਕ ਕਰਕੇ ਖੋਲ੍ਹੋ single-click = ਇੱਕ ਵਾਰ ਕਲਿੱਕ ਕਰਕੇ ਖੋਲ੍ਹੋ
show-recents = ਬਾਹੀ ਵਿੱਚ ਸੱਜਰੇ ਫੋਲਡਰ show-recents = ਬਾਹੀ ਵਿੱਚ ਸੱਜਰੇ ਫੋਲਡਰ
match-desktop = ਡੈਸਕਟਾਪ ਨਾਲ ਮਿਲਾਓ match-desktop = ਡੈਸਕਟਾਪ ਨਾਲ ਮਿਲਦਾ
dark = ਗੂੜ੍ਹਾ dark = ਗੂੜ੍ਹਾ
light = ਫਿੱਕਾ light = ਹਲਕਾ
type-to-search = ਖੋਜਣ ਲਈ ਲਿਖੋ type-to-search = ਖੋਜਣ ਲਈ ਲਿਖੋ
type-to-search-recursive = ਮੌਜੂਦਾ ਫੋਲਡਰ ਅਤੇ ਸਭ ਅਧੀਨ-ਫੋਲਡਰਾਂ ਵਿੱਚ ਖੋਜੋ type-to-search-recursive = ਮੌਜੂਦਾ ਫੋਲਡਰ ਅਤੇ ਸਭ ਅਧੀਨ-ਫੋਲਡਰਾਂ ਵਿੱਚ ਖੋਜੋ
add-to-sidebar = ਬਾਹੀ ਵਿੱਚ ਜੋੜੋ add-to-sidebar = ਬਾਹੀ ਵਿੱਚ ਜੋੜੋ

View file

@ -1,5 +1,5 @@
cosmic-files = Pliki COSMIC cosmic-files = Pliki COSMIC
comment = Menedżer plików dla środowiska COSMIC comment = Menedżer plików pulpitu COSMIC
keywords = Katalogi;Pliki;Menedżer; keywords = Katalogi;Pliki;Menedżer;
empty-folder = Pusty katalog empty-folder = Pusty katalog
empty-folder-hidden = Pusty katalog (z ukrytymi plikami) empty-folder-hidden = Pusty katalog (z ukrytymi plikami)
@ -27,7 +27,7 @@ modified = Zmodyfikowano
trashed-on = Wyrzucono do kosza trashed-on = Wyrzucono do kosza
size = Rozmiar size = Rozmiar
# Progress footer # Progress footer
details = Szczegóły details = Detale
dismiss = Odrzuć wiadomość dismiss = Odrzuć wiadomość
operations-running = operations-running =
{ $running } bieżące { $running -> { $running } bieżące { $running ->
@ -390,7 +390,7 @@ new-window = Nowe okno
reload-folder = Odśwież katalog reload-folder = Odśwież katalog
rename = Zmień nazwę… rename = Zmień nazwę…
close-tab = Zamknij kartę close-tab = Zamknij kartę
quit = Zakończ quit = Zamknij
## Edit ## Edit
@ -447,9 +447,3 @@ context-action-confirm-warning =
}. }.
run = Uruchom run = Uruchom
rename-confirm = Zmień nazwę rename-confirm = Zmień nazwę
checksum = { $kind } suma kontrolna
calculate = Oblicz
error = Błąd
sidebar-label = Etykieta panelu bocznego
search-application = Wyszukaj po nazwie aplikacji
change-sidebar-label = Zmień etykietę panelu bocznego

View file

@ -109,10 +109,6 @@ permanently-delete-warning = Deseja realmente excluir permanentemente { $target
rename-file = Renomear arquivo rename-file = Renomear arquivo
rename-folder = Renomear pasta rename-folder = Renomear pasta
## Change Sidebar Label Dialog
sidebar-label = Legenda da barra lateral
## Replace Dialog ## Replace Dialog
replace = Substituir replace = Substituir
@ -354,7 +350,7 @@ light = Estilo claro
### Type to Search ### Type to Search
type-to-search = Digitar para pesquisar type-to-search = Digite para pesquisar
type-to-search-recursive = Pesquisa na pasta atual e em todas as subpastas type-to-search-recursive = Pesquisa na pasta atual e em todas as subpastas
type-to-search-enter-path = Insere o caminho do diretório ou arquivo type-to-search-enter-path = Insere o caminho do diretório ou arquivo
# Context menu # Context menu
@ -369,7 +365,6 @@ open-in-terminal = Abrir no terminal
move-to-trash = Mover para a lixeira move-to-trash = Mover para a lixeira
restore-from-trash = Restaurar da lixeira restore-from-trash = Restaurar da lixeira
remove-from-sidebar = Remover da barra lateral remove-from-sidebar = Remover da barra lateral
change-sidebar-label = Alterar legenda da barra lateral
sort-by-name = Ordenar por nome sort-by-name = Ordenar por nome
sort-by-modified = Ordenar por data de modificação sort-by-modified = Ordenar por data de modificação
sort-by-size = Ordenar por tamanho sort-by-size = Ordenar por tamanho
@ -393,7 +388,7 @@ new-window = Nova janela
reload-folder = Recarregar pasta reload-folder = Recarregar pasta
rename = Renomear... rename = Renomear...
close-tab = Fechar aba close-tab = Fechar aba
quit = Encerrar quit = Sair
## Edit ## Edit
@ -451,7 +446,3 @@ context-action-confirm-warning =
}. }.
run = Executar run = Executar
rename-confirm = Renomear rename-confirm = Renomear
checksum = Soma de verificação { $kind }
calculate = Calcular
error = Erro
search-application = Pesquisar por nome de aplicativo

View file

@ -174,7 +174,6 @@ unknown-folder = неизвестная папка
menu-open-with = Открыть с помощью… menu-open-with = Открыть с помощью…
default-app = { $name } (по умолчанию) default-app = { $name } (по умолчанию)
search-application = Поиск по названию приложения
## Show details ## Show details
@ -189,7 +188,7 @@ settings = Параметры
### Appearance ### Appearance
appearance = Внешний вид appearance = Оформление
theme = Тема theme = Тема
match-desktop = Как в системе match-desktop = Как в системе
dark = Тёмная dark = Тёмная
@ -219,7 +218,7 @@ new-tab = Новая вкладка
new-window = Новое окно new-window = Новое окно
rename = Переименовать… rename = Переименовать…
close-tab = Закрыть вкладку close-tab = Закрыть вкладку
quit = Завершить quit = Выйти
## Edit ## Edit
@ -272,8 +271,8 @@ extract-to = Распаковать в…
extract-to-title = Распаковать в папку extract-to-title = Распаковать в папку
mount-error = Не удалось получить доступ к диску mount-error = Не удалось получить доступ к диску
create = Создать create = Создать
open-with-title = Как хотите открыть «{ $name }»? open-with-title = Как вы хотите открыть «{ $name }»?
browse-store = Перейти в { $store } browse-store = Искать в { $store }
other-apps = Другие приложения other-apps = Другие приложения
related-apps = Связанные приложения related-apps = Связанные приложения
selected-items = { $items } выделенных элем. selected-items = { $items } выделенных элем.
@ -321,7 +320,7 @@ type-to-search-enter-path = Ввод пути к каталогу или фай
delete-permanently = Удалить навсегда delete-permanently = Удалить навсегда
eject = Извлечь eject = Извлечь
remove-from-recents = Убрать из недавних remove-from-recents = Убрать из недавних
change-wallpaper = Изменить фон... change-wallpaper = Изменить обои…
desktop-appearance = Параметры оформления… desktop-appearance = Параметры оформления…
display-settings = Параметры экрана… display-settings = Параметры экрана…
reload-folder = Обновить папку reload-folder = Обновить папку
@ -383,16 +382,11 @@ clear-recents-history = Очистить историю недавних
copy-path = Копировать путь copy-path = Копировать путь
mixed = Смешанные mixed = Смешанные
context-action = Контекстная команда context-action = Контекстная команда
context-action-confirm-title = Запустить «{ $name }»? context-action-confirm-title = Выполнить «{ $name }»?
context-action-confirm-warning = context-action-confirm-warning =
Команда затронет { $items } { $items -> Команда затронет { $items } { $items ->
[one] элемент [one] элемент
*[other] элем. *[other] элем.
}. }.
run = Запустить run = Выполнить
rename-confirm = Переименовать rename-confirm = Переименовать
checksum = Хеш-сумма { $kind }
calculate = Вычислить
error = Ошибка
sidebar-label = Подпись в боковой панели
change-sidebar-label = Изменить подпись

View file

@ -1,3 +0,0 @@
cancel = ᱵᱟᱹᱛᱤᱞ
name = ᱧᱩᱛᱩᱢ
save = ᱥᱟᱧᱪᱟᱣ

View file

@ -319,7 +319,7 @@ list-view = Listvy
show-hidden-files = Visa dolda filer show-hidden-files = Visa dolda filer
list-directories-first = Lista mappar först list-directories-first = Lista mappar först
gallery-preview = Galleri förhandsvisning gallery-preview = Galleri förhandsvisning
menu-settings = Inställningar... menu-settings = Inställningar…
menu-about = Om COSMIC Filer... menu-about = Om COSMIC Filer...
## Sortera ## Sortera
@ -419,9 +419,3 @@ context-action-confirm-warning =
}. }.
run = Kör run = Kör
rename-confirm = Byt namn rename-confirm = Byt namn
checksum = { $kind }-kontrollsumma
calculate = Beräkna
error = Fel
sidebar-label = Sidofältsetikett
search-application = Sök på programnamn
change-sidebar-label = Ändra sidofältsetikett

View file

@ -202,9 +202,17 @@ moved =
*[other] ไฟล์ *[other] ไฟล์
} จาก "{ $from }" สู่ "{ $to }" } จาก "{ $from }" สู่ "{ $to }"
renaming = กำลังเปลี่ยนชื่อจาก "{ $from }" เป็น "{ $to }" renaming = กำลังเปลี่ยนชื่อจาก "{ $from }" เป็น "{ $to }"
renamed = เปลี่ยนชื่อจาก "{ $from }" เป็น "{ $to }" แล้ว renamed = Renamed "{ $from }" to "{ $to }"
restoring = กำลังกู้คืน { $items } รายการจาก { trash } ({ $progress })... restoring =
restored = กู้คืน { $items } รายการจาก { trash } แล้ว Restoring { $items } { $items ->
[one] item
*[other] items
} from { trash } ({ $progress })...
restored =
Restored { $items } { $items ->
[one] item
*[other] items
} from { trash }
unknown-folder = แฟ้มที่ไม่รู้จัก unknown-folder = แฟ้มที่ไม่รู้จัก
## Open with ## Open with
@ -232,8 +240,8 @@ settings = การตั้งค่า
appearance = ลักษณะ appearance = ลักษณะ
theme = ธีม theme = ธีม
match-desktop = ใช้ตามธีมหน้าจอหลัก match-desktop = ใช้ตามธีมหน้าจอหลัก
dark = มืด dark = ธีมมืด
light = สว่าง light = ธีมสว่าง
# Context menu # Context menu
add-to-sidebar = เพิ่มเข้าแถบด้านข้าง add-to-sidebar = เพิ่มเข้าแถบด้านข้าง
compress = บีบอัด compress = บีบอัด
@ -298,4 +306,3 @@ sort-newest-first = ไฟล์ใหม่ก่อน
sort-oldest-first = ไฟล์เก่าก่อน sort-oldest-first = ไฟล์เก่าก่อน
sort-smallest-to-largest = ขนาดเล็กก่อน sort-smallest-to-largest = ขนาดเล็กก่อน
sort-largest-to-smallest = ขนาดใหญ่ก่อน sort-largest-to-smallest = ขนาดใหญ่ก่อน
remove = ลบ

View file

@ -158,7 +158,7 @@ new-tab = Нова вкладка
new-window = Нове вікно new-window = Нове вікно
rename = Перейменувати... rename = Перейменувати...
close-tab = Закрити вкладку close-tab = Закрити вкладку
quit = Завершити quit = Вийти
## Edit ## Edit
@ -182,7 +182,7 @@ menu-settings = Налаштування...
menu-about = Про Файли COSMIC... menu-about = Про Файли COSMIC...
repository = Сховище repository = Сховище
support = Підтримка support = Підтримка
details = Подробиці details = Деталі
dismiss = Закрити повідомлення dismiss = Закрити повідомлення
remove = Вилучити remove = Вилучити
cancelled = Скасовані cancelled = Скасовані
@ -389,9 +389,3 @@ context-action-confirm-warning =
context-action = Контекстна дія context-action = Контекстна дія
rename-confirm = Перейменувати rename-confirm = Перейменувати
mixed = Змішано mixed = Змішано
checksum = контрольна сума { $kind }
calculate = Вирахувати
error = Помилка
sidebar-label = Назва бічної панелі
search-application = Пошук за назвою застосунку
change-sidebar-label = Змінити назву бічної панелі

View file

@ -25,7 +25,7 @@ modified = 修改时间
trashed-on = 删除时间 trashed-on = 删除时间
size = 大小 size = 大小
# Progress footer # Progress footer
details = 详情 details = 详细信息
dismiss = 清除消息 dismiss = 清除消息
operations-running = operations-running =
正在进行 { $running } { $running -> 正在进行 { $running } { $running ->
@ -169,8 +169,8 @@ read-write-execute = 读取、写入和执行
favorite-path-error = 打开目录时出错 favorite-path-error = 打开目录时出错
favorite-path-error-description = favorite-path-error-description =
无法打开“{ $path }” 。 无法打开 "{ $path }" 。
“{ $path }”可能不存在或您没有权限打开它。 "{ $path }" 可能不存在或您没有权限打开它。
您想要从侧边栏中移除它吗? 您想要从侧边栏中移除它吗?
remove = 移除 remove = 移除
@ -267,10 +267,10 @@ extracted =
[one] 项目 [one] 项目
*[other] 项目 *[other] 项目
}到“{ $to }” }到“{ $to }”
setting-executable-and-launching = 设置 “{ $name }” 为可执行文件并启动 setting-executable-and-launching = 设置 "{ $name }" 为可执行文件并启动
set-executable-and-launched = 已设 “{ $name }” 为可执行文件并且启动 set-executable-and-launched = 已设 "{ $name }" 为可执行文件并且启动
setting-permissions = 正在设置 “{ $name }” 的权限为 { $mode } setting-permissions = 正在设置 "{ $name }" 的权限为 { $mode }
set-permissions = 设置 “{ $name }” 的权限为 { $mode } set-permissions = 设置 "{ $name }" 的权限为 { $mode }
moving = moving =
正在从“{ $from }”移动 { $items } { $items -> 正在从“{ $from }”移动 { $items } { $items ->
[one] 项目 [one] 项目
@ -302,7 +302,7 @@ removed-from-recents =
*[other] 项目 *[other] 项目
} }
renaming = 正在将“{ $from }”重命名为“{ $to }” renaming = 正在将“{ $from }”重命名为“{ $to }”
renamed = 已重命名 “{ $from }” 为 “{ $to }” renamed = 已重命名"{ $from }"为"{ $to }"
restoring = restoring =
正在从{ trash }中还原 { $items } { $items -> 正在从{ trash }中还原 { $items } { $items ->
[one] 项目 [one] 项目
@ -446,9 +446,3 @@ context-action-confirm-warning =
} 上运行。 } 上运行。
context-action = 环境行动 context-action = 环境行动
rename-confirm = 重命名 rename-confirm = 重命名
calculate = 计算
error = 错误
checksum = { $kind } 校验和
sidebar-label = 侧边栏标签
search-application = 使用应用名称搜索
change-sidebar-label = 更改侧边栏标签

View file

@ -12,7 +12,7 @@ undo = 復原
today = 今天 today = 今天
# List view # List view
name = 名稱 name = 名稱
modified = 修改時間 modified = 修改日期
size = 大小 size = 大小
# Dialogs # Dialogs
@ -42,7 +42,7 @@ name-no-slashes = 名稱不能包含斜線
## Open/Save Dialog ## Open/Save Dialog
cancel = 取消 cancel = 取消
create = 創作 create = 建立
open = 開啟 open = 開啟
open-file = 開啟檔案 open-file = 開啟檔案
open-folder = 開啟資料夾 open-folder = 開啟資料夾
@ -161,8 +161,8 @@ moved =
[one] 項目 [one] 項目
*[other] 項目 *[other] 項目
} 從「{ $from }」至「{ $to }」 } 從「{ $from }」至「{ $to }」
renaming = 正在將「{ $from }」重新命名為「{ $to }」 renaming = 正在重新命名「{ $from }」至「{ $to }」
renamed = 已將「{ $from }」重新命名為「{ $to }」 renamed = 已經重新命名「{ $from }」至「{ $to }」
restoring = restoring =
正在還原 { $items } 項目 { $items -> 正在還原 { $items } 項目 { $items ->
[one] 項目 [one] 項目
@ -182,7 +182,7 @@ default-app = { $name } (預設)
## Show details ## Show details
show-details = 顯示詳細資訊 show-details = 顯示詳細資料
## Settings ## Settings
@ -196,13 +196,13 @@ match-desktop = 符合桌面
dark = 深色 dark = 深色
light = 淺色 light = 淺色
# Context menu # Context menu
add-to-sidebar = 加入側邊欄 add-to-sidebar = 添加至側邊欄
compress = 壓縮… compress = 壓縮…
extract-here = 解壓縮 extract-here = 解壓縮
new-file = 新增檔案... new-file = 新建檔案...
new-folder = 新增資料夾... new-folder = 新建資料夾...
open-in-terminal = 在終端機中開啟 open-in-terminal = 在終端機中開啟
move-to-trash = 丟入垃圾桶 move-to-trash = 移動至垃圾桶
restore-from-trash = 從垃圾桶還原 restore-from-trash = 從垃圾桶還原
remove-from-sidebar = 從側邊欄移除 remove-from-sidebar = 從側邊欄移除
sort-by-name = 依名稱排序 sort-by-name = 依名稱排序
@ -215,11 +215,11 @@ sort-by-size = 依大小排序
## File ## File
file = 檔案 file = 檔案
new-tab = 新增分頁 new-tab = 新建分頁
new-window = 新增視窗 new-window = 新建視窗
rename = 重新命名... rename = 重新命名...
close-tab = 關閉分頁 close-tab = 關閉分頁
quit = 結束 quit = 退出
## Edit ## Edit
@ -258,18 +258,18 @@ deleted =
}從 { trash } }從 { trash }
permanently-deleting = permanently-deleting =
正在永久刪除 { $items } { $items -> 正在永久刪除 { $items } { $items ->
[one] 項目 [one] 项目
*[other] 項目 *[other] 项目
} }
permanently-deleted = permanently-deleted =
已經永久刪除 { $items } { $items -> 已經永久刪除 { $items } { $items ->
[one] 項目 [one] 项目
*[other] 項目 *[other] 项目
} }
removing-from-recents = removing-from-recents =
正在從 { recents } 中移除 { $items } { $items -> 正在從 { recents } 中移除 { $items } { $items ->
[one] 項目 [one] 项目
*[other] 項目 *[other] 项目
} }
deleting = deleting =
正在刪除 { $items } { $items -> 正在刪除 { $items } { $items ->
@ -278,21 +278,21 @@ deleting =
}從 { trash }({ $progress })… }從 { trash }({ $progress })…
removed-from-recents = removed-from-recents =
已經從 { recents } 中移除 { $items } { $items -> 已經從 { recents } 中移除 { $items } { $items ->
[one] 項目 [one] 项目
*[other] 項目 *[other] 项目
} }
repository = 軟體庫 repository = 軟體庫源
desktop-view-options = 桌面檢視選項... desktop-view-options = 桌面檢視選項...
show-on-desktop = 顯示在桌面 show-on-desktop = 顯示在桌面
desktop-folder-content = 桌面資料夾內容 desktop-folder-content = 桌面資料夾內容
mounted-drives = 已經掛載的磁碟機 mounted-drives = 已經掛載的磁碟機
trash-folder-icon = 垃圾桶圖示 trash-folder-icon = 垃圾桶圖示
trashed-on = 丟入時間 trashed-on = 遺棄時間
icon-size-and-spacing = 圖示大小與間距 icon-size-and-spacing = 圖示大小與間距
icon-size = 圖示大小 icon-size = 圖示大小
grid-spacing = 網格間距 grid-spacing = 網格間距
details = 詳細資訊 details = 詳情
dismiss = 關閉訊息 dismiss = 撤停訊息
delete = 刪除 delete = 刪除
remove = 移除 remove = 移除
support = 支援 support = 支援
@ -393,9 +393,3 @@ context-action-confirm-warning =
} 上執行。 } 上執行。
run = 執行 run = 執行
rename-confirm = 重新命名 rename-confirm = 重新命名
checksum = { $kind } 核對和
calculate = 計算
error = 錯誤
sidebar-label = 側邊欄標籤
search-application = 依應用程式名稱搜尋
change-sidebar-label = 變更側邊欄標籤

View file

@ -12,10 +12,6 @@ cargo-target-dir := env('CARGO_TARGET_DIR', 'target')
bin-src := cargo-target-dir / 'release' / name bin-src := cargo-target-dir / 'release' / name
bin-dst := base-dir / 'bin' / name bin-dst := base-dir / 'bin' / name
thumbnailer-bin-name := name + '-thumbnailer'
thumbnailer-bin-src := cargo-target-dir / 'release' / thumbnailer-bin-name
thumbnailer-bin-dst := base-dir / 'bin' / thumbnailer-bin-name
applet-name := name + '-applet' applet-name := name + '-applet'
applet-src := cargo-target-dir / 'release' / applet-name applet-src := cargo-target-dir / 'release' / applet-name
applet-dst := base-dir / 'bin' / applet-name applet-dst := base-dir / 'bin' / applet-name
@ -28,10 +24,6 @@ metainfo := APPID + '.metainfo.xml'
metainfo-src := 'target/xdgen' / metainfo metainfo-src := 'target/xdgen' / metainfo
metainfo-dst := clean(rootdir / prefix) / 'share' / 'metainfo' / metainfo metainfo-dst := clean(rootdir / prefix) / 'share' / 'metainfo' / metainfo
thumbnailer := APPID + '.thumbnailer'
thumbnailer-src := 'res' / thumbnailer
thumbnailer-dst := clean(rootdir / prefix) / 'share' / 'thumbnailers' / thumbnailer
icons-src := 'res' / 'icons' / 'hicolor' icons-src := 'res' / 'icons' / 'hicolor'
icons-dst := clean(rootdir / prefix) / 'share' / 'icons' / 'hicolor' icons-dst := clean(rootdir / prefix) / 'share' / 'icons' / 'hicolor'
@ -53,11 +45,14 @@ clean-dist: clean clean-vendor
build-debug *args: build-debug *args:
cargo build {{args}} cargo build {{args}}
cargo build --package {{applet-name}} {{args}} cargo build --package {{applet-name}} {{args}}
cargo build --package {{thumbnailer-bin-name}} {{args}}
# Compiles with release profile # Compiles with release profile
build-release *args: (build-debug '--release' args) build-release *args: (build-debug '--release' args)
# Compiles applet with release profile
build-release-applet *args:
cargo build --package {{applet-name}} --release {{args}}
# Compiles release profile with vendored dependencies # Compiles release profile with vendored dependencies
build-vendored *args: vendor-extract (build-release '--frozen --offline' args) build-vendored *args: vendor-extract (build-release '--frozen --offline' args)
@ -97,11 +92,9 @@ heaptrack *args:
# Installs files # Installs files
install: install:
install -Dm0755 {{bin-src}} {{bin-dst}} install -Dm0755 {{bin-src}} {{bin-dst}}
install -Dm0755 {{thumbnailer-bin-src}} {{thumbnailer-bin-dst}}
install -Dm0755 {{applet-src}} {{applet-dst}} install -Dm0755 {{applet-src}} {{applet-dst}}
install -Dm0644 {{desktop-src}} {{desktop-dst}} install -Dm0644 {{desktop-src}} {{desktop-dst}}
install -Dm0644 {{metainfo-src}} {{metainfo-dst}} install -Dm0644 {{metainfo-src}} {{metainfo-dst}}
install -Dm0644 {{thumbnailer-src}} {{thumbnailer-dst}}
for size in `ls {{icons-src}}`; do \ for size in `ls {{icons-src}}`; do \
install -Dm0644 "{{icons-src}}/$size/apps/{{APPID}}.svg" "{{icons-dst}}/$size/apps/{{APPID}}.svg"; \ install -Dm0644 "{{icons-src}}/$size/apps/{{APPID}}.svg" "{{icons-dst}}/$size/apps/{{APPID}}.svg"; \
done done
@ -112,7 +105,7 @@ install-applet:
# Uninstalls installed files # Uninstalls installed files
uninstall: uninstall:
rm -f {{bin-dst}} {{thumbnailer-bin-dst}} {{applet-dst}} {{thumbnailer-dst}} rm -f {{bin-dst}} {{applet-dst}}
# Vendor dependencies locally # Vendor dependencies locally
vendor: vendor:

View file

@ -1,4 +0,0 @@
[Thumbnailer Entry]
TryExec=cosmic-files-thumbnailer
Exec=cosmic-files-thumbnailer %o --size %s %i
MimeType=application/vnd.microsoft.portable-executable;application/x-msdownload;

View file

@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::app::{self, Core, Task, context_drawer}; use cosmic::app::{self, Core, Task, context_drawer};
use cosmic::core::Auto;
use cosmic::cosmic_config::{self, ConfigSet}; use cosmic::cosmic_config::{self, ConfigSet};
use cosmic::iced::clipboard::dnd::DndAction; use cosmic::iced::clipboard::dnd::DndAction;
use cosmic::iced::core::SmolStr; use cosmic::iced::core::SmolStr;
@ -17,16 +16,18 @@ use cosmic::iced::widget::button::focus;
use cosmic::iced::widget::scrollable; use cosmic::iced::widget::scrollable;
use cosmic::iced::widget::scrollable::AbsoluteOffset; use cosmic::iced::widget::scrollable::AbsoluteOffset;
use cosmic::iced::window::{self, Event as WindowEvent, Id as WindowId}; use cosmic::iced::window::{self, Event as WindowEvent, Id as WindowId};
use cosmic::iced::{self, Alignment, Event, Length, Size, Subscription, event, mouse, stream}; use cosmic::iced::{
self, Alignment, Event, Length, Rectangle, Size, Subscription, event, mouse, stream,
};
#[cfg(all(feature = "wayland", feature = "desktop-applet"))] #[cfg(all(feature = "wayland", feature = "desktop-applet"))]
use cosmic::iced::{ use cosmic::iced::{
Limits, Point, Rectangle, Limits, Point,
event::wayland::{Event as WaylandEvent, OutputEvent, OverlapNotifyEvent}, event::wayland::{Event as WaylandEvent, OutputEvent, OverlapNotifyEvent},
platform_specific::runtime::wayland::layer_surface::{ platform_specific::runtime::wayland::layer_surface::{
IcedMargin, IcedOutput, SctkLayerSurfaceSettings, IcedMargin, IcedOutput, SctkLayerSurfaceSettings,
}, },
platform_specific::shell::wayland::commands::layer_surface::{ platform_specific::shell::wayland::commands::layer_surface::{
Anchor, KeyboardInteractivity, Layer, destroy_layer_surface, Anchor, KeyboardInteractivity, Layer, destroy_layer_surface, get_layer_surface,
}, },
}; };
use cosmic::widget::about::About; use cosmic::widget::about::About;
@ -35,7 +36,7 @@ use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::segmented_button::{self, Entity, ReorderEvent}; use cosmic::widget::segmented_button::{self, Entity, ReorderEvent};
use cosmic::widget::{self, icon, settings, space}; use cosmic::widget::{self, icon, settings, space};
use cosmic::{Application, ApplicationExt, Element, cosmic_theme, executor, surface, theme}; use cosmic::{Application, ApplicationExt, Element, cosmic_theme, executor, style, surface, theme};
use mime_guess::Mime; use mime_guess::Mime;
use notify_debouncer_full::notify::{self, RecommendedWatcher}; use notify_debouncer_full::notify::{self, RecommendedWatcher};
use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache, new_debouncer}; use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache, new_debouncer};
@ -426,7 +427,6 @@ pub enum NavMenuAction {
Preview(segmented_button::Entity), Preview(segmented_button::Entity),
RunContextAction(segmented_button::Entity, usize), RunContextAction(segmented_button::Entity, usize),
RemoveFromSidebar(segmented_button::Entity), RemoveFromSidebar(segmented_button::Entity),
ChangeSidebarLabel(segmented_button::Entity),
} }
impl MenuAction for NavMenuAction { impl MenuAction for NavMenuAction {
@ -502,7 +502,6 @@ pub enum Message {
OpenWithBrowse, OpenWithBrowse,
OpenWithDialog(Option<Entity>), OpenWithDialog(Option<Entity>),
OpenWithSelection(usize), OpenWithSelection(usize),
OpenWithSearchClear,
OpenWithToggleDefault(bool), OpenWithToggleDefault(bool),
#[cfg(all(feature = "wayland", feature = "desktop-applet"))] #[cfg(all(feature = "wayland", feature = "desktop-applet"))]
Overlap(window::Id, OverlapNotifyEvent), Overlap(window::Id, OverlapNotifyEvent),
@ -606,7 +605,7 @@ pub enum Message {
OutputEvent(OutputEvent, WlOutput), OutputEvent(OutputEvent, WlOutput),
Cosmic(app::Action), Cosmic(app::Action),
None, None,
Surface(surface::Action<Message>), Surface(surface::Action),
CutPaths(Vec<PathBuf>), CutPaths(Vec<PathBuf>),
} }
@ -691,7 +690,6 @@ pub enum DialogPage {
mime: mime_guess::Mime, mime: mime_guess::Mime,
selected: usize, selected: usize,
store_opt: Option<Arc<MimeApp>>, store_opt: Option<Arc<MimeApp>>,
search_app_name: String,
/// When true, the chosen app is written to mimeapps.list as the /// When true, the chosen app is written to mimeapps.list as the
/// default handler for `mime` after it spawns. /// default handler for `mime` after it spawns.
set_default: bool, set_default: bool,
@ -702,10 +700,6 @@ pub enum DialogPage {
DeleteTrash { DeleteTrash {
items: Vec<TrashItem>, items: Vec<TrashItem>,
}, },
ChangeSidebarLabel {
entity: Entity,
label: String,
},
RenameItem { RenameItem {
from: PathBuf, from: PathBuf,
parent: PathBuf, parent: PathBuf,
@ -798,6 +792,7 @@ pub struct MounterData(MounterKey, MounterItem);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum WindowKind { pub enum WindowKind {
ContextMenu(Entity, widget::Id),
Desktop(Entity), Desktop(Entity),
DesktopViewOptions, DesktopViewOptions,
Dialogs(widget::Id), Dialogs(widget::Id),
@ -1590,6 +1585,12 @@ impl App {
fn remove_window(&mut self, id: &window::Id) { fn remove_window(&mut self, id: &window::Id) {
if let Some(window) = self.windows.remove(id) { if let Some(window) = self.windows.remove(id) {
match window.kind { match window.kind {
WindowKind::ContextMenu(entity, _) => {
// Close context menu
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
tab.context_menu = None;
}
}
WindowKind::Desktop(entity) => { WindowKind::Desktop(entity) => {
// Remove the tab from the tab model // Remove the tab from the tab model
self.tab_model.remove(entity); self.tab_model.remove(entity);
@ -1926,6 +1927,21 @@ impl App {
} }
} }
fn close_context_menus(&mut self) -> Task<Message> {
let active = self.tab_model.active();
if let Some(tab) = self.tab_model.data_mut::<Tab>(active) {
tab.location_context_menu_index = None;
if tab.context_menu.is_some() {
return self.update(Message::TabMessage(
Some(active),
tab::Message::ContextMenu(None, None),
));
}
}
Task::none()
}
fn update_nav_model(&mut self) { fn update_nav_model(&mut self) {
let mut nav_model = segmented_button::ModelBuilder::default(); let mut nav_model = segmented_button::ModelBuilder::default();
@ -1939,7 +1955,15 @@ impl App {
for (favorite_i, favorite) in self.config.favorites.iter().enumerate() { for (favorite_i, favorite) in self.config.favorites.iter().enumerate() {
if let Some(path) = favorite.path_opt() { if let Some(path) = favorite.path_opt() {
let name = favorite.display_name().unwrap_or_else(|| fl!("filesystem")); let name = if matches!(favorite, Favorite::Home) {
fl!("home")
} else if let Favorite::Network { name, .. } = favorite {
name.clone()
} else if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) {
file_name.to_string()
} else {
fl!("filesystem")
};
nav_model = nav_model.insert(move |b| { nav_model = nav_model.insert(move |b| {
b.text(name.clone()) b.text(name.clone())
.icon( .icon(
@ -2576,33 +2600,15 @@ impl App {
.favorites .favorites
.iter() .iter()
.map(|favorite| { .map(|favorite| {
match favorite { if let Favorite::Path(path) = favorite {
Favorite::Path(path) => { for (from, to) in path_changes.iter().map(|(f, t)| (f.as_ref(), t.as_ref())) {
for (from, to) in path_changes.iter().map(|(f, t)| (f.as_ref(), t.as_ref())) if path.starts_with(from)
&& let Ok(relative) = path.strip_prefix(from)
{ {
if path.starts_with(from) favorites_changed = true;
&& let Ok(relative) = path.strip_prefix(from) return Favorite::from_path(to.join(relative));
{
favorites_changed = true;
return Favorite::from_path(to.join(relative));
}
} }
} }
Favorite::Named { path, name } => {
for (from, to) in path_changes.iter().map(|(f, t)| (f.as_ref(), t.as_ref()))
{
if path.starts_with(from)
&& let Ok(relative) = path.strip_prefix(from)
{
favorites_changed = true;
return Favorite::Named {
path: to.join(relative),
name: name.clone(),
};
}
}
}
_ => {}
} }
favorite.clone() favorite.clone()
}) })
@ -2712,9 +2718,6 @@ impl Application for App {
), ),
]); ]);
if matches!(flags.mode, Mode::Desktop) {
core.set_auto_blur(Auto::Window | Auto::Popup);
}
let mut app = Self { let mut app = Self {
core, core,
about, about,
@ -2833,16 +2836,8 @@ impl Application for App {
cosmic::Action::App(Message::NavMenuAction(NavMenuAction::OpenInNewTab(entity))) cosmic::Action::App(Message::NavMenuAction(NavMenuAction::OpenInNewTab(entity)))
}) })
.context_menu(self.nav_context_menu()) .context_menu(self.nav_context_menu())
.close_icon(icon::from_name("media-eject-symbolic").size(16).icon()); .close_icon(icon::from_name("media-eject-symbolic").size(16).icon())
.into_container();
#[cfg(feature = "wayland")]
{
nav = nav
.window_id_maybe(self.core().main_window_id())
.on_surface_action(|action| cosmic::Action::Surface(action.flatten()))
}
let mut nav = nav.into_container();
if !self.core.is_condensed() { if !self.core.is_condensed() {
nav = nav.max_width(280); nav = nav.max_width(280);
@ -2853,12 +2848,14 @@ impl Application for App {
)) ))
} }
fn nav_context_menu(&self) -> Option<Vec<widget::menu::Tree<cosmic::Action<Self::Message>>>> { fn nav_context_menu(
&self,
) -> Option<Vec<widget::menu::Tree<cosmic::Action<Self::Message>>>> {
let items = self.nav_model.iter().map(|entity| { let items = self.nav_model.iter().map(|entity| {
let favorite_index_opt = self.nav_model.data::<FavoriteIndex>(entity); let favorite_index_opt = self.nav_model.data::<FavoriteIndex>(entity);
let location_opt = self.nav_model.data::<Location>(entity); let location_opt = self.nav_model.data::<Location>(entity);
let mut items: Vec<widget::menu::Item<NavMenuAction, String>> = Vec::with_capacity(7); let mut items = Vec::with_capacity(7);
if location_opt if location_opt
.and_then(Location::path_opt) .and_then(Location::path_opt)
@ -2918,11 +2915,6 @@ impl Application for App {
} }
items.push(cosmic::widget::menu::Item::Divider); items.push(cosmic::widget::menu::Item::Divider);
if favorite_index_opt.is_some() { if favorite_index_opt.is_some() {
items.push(cosmic::widget::menu::Item::Button(
fl!("change-sidebar-label"),
None,
NavMenuAction::ChangeSidebarLabel(entity),
));
items.push(cosmic::widget::menu::Item::Button( items.push(cosmic::widget::menu::Item::Button(
fl!("remove-from-sidebar"), fl!("remove-from-sidebar"),
None, None,
@ -2990,14 +2982,10 @@ impl Application for App {
}) })
&& let Some(mounter) = MOUNTERS.get(&key) && let Some(mounter) = MOUNTERS.get(&key)
{ {
return mounter.network_drive(uri.clone()).map(move |mounted| { return mounter.network_drive(uri.clone()).map(move |()| {
if mounted { cosmic::Action::App(Message::NetworkDriveOpenEntityAfterMount {
cosmic::Action::App(Message::NetworkDriveOpenEntityAfterMount { entity,
entity, })
})
} else {
cosmic::action::none()
}
}); });
} }
@ -3107,6 +3095,18 @@ impl Application for App {
return cosmic::task::message(cosmic::action::app(Message::SetShowDetails(false))); return cosmic::task::message(cosmic::action::app(Message::SetShowDetails(false)));
} }
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) { if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
if tab.location_context_menu_index.is_some() {
tab.location_context_menu_index = None;
return Task::none();
}
if tab.context_menu.is_some() {
return self.update(Message::TabMessage(
Some(entity),
tab::Message::ContextMenu(None, None),
));
}
if tab.edit_location.is_some() { if tab.edit_location.is_some() {
tab.edit_location = None; tab.edit_location = None;
return Task::none(); return Task::none();
@ -3202,8 +3202,7 @@ impl Application for App {
} else { } else {
Favorite::from_path(path) Favorite::from_path(path)
}; };
let favorite_path = favorite.path_opt(); if !favorites.contains(&favorite) {
if !favorites.iter().any(|f| f.path_opt() == favorite_path) {
favorites.push(favorite); favorites.push(favorite);
} }
} }
@ -3510,18 +3509,10 @@ impl Application for App {
path, path,
mime, mime,
selected, selected,
search_app_name,
set_default, set_default,
.. ..
} => { } => {
let mut available_apps = let available_apps = self.mime_app_cache.get_apps_for_mime(&mime, true);
self.mime_app_cache.get_apps_for_mime(&mime, true);
available_apps.retain(|(app, _)| {
app.name
.to_lowercase()
.trim()
.contains(search_app_name.to_lowercase().as_str().trim())
});
if let Some((app, _)) = available_apps.get(selected) { if let Some((app, _)) = available_apps.get(selected) {
if let Some(mut command) = if let Some(mut command) =
@ -3567,18 +3558,6 @@ impl Application for App {
DialogPage::DeleteTrash { items } => { DialogPage::DeleteTrash { items } => {
tasks.push(self.operation(Operation::DeleteTrash { items })); tasks.push(self.operation(Operation::DeleteTrash { items }));
} }
DialogPage::ChangeSidebarLabel { entity, label } => {
if let Some(FavoriteIndex(favorite_i)) =
self.nav_model.data::<FavoriteIndex>(entity)
{
let mut favorites = self.config.favorites.clone();
if let Some(favorite) = favorites.get_mut(*favorite_i) {
*favorite = favorite.with_label(label.trim());
config_set!(favorites, favorites);
tasks.push(self.update_config());
}
}
}
DialogPage::RenameItem { DialogPage::RenameItem {
from, parent, name, .. from, parent, name, ..
} => { } => {
@ -3864,7 +3843,7 @@ impl Application for App {
Message::Mouse(window_id, _button) => { Message::Mouse(window_id, _button) => {
// Close context menu when clicking outside. // Close context menu when clicking outside.
if self.core.main_window_id() == Some(window_id) { if self.core.main_window_id() == Some(window_id) {
return Task::none(); return self.close_context_menus();
} }
} }
Message::MoveTo(entity_opt) => { Message::MoveTo(entity_opt) => {
@ -3917,7 +3896,7 @@ impl Application for App {
Some((*mounter_key, self.network_drive_input.clone())); Some((*mounter_key, self.network_drive_input.clone()));
return mounter return mounter
.network_drive(self.network_drive_input.clone()) .network_drive(self.network_drive_input.clone())
.map(|_| cosmic::action::none()); .map(|()| cosmic::action::none());
} }
log::warn!( log::warn!(
"no mounter found for connecting to {:?}", "no mounter found for connecting to {:?}",
@ -4180,25 +4159,21 @@ impl Application for App {
let Some(path) = item.path_opt() else { let Some(path) = item.path_opt() else {
continue; continue;
}; };
return Task::batch([ return self.push_dialog(
self.push_dialog( DialogPage::OpenWith {
DialogPage::OpenWith { path: path.clone(),
path: path.clone(), mime: item.mime.clone(),
mime: item.mime.clone(), selected: 0,
selected: 0, store_opt: "x-scheme-handler/mime"
store_opt: "x-scheme-handler/mime" .parse::<mime_guess::Mime>()
.parse::<mime_guess::Mime>() .ok()
.ok() .and_then(|mime| {
.and_then(|mime| { self.mime_app_cache.get(&mime).first().cloned()
self.mime_app_cache.get(&mime).first().cloned() }),
}), set_default: false,
search_app_name: String::new(), },
set_default: false, Some(CONFIRM_OPEN_WITH_BUTTON_ID.clone()),
}, );
Some(CONFIRM_OPEN_WITH_BUTTON_ID.clone()),
),
widget::text_input::focus(self.dialog_text_input.clone()),
]);
} }
} }
} }
@ -4214,14 +4189,6 @@ impl Application for App {
*set_default = enabled; *set_default = enabled;
} }
} }
Message::OpenWithSearchClear => {
if let Some(DialogPage::OpenWith {
search_app_name, ..
}) = self.dialog_pages.front_mut()
{
*search_app_name = String::new();
}
}
Message::Paste(entity_opt) => { Message::Paste(entity_opt) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) if let Some(tab) = self.tab_model.data_mut::<Tab>(entity)
@ -4677,7 +4644,7 @@ impl Application for App {
)); ));
} }
Message::SearchActivate => { Message::SearchActivate => {
let mut tasks = vec![]; let mut tasks = vec![self.close_context_menus()];
if self.search_get().is_none() { if self.search_get().is_none() {
tasks.push(self.search_set_active(Some(String::new()))); tasks.push(self.search_set_active(Some(String::new())));
@ -4688,7 +4655,7 @@ impl Application for App {
return Task::batch(tasks); return Task::batch(tasks);
} }
Message::SearchClear => { Message::SearchClear => {
return self.search_set_active(None); return Task::batch([self.close_context_menus(), self.search_set_active(None)]);
} }
Message::SearchInput(input) => { Message::SearchInput(input) => {
return self.search_set_active(Some(input)); return self.search_set_active(Some(input));
@ -4789,7 +4756,7 @@ impl Application for App {
return self.update_config(); return self.update_config();
} }
Message::TabActivate(entity) => { Message::TabActivate(entity) => {
let mut tasks = vec![]; let mut tasks = vec![self.close_context_menus()];
// Activate new tab // Activate new tab
self.tab_model.activate(entity); self.tab_model.activate(entity);
@ -4851,9 +4818,7 @@ impl Application for App {
tasks.push(Task::future(async move { tasks.push(Task::future(async move {
cosmic::action::app(Message::WindowClose) cosmic::action::app(Message::WindowClose)
})); }));
} else if entity == self.tab_model.active() } else if let Some(position) = self.tab_model.position(entity) {
&& let Some(position) = self.tab_model.position(entity)
{
let new_position = if position > 0 { let new_position = if position > 0 {
position - 1 position - 1
} else { } else {
@ -4890,8 +4855,6 @@ impl Application for App {
} }
Message::TabMessage(entity_opt, tab_message) => { Message::TabMessage(entity_opt, tab_message) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
// The context menu opens on right-button release, so refresh paste availability now
let right_click = matches!(tab_message, tab::Message::RightClick(..));
let tab_commands = match self.tab_model.data_mut::<Tab>(entity) { let tab_commands = match self.tab_model.data_mut::<Tab>(entity) {
Some(tab) => tab.update(tab_message, self.modifiers), Some(tab) => tab.update(tab_message, self.modifiers),
@ -4899,9 +4862,6 @@ impl Application for App {
}; };
let mut commands = Vec::new(); let mut commands = Vec::new();
if right_click {
commands.push(self.update(Message::CheckClipboard));
}
for tab_command in tab_commands { for tab_command in tab_commands {
match tab_command { match tab_command {
tab::Command::Action(action) => { tab::Command::Action(action) => {
@ -4914,8 +4874,7 @@ impl Application for App {
tab::Command::AddToSidebar(path) => { tab::Command::AddToSidebar(path) => {
let mut favorites = self.config.favorites.clone(); let mut favorites = self.config.favorites.clone();
let favorite = Favorite::from_path(path); let favorite = Favorite::from_path(path);
let favorite_path = favorite.path_opt(); if !favorites.contains(&favorite) {
if !favorites.iter().any(|f| f.path_opt() == favorite_path) {
favorites.push(favorite); favorites.push(favorite);
} }
config_set!(favorites, favorites); config_set!(favorites, favorites);
@ -4942,11 +4901,78 @@ impl Application for App {
self.update_tab(entity, tab_path, selection_paths), self.update_tab(entity, tab_path, selection_paths),
])); ]));
} }
tab::Command::Surface(action) => { tab::Command::ContextMenu(point_opt, parent_id) => {
// re-type the tab's surface action the way its messages are re-typed #[cfg(feature = "wayland")]
let action = action if let Some(point) = point_opt {
.map(move |message| Message::TabMessage(Some(entity), message)); if crate::is_wayland() {
commands.push(self.update(Message::Surface(action))); // Open context menu
use cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{
Anchor, Gravity,
};
use cosmic::iced::runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};
let window_id = WindowId::unique();
self.windows.insert(
window_id,
Window::new(WindowKind::ContextMenu(
entity,
widget::Id::unique(),
)),
);
commands.push(self.update(Message::CheckClipboard));
commands.push(self.update(Message::Surface(
cosmic::surface::action::app_popup(
|_: &Self| cosmic::surface::action::LiveSettings::default(),
move |app: &mut Self| -> SctkPopupSettings {
let anchor_rect = Rectangle {
x: point.x as i32,
y: point.y as i32,
width: 1,
height: 1,
};
let positioner = SctkPositioner {
size: None,
anchor_rect,
anchor: Anchor::None,
gravity: Gravity::BottomRight,
reactive: true,
..Default::default()
};
SctkPopupSettings {
parent: parent_id.unwrap_or(
app.core
.main_window_id()
.unwrap_or(WindowId::NONE),
),
id: window_id,
positioner,
parent_size: None,
grab: true,
close_with_children: false,
input_zone: None,
}
},
None,
),
)));
}
} else {
// Destroy previous popup
let mut window_ids = Vec::new();
for (window_id, window) in &self.windows {
if let WindowKind::ContextMenu(e, _) = &window.kind
&& *e == entity
{
window_ids.push(*window_id);
}
}
for window_id in window_ids {
commands.push(self.update(Message::Surface(
cosmic::surface::action::destroy_popup(window_id),
)));
}
}
} }
tab::Command::Delete(paths) => commands.push(self.delete(paths)), tab::Command::Delete(paths) => commands.push(self.delete(paths)),
tab::Command::DropFiles(to, from) => { tab::Command::DropFiles(to, from) => {
@ -4996,6 +5022,7 @@ impl Application for App {
} }
tab::Command::OpenFile(paths) => commands.push(self.open_file(&paths)), tab::Command::OpenFile(paths) => commands.push(self.open_file(&paths)),
tab::Command::OpenInNewTab(path) => { tab::Command::OpenInNewTab(path) => {
commands.push(self.close_context_menus());
commands.push(self.open_tab(Location::Path(path), false, None)); commands.push(self.open_tab(Location::Path(path), false, None));
} }
tab::Command::OpenInNewWindow(path) => match env::current_exe() { tab::Command::OpenInNewWindow(path) => match env::current_exe() {
@ -5432,6 +5459,12 @@ impl Application for App {
if let Some(tab) = self.tab_model.data_mut::<Tab>(tab_entity) { if let Some(tab) = self.tab_model.data_mut::<Tab>(tab_entity) {
// Close location editing if enabled // Close location editing if enabled
tab.edit_location = None; tab.edit_location = None;
// Close other context menus.
tab.location_context_menu_index = None;
return Task::done(cosmic::Action::App(Message::TabMessage(
Some(tab_entity),
tab::Message::ContextMenu(None, None),
)));
} }
} }
Message::NavMenuAction(action) => match action { Message::NavMenuAction(action) => match action {
@ -5475,7 +5508,6 @@ impl Application for App {
.and_then(|mime| { .and_then(|mime| {
self.mime_app_cache.get(&mime).first().cloned() self.mime_app_cache.get(&mime).first().cloned()
}), }),
search_app_name: String::new(),
set_default: false, set_default: false,
}, },
None, None,
@ -5530,7 +5562,7 @@ impl Application for App {
_ => Task::none(), _ => Task::none(),
}; };
return open_task; return Task::batch([self.close_context_menus(), open_task]);
} }
// Open the selected path in a new cosmic-files window. // Open the selected path in a new cosmic-files window.
@ -5611,20 +5643,6 @@ impl Application for App {
return self.update_config(); return self.update_config();
} }
} }
NavMenuAction::ChangeSidebarLabel(entity) => {
if let Some(favorite) = self.nav_model.data::<FavoriteIndex>(entity).and_then(
|FavoriteIndex(favorite_i)| self.config.favorites.get(*favorite_i),
) {
let label = favorite.display_name().unwrap_or_else(|| fl!("filesystem"));
return Task::batch([
self.dialog_pages
.push_back(DialogPage::ChangeSidebarLabel { entity, label }),
widget::text_input::focus(self.dialog_text_input.clone()),
widget::text_input::select_all(self.dialog_text_input.clone()),
]);
}
}
}, },
Message::Recents => { Message::Recents => {
if self.config.show_recents { if self.config.show_recents {
@ -5676,33 +5694,24 @@ impl Application for App {
.insert(surface_id, Window::new(WindowKind::Desktop(entity))); .insert(surface_id, Window::new(WindowKind::Desktop(entity)));
return Task::batch([ return Task::batch([
command, command,
cosmic::task::message(cosmic::Action::Surface( get_layer_surface(SctkLayerSurfaceSettings {
cosmic::surface::action::app_layer_shell( id: surface_id,
|_| Default::default(), layer: Layer::Bottom,
move |_: &mut App| SctkLayerSurfaceSettings { keyboard_interactivity: KeyboardInteractivity::OnDemand,
id: surface_id, input_zone: None,
layer: Layer::Bottom, anchor: Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT,
keyboard_interactivity: KeyboardInteractivity::OnDemand, output: IcedOutput::Output(output),
input_zone: None, namespace: "cosmic-files-applet".into(),
anchor: Anchor::TOP size: Some((None, None)),
| Anchor::BOTTOM margin: IcedMargin {
| Anchor::LEFT top: 0,
| Anchor::RIGHT, bottom: 0,
output: IcedOutput::Output(output.clone()), left: 0,
namespace: "cosmic-files-applet".into(), right: 0,
size: Some((None, None)), },
margin: IcedMargin { exclusive_zone: 0,
top: 0, size_limits: Limits::NONE.min_width(1.0).min_height(1.0),
bottom: 0, }),
left: 0,
right: 0,
},
exclusive_zone: 0,
size_limits: Limits::NONE.min_width(1.0).min_height(1.0),
},
None,
),
)),
#[cfg(all(feature = "wayland", feature = "desktop-applet"))] #[cfg(all(feature = "wayland", feature = "desktop-applet"))]
overlap_notify(surface_id, true), overlap_notify(surface_id, true),
]); ]);
@ -5795,7 +5804,9 @@ impl Application for App {
}); });
} }
Message::Surface(action) => { Message::Surface(action) => {
return cosmic::task::message(cosmic::Action::Surface(action)); return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(action),
));
} }
Message::SaveSortNames => { Message::SaveSortNames => {
self.must_save_sort_names = false; self.must_save_sort_names = false;
@ -6341,9 +6352,7 @@ impl Application for App {
mime, mime,
selected, selected,
store_opt, store_opt,
search_app_name,
set_default, set_default,
..
} => { } => {
let name = match path.file_name() { let name = match path.file_name() {
Some(file_name) => file_name.to_str(), Some(file_name) => file_name.to_str(),
@ -6351,13 +6360,7 @@ impl Application for App {
}; };
let mut column = widget::list_column(); let mut column = widget::list_column();
let mut available_apps = self.mime_app_cache.get_apps_for_mime(mime, true); let available_apps = self.mime_app_cache.get_apps_for_mime(mime, true);
available_apps.retain(|(app, _)| {
app.name
.to_lowercase()
.trim()
.contains(search_app_name.to_lowercase().as_str().trim())
});
let item_height = 32.0; let item_height = 32.0;
let mut displayed_default = false; let mut displayed_default = false;
let mut last_kind = MimeAppMatch::Exact; let mut last_kind = MimeAppMatch::Exact;
@ -6421,25 +6424,6 @@ impl Application for App {
.secondary_action( .secondary_action(
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel), widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
) )
.control(
widget::text_input::search_input(
fl!("search-application"),
search_app_name,
)
.id(self.dialog_text_input.clone())
.on_clear(Message::OpenWithSearchClear)
.on_input(move |search_app_name| {
Message::DialogUpdate(DialogPage::OpenWith {
path: path.clone(),
mime: mime.clone(),
selected: *selected,
store_opt: store_opt.clone(),
search_app_name,
set_default: *set_default,
})
})
.on_submit(|_| Message::DialogComplete),
)
.control(widget::scrollable(column).height({ .control(widget::scrollable(column).height({
let max_size = self let max_size = self
.size .size
@ -6528,40 +6512,6 @@ impl Application for App {
target = target target = target
))) )))
} }
DialogPage::ChangeSidebarLabel { entity, label } => {
let entity = *entity;
let complete_maybe = if label.trim().is_empty() {
None
} else {
Some(Message::DialogComplete)
};
widget::dialog()
.title(fl!("change-sidebar-label"))
.primary_action(
widget::button::suggested(fl!("save"))
.on_press_maybe(complete_maybe.clone()),
)
.secondary_action(
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
)
.control(
widget::column::with_children([
widget::text::body(fl!("sidebar-label")).into(),
widget::text_input("", label.as_str())
.id(self.dialog_text_input.clone())
.on_input(move |label| {
Message::DialogUpdate(DialogPage::ChangeSidebarLabel {
entity,
label,
})
})
.on_submit_maybe(complete_maybe.map(|maybe| move |_| maybe.clone()))
.into(),
])
.spacing(space_xxs),
)
}
DialogPage::RenameItem { DialogPage::RenameItem {
from, from,
parent, parent,
@ -6948,6 +6898,7 @@ impl Application for App {
}) })
.drag_id(self.tab_drag_id), .drag_id(self.tab_drag_id),
) )
.class(style::Container::Background)
.width(Length::Fill) .width(Length::Fill)
.padding([0, space_s]), .padding([0, space_s]),
); );
@ -7011,6 +6962,23 @@ impl Application for App {
fn view_window(&self, id: WindowId) -> Element<'_, Self::Message> { fn view_window(&self, id: WindowId) -> Element<'_, Self::Message> {
let content = match self.windows.get(&id) { let content = match self.windows.get(&id) {
Some(window) => match &window.kind { Some(window) => match &window.kind {
WindowKind::ContextMenu(entity, id) => match self.tab_model.data::<Tab>(*entity) {
Some(tab) => {
return widget::autosize::autosize(
menu::context_menu(
tab,
&self.key_binds,
&window.modifiers,
self.clipboard_has_content(),
&self.config.context_actions,
)
.map(|x| Message::TabMessage(Some(*entity), x)),
id.clone(),
)
.into();
}
None => widget::text("Unknown tab ID").into(),
},
WindowKind::Desktop(entity) => { WindowKind::Desktop(entity) => {
let mut tab_column = widget::column::with_capacity(3); let mut tab_column = widget::column::with_capacity(3);
@ -7070,7 +7038,6 @@ impl Application for App {
return self.view_main().map(|message| match message { return self.view_main().map(|message| match message {
cosmic::Action::App(app) => app, cosmic::Action::App(app) => app,
cosmic::Action::Cosmic(cosmic) => Message::Cosmic(cosmic), cosmic::Action::Cosmic(cosmic) => Message::Cosmic(cosmic),
cosmic::Action::Surface(action) => Message::Surface(action),
cosmic::Action::None => Message::None, cosmic::Action::None => Message::None,
}); });
} }

View file

@ -70,11 +70,6 @@ pub enum Favorite {
name: String, name: String,
path: PathBuf, path: PathBuf,
}, },
/// A path with a custom name chosen by the user
Named {
path: PathBuf,
name: String,
},
} }
impl Favorite { impl Favorite {
@ -103,38 +98,6 @@ impl Favorite {
Self::Videos => dirs::video_dir(), Self::Videos => dirs::video_dir(),
Self::Path(path) => Some(path.clone()), Self::Path(path) => Some(path.clone()),
Self::Network { path, .. } => Some(path.clone()), Self::Network { path, .. } => Some(path.clone()),
Self::Named { path, .. } => Some(path.clone()),
}
}
/// Name shown in the sidebar, or `None` if the path has no usable file name
pub fn display_name(&self) -> Option<String> {
match self {
Self::Home => Some(crate::fl!("home")),
Self::Named { name, .. } | Self::Network { name, .. } => Some(name.clone()),
_ => self
.path_opt()?
.file_name()
.and_then(|x| x.to_str())
.map(ToString::to_string),
}
}
/// Return this favorite with a custom sidebar label chosen by the user
pub fn with_label(&self, name: &str) -> Self {
match self {
Self::Network { uri, path, .. } => Self::Network {
uri: uri.clone(),
name: name.to_string(),
path: path.clone(),
},
other => match other.path_opt() {
Some(path) => Self::Named {
path,
name: name.to_string(),
},
None => other.clone(),
},
} }
} }
} }
@ -150,7 +113,6 @@ pub enum TypeToSearch {
#[serde(default)] #[serde(default)]
pub struct State { pub struct State {
pub sort_names: FxOrderMap<String, (HeadingOptions, bool)>, pub sort_names: FxOrderMap<String, (HeadingOptions, bool)>,
pub dialog_sort_name: (HeadingOptions, bool),
} }
impl Default for State { impl Default for State {
@ -162,7 +124,6 @@ impl Default for State {
(HeadingOptions::Modified, false), (HeadingOptions::Modified, false),
) )
})), })),
dialog_sort_name: (HeadingOptions::Modified, false),
} }
} }
} }
@ -515,66 +476,3 @@ pub const TIME_CONFIG_ID: &str = "com.system76.CosmicAppletTime";
pub struct TimeConfig { pub struct TimeConfig {
pub military_time: bool, pub military_time: bool,
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn favorite_with_label_converts_path_to_named() {
let favorite = Favorite::Path(PathBuf::from("/some/dir"));
assert_eq!(
favorite.with_label("Custom"),
Favorite::Named {
path: PathBuf::from("/some/dir"),
name: "Custom".to_string(),
}
);
}
#[test]
fn favorite_with_label_updates_network_in_place() {
let favorite = Favorite::Network {
uri: "sftp://example.com/".to_string(),
name: "example.com".to_string(),
path: PathBuf::from("/run/mount/example"),
};
assert_eq!(
favorite.with_label("Custom"),
Favorite::Network {
uri: "sftp://example.com/".to_string(),
name: "Custom".to_string(),
path: PathBuf::from("/run/mount/example"),
}
);
}
#[test]
fn favorite_with_label_converts_special_folder_to_named() {
let home = dirs::home_dir().unwrap();
assert_eq!(
Favorite::Home.with_label("Custom"),
Favorite::Named {
path: home,
name: "Custom".to_string(),
}
);
}
#[test]
fn favorite_display_name() {
assert_eq!(
Favorite::Path(PathBuf::from("/some/dir")).display_name(),
Some("dir".to_string())
);
assert_eq!(Favorite::Path(PathBuf::from("/")).display_name(), None);
assert_eq!(
Favorite::Named {
path: PathBuf::from("/some/dir"),
name: "Custom".to_string(),
}
.display_name(),
Some("Custom".to_string())
);
}
}

View file

@ -3,7 +3,6 @@
use cosmic::app::cosmic::Cosmic; use cosmic::app::cosmic::Cosmic;
use cosmic::app::{Core, Task, context_drawer}; use cosmic::app::{Core, Task, context_drawer};
use cosmic::cosmic_config::ConfigSet;
use cosmic::iced::core::SmolStr; use cosmic::iced::core::SmolStr;
use cosmic::iced::core::widget::operation; use cosmic::iced::core::widget::operation;
use cosmic::iced::futures::{self, SinkExt}; use cosmic::iced::futures::{self, SinkExt};
@ -34,12 +33,12 @@ use crate::app::{
Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID, Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID,
}; };
use crate::config::{ use crate::config::{
Config, DialogConfig, State, TIME_CONFIG_ID, ThumbCfg, TimeConfig, TypeToSearch, Config, DialogConfig, Favorite, TIME_CONFIG_ID, ThumbCfg, TimeConfig, TypeToSearch,
}; };
use crate::key_bind::key_binds; use crate::key_bind::key_binds;
use crate::localize::LANGUAGE_SORTER; use crate::localize::LANGUAGE_SORTER;
use crate::mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage}; use crate::mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage};
use crate::tab::{self, HeadingOptions, ItemMetadata, Location, SearchLocation, Tab}; use crate::tab::{self, ItemMetadata, Location, SearchLocation, Tab};
use crate::zoom::{zoom_in_view, zoom_out_view, zoom_to_default}; use crate::zoom::{zoom_in_view, zoom_out_view, zoom_to_default};
use crate::{fl, home_dir, menu, mime_icon}; use crate::{fl, home_dir, menu, mime_icon};
@ -254,7 +253,6 @@ impl<M: Send + 'static> Dialog<M> {
crate::localize::localize(); crate::localize::localize();
let (config_handler, config) = Config::load(); let (config_handler, config) = Config::load();
let (state_handler, state) = State::load();
let mut settings = window::Settings { let mut settings = window::Settings {
decorations: false, decorations: false,
@ -289,8 +287,6 @@ impl<M: Send + 'static> Dialog<M> {
window_id, window_id,
config_handler, config_handler,
config, config,
state_handler,
state,
}; };
let (cosmic, cosmic_command) = Cosmic::<App>::init((core, flags)); let (cosmic, cosmic_command) = Cosmic::<App>::init((core, flags));
@ -445,8 +441,6 @@ struct Flags {
#[allow(dead_code)] #[allow(dead_code)]
config_handler: Option<cosmic_config::Config>, config_handler: Option<cosmic_config::Config>,
config: Config, config: Config,
state_handler: Option<cosmic_config::Config>,
state: State,
} }
/// Messages that are used specifically by our [`App`]. /// Messages that are used specifically by our [`App`].
@ -476,7 +470,7 @@ enum Message {
SearchActivate, SearchActivate,
SearchClear, SearchClear,
SearchInput(String), SearchInput(String),
Surface(cosmic::surface::Action<Message>), Surface(cosmic::surface::Action),
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
TabMessage(tab::Message), TabMessage(tab::Message),
TabRescan( TabRescan(
@ -509,7 +503,7 @@ impl From<AppMessage> for Message {
AppMessage::ZoomIn(_entity_opt) => Self::ZoomIn, AppMessage::ZoomIn(_entity_opt) => Self::ZoomIn,
AppMessage::ZoomOut(_entity_opt) => Self::ZoomOut, AppMessage::ZoomOut(_entity_opt) => Self::ZoomOut,
AppMessage::NewItem(_entity_opt, true) => Self::NewFolder, AppMessage::NewItem(_entity_opt, true) => Self::NewFolder,
AppMessage::Surface(action) => Self::Surface(action.map(Self::from)), AppMessage::Surface(action) => Self::Surface(action),
unsupported => { unsupported => {
log::warn!("{unsupported:?} not supported in dialog mode"); log::warn!("{unsupported:?} not supported in dialog mode");
Self::None Self::None
@ -549,6 +543,7 @@ struct App {
title: String, title: String,
accept_label: DialogLabel, accept_label: DialogLabel,
choices: Vec<DialogChoice>, choices: Vec<DialogChoice>,
context_menu_window: Option<window::Id>,
context_page: ContextPage, context_page: ContextPage,
dialog_pages: VecDeque<DialogPage>, dialog_pages: VecDeque<DialogPage>,
dialog_text_input: widget::Id, dialog_text_input: widget::Id,
@ -878,6 +873,15 @@ impl App {
} }
} }
fn close_context_menus(&mut self) -> Task<Message> {
self.tab.location_context_menu_index = None;
if self.tab.context_menu.is_some() {
return self.update(Message::TabMessage(tab::Message::ContextMenu(None, None)));
}
Task::none()
}
fn update_nav_model(&mut self) { fn update_nav_model(&mut self) {
let mut nav_model = segmented_button::ModelBuilder::default(); let mut nav_model = segmented_button::ModelBuilder::default();
@ -891,7 +895,13 @@ impl App {
for favorite in &self.flags.config.favorites { for favorite in &self.flags.config.favorites {
if let Some(path) = favorite.path_opt() { if let Some(path) = favorite.path_opt() {
let Some(name) = favorite.display_name() else { let name = if matches!(favorite, Favorite::Home) {
fl!("home")
} else if let Favorite::Network { name, .. } = favorite {
name.clone()
} else if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) {
file_name.to_string()
} else {
continue; continue;
}; };
nav_model = nav_model.insert(move |b| { nav_model = nav_model.insert(move |b| {
@ -1041,7 +1051,8 @@ impl Application for App {
None, None,
); );
tab.mode = tab::Mode::Dialog(flags.kind.clone()); tab.mode = tab::Mode::Dialog(flags.kind.clone());
(tab.sort_name, tab.sort_direction) = flags.state.dialog_sort_name; tab.sort_name = tab::HeadingOptions::Modified;
tab.sort_direction = false;
let key_binds = key_binds(&tab.mode); let key_binds = key_binds(&tab.mode);
@ -1051,6 +1062,7 @@ impl Application for App {
title, title,
accept_label: DialogLabel::from(accept_label), accept_label: DialogLabel::from(accept_label),
choices: Vec::new(), choices: Vec::new(),
context_menu_window: None,
context_page: ContextPage::Preview(None, PreviewKind::Selected), context_page: ContextPage::Preview(None, PreviewKind::Selected),
dialog_pages: VecDeque::new(), dialog_pages: VecDeque::new(),
dialog_text_input: widget::Id::new("Dialog Text Input"), dialog_text_input: widget::Id::new("Dialog Text Input"),
@ -1321,6 +1333,15 @@ impl Application for App {
return Task::none(); return Task::none();
} }
if self.tab.location_context_menu_index.is_some() {
self.tab.location_context_menu_index = None;
return Task::none();
}
if self.tab.context_menu.is_some() {
return self.update(Message::TabMessage(tab::Message::ContextMenu(None, None)));
}
if self.tab.edit_location.is_some() { if self.tab.edit_location.is_some() {
// Close location editing if enabled // Close location editing if enabled
self.tab.edit_location = None; self.tab.edit_location = None;
@ -1555,7 +1576,7 @@ impl Application for App {
Message::Mouse(window_id, _button) => { Message::Mouse(window_id, _button) => {
// Close context menu when clicking outside. // Close context menu when clicking outside.
if self.core.main_window_id() == Some(window_id) { if self.core.main_window_id() == Some(window_id) {
return Task::none(); return self.close_context_menus();
} }
} }
Message::NewFolder => { Message::NewFolder => {
@ -1715,7 +1736,7 @@ impl Application for App {
))); )));
} }
Message::SearchActivate => { Message::SearchActivate => {
let mut tasks = vec![]; let mut tasks = vec![self.close_context_menus()];
if self.search_get().is_none() { if self.search_get().is_none() {
tasks.push(self.search_set(Some(String::new()))); tasks.push(self.search_set(Some(String::new())));
@ -1726,7 +1747,7 @@ impl Application for App {
return Task::batch(tasks); return Task::batch(tasks);
} }
Message::SearchClear => { Message::SearchClear => {
return self.search_set(None); return Task::batch([self.close_context_menus(), self.search_set(None)]);
} }
Message::SearchInput(input) => { Message::SearchInput(input) => {
return self.search_set(Some(input)); return self.search_set(Some(input));
@ -1762,9 +1783,78 @@ impl Application for App {
self.rescan_tab(selection_paths), self.rescan_tab(selection_paths),
])); ]));
} }
tab::Command::Surface(action) => { tab::Command::ContextMenu(point_opt, parent_id) => {
let action = action.map(Message::TabMessage); #[cfg(feature = "wayland")]
commands.push(self.update(Message::Surface(action))); match point_opt {
Some(point) => {
if crate::is_wayland() {
// Open context menu
use cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{
Anchor, Gravity,
};
use cosmic::iced::runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};
use cosmic::iced::Rectangle;
let window_id = window::Id::unique();
self.context_menu_window = Some(window_id);
let autosize_id = widget::Id::unique();
commands.push(self.update(Message::Surface(
cosmic::surface::action::app_popup(
|_: &Self| cosmic::surface::action::LiveSettings::default(),
move |app: &mut Self| -> SctkPopupSettings {
let anchor_rect = Rectangle {
x: point.x as i32,
y: point.y as i32,
width: 1,
height: 1,
};
let positioner = SctkPositioner {
size: None,
anchor_rect,
anchor: Anchor::None,
gravity: Gravity::BottomRight,
reactive: true,
..Default::default()
};
SctkPopupSettings {
parent: parent_id
.unwrap_or(app.flags.window_id),
id: window_id,
positioner,
parent_size: None,
grab: true,
close_with_children: false,
input_zone: None,
}
},
Some(Box::new(move |app: &Self| {
widget::autosize::autosize(
menu::context_menu(
&app.tab,
&app.key_binds,
&app.modifiers,
false, // Paste not used in dialogs
&app.flags.config.context_actions,
)
.map(Message::TabMessage)
.map(cosmic::Action::App),
autosize_id.clone(),
)
.into()
})),
),
)));
}
}
None => {
if let Some(window_id) = self.context_menu_window.take() {
commands.push(self.update(Message::Surface(
cosmic::surface::action::destroy_popup(window_id),
)));
}
}
}
} }
tab::Command::Iced(iced_command) => { tab::Command::Iced(iced_command) => {
commands.push(iced_command.0.map(|tab_message| { commands.push(iced_command.0.map(|tab_message| {
@ -1799,17 +1889,6 @@ impl Application for App {
self.auto_scroll_speed = None; self.auto_scroll_speed = None;
} }
} }
tab::Command::SetSort(_, heading_options, direction) => {
self.flags.state.dialog_sort_name = (heading_options, direction);
if let Some(state_handler) = self.flags.state_handler.as_ref()
&& let Err(err) = state_handler.set::<&(HeadingOptions, bool)>(
"dialog_sort_name",
&self.flags.state.dialog_sort_name,
)
{
log::warn!("Failed to save dialog sort name: {err:?}");
}
}
unsupported => { unsupported => {
log::warn!("{unsupported:?} not supported in dialog mode"); log::warn!("{unsupported:?} not supported in dialog mode");
} }
@ -1930,7 +2009,9 @@ impl Application for App {
}); });
} }
Message::Surface(action) => { Message::Surface(action) => {
return cosmic::task::message(cosmic::Action::Surface(action)); return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(action),
));
} }
} }

View file

@ -1,12 +1,16 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::{Element, theme};
use cosmic::app::Core; use cosmic::app::Core;
use cosmic::iced::advanced::widget::text::Style as TextStyle;
use cosmic::iced::keyboard::Modifiers; use cosmic::iced::keyboard::Modifiers;
use cosmic::widget::menu::action::MenuAction; use cosmic::iced::{Alignment, Background, Border, Length};
use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar}; use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar};
use cosmic::widget::{self, responsive_menu_bar}; use cosmic::widget::{
self, Row, button, column, container, divider, responsive_menu_bar, space, text,
};
use cosmic::{Element, theme};
#[cfg(feature = "desktop")]
use i18n_embed::LanguageLoader; use i18n_embed::LanguageLoader;
use mime_guess::Mime; use mime_guess::Mime;
use std::collections::HashMap; use std::collections::HashMap;
@ -23,6 +27,21 @@ use crate::trash::{Trash, TrashExt};
static MENU_ID: LazyLock<cosmic::widget::Id> = static MENU_ID: LazyLock<cosmic::widget::Id> =
LazyLock::new(|| cosmic::widget::Id::new("responsive-menu")); LazyLock::new(|| cosmic::widget::Id::new("responsive-menu"));
macro_rules! menu_button {
($($x:expr),+ $(,)?) => (
button::custom(
Row::with_children(
[$(Element::from($x)),+]
)
.height(Length::Fixed(24.0))
.align_y(Alignment::Center)
)
.padding([theme::spacing().space_xxs, 16])
.width(Length::Fill)
.class(theme::Button::MenuItem)
);
}
const fn menu_button_optional( const fn menu_button_optional(
label: String, label: String,
action: Action, action: Action,
@ -35,46 +54,82 @@ const fn menu_button_optional(
} }
} }
/// A menu action dispatched to the tab the menu was opened on.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TabAction(pub Action);
impl MenuAction for TabAction {
type Message = tab::Message;
fn message(&self) -> tab::Message {
tab::Message::ContextAction(self.0)
}
}
pub fn context_menu<'a>( pub fn context_menu<'a>(
tab: &Tab, tab: &Tab,
key_binds: &HashMap<KeyBind, Action>, key_binds: &HashMap<KeyBind, Action>,
modifiers: &Modifiers, modifiers: &Modifiers,
clipboard_paste_available: bool, clipboard_paste_available: bool,
context_actions: &[ContextActionPreset], context_actions: &[ContextActionPreset],
) -> Vec<menu::Tree<tab::Message>> { ) -> Element<'a, tab::Message> {
let menu_item = let find_key = |action: &Action| -> String {
|label: String, action: Action| menu::Item::Button(label, None, TabAction(action)); for (key_bind, key_action) in key_binds {
let menu_item_disabled = if action == key_action {
|label: String, action: Action| menu::Item::ButtonDisabled(label, None, TabAction(action)); return key_bind.to_string();
}
}
String::new()
};
fn key_style(theme: &cosmic::Theme) -> TextStyle {
let mut color = theme.cosmic().background(false).component.on;
color.alpha *= 0.75;
TextStyle {
color: Some(color.into()),
..Default::default()
}
}
fn disabled_style(theme: &cosmic::Theme) -> TextStyle {
let mut color = theme.cosmic().background(false).component.on;
color.alpha *= 0.5;
TextStyle {
color: Some(color.into()),
..Default::default()
}
}
let menu_item = |label, action| {
let key = find_key(&action);
menu_button!(
text::body(label),
space::horizontal(),
text::body(key).class(theme::Text::Custom(key_style))
)
.on_press(tab::Message::ContextAction(action))
};
let menu_item_disabled = |label, action: Action| {
let key = find_key(&action);
menu_button!(
text::body(label).class(theme::Text::Custom(disabled_style)),
space::horizontal(),
text::body(key).class(theme::Text::Custom(disabled_style))
)
};
// Allow paste when clipboard has data and we're in a location that supports it // Allow paste when clipboard has data and we're in a location that supports it
let can_paste = clipboard_paste_available && tab.location.supports_paste(); let can_paste = clipboard_paste_available && tab.location.supports_paste();
let (sort_name, sort_direction, _) = tab.sort_options(); let (sort_name, sort_direction, _) = tab.sort_options();
let sort_item = |label: String, variant| { let sort_item = |label, variant| {
let entry = menu::Entry::new(label, TabAction(Action::ToggleSort(variant))); let key = find_key(&Action::ToggleSort(variant));
menu::Item::Entry(if sort_name == variant { let leading: Element<'a, tab::Message> = if sort_name == variant {
let icon_name = if sort_direction { let icon_name = if sort_direction {
"view-sort-ascending-symbolic" "view-sort-ascending-symbolic"
} else { } else {
"view-sort-descending-symbolic" "view-sort-descending-symbolic"
}; };
entry.icon(widget::icon::from_name(icon_name).size(14).handle()) widget::icon::from_name(icon_name).size(14).into()
} else { } else {
entry.reserve_icon() space::horizontal().width(Length::Fixed(14.0)).into()
}) };
menu_button!(
leading,
space::horizontal().width(Length::Fixed(theme::spacing().space_xxs.into())),
text::body(label),
space::horizontal(),
text::body(key).class(theme::Text::Custom(key_style))
)
.on_press(tab::Message::ContextAction(Action::ToggleSort(variant)))
.into()
}; };
let mut selected_dir = 0; let mut selected_dir = 0;
@ -119,8 +174,8 @@ pub fn context_menu<'a>(
.iter() .iter()
.enumerate() .enumerate()
.filter(|(_, action)| action.matches_selection(selected, selected_dir)) .filter(|(_, action)| action.matches_selection(selected, selected_dir))
.map(|(i, action)| menu_item(action.name.clone(), Action::RunContextAction(i))) .map(|(i, action)| menu_item(action.name.clone(), Action::RunContextAction(i)).into())
.collect::<Vec<_>>() .collect::<Vec<Element<'a, tab::Message>>>()
}; };
// Parse the desktop entry if it is the only selection // Parse the desktop entry if it is the only selection
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
@ -135,7 +190,7 @@ pub fn context_menu<'a>(
} }
}); });
let mut children: Vec<menu::Item<TabAction, String>> = Vec::new(); let mut children: Vec<Element<_>> = Vec::new();
match (&tab.mode, &tab.location) { match (&tab.mode, &tab.location) {
( (
tab::Mode::App | tab::Mode::Desktop, tab::Mode::App | tab::Mode::Desktop,
@ -147,114 +202,108 @@ pub fn context_menu<'a>(
| Location::Network(_, _, Some(_)), | Location::Network(_, _, Some(_)),
) => { ) => {
if selected_trash_only { if selected_trash_only {
children.push(menu_item(fl!("open"), Action::Open)); children.push(menu_item(fl!("open"), Action::Open).into());
if !Trash::is_empty() { if !Trash::is_empty() {
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash)); children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into());
} }
} else if let Some(entry) = selected_desktop_entry { } else if let Some(_entry) = selected_desktop_entry {
children.push(menu_item(fl!("open"), Action::Open)); children.push(menu_item(fl!("open"), Action::Open).into());
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
{ {
children.extend( children.extend(_entry.desktop_actions.into_iter().enumerate().map(
entry |(i, action)| menu_item(action.name, Action::ExecEntryAction(i)).into(),
.desktop_actions ));
.into_iter()
.enumerate()
.map(|(i, action)| menu_item(action.name, Action::ExecEntryAction(i))),
);
} }
children.push(menu_item(fl!("edit-desktop-entry"), Action::EditDesktopEntry)); children
children.push(menu::Item::Divider); .push(menu_item(fl!("edit-desktop-entry"), Action::EditDesktopEntry).into());
children.push(menu_item(fl!("rename"), Action::Rename)); children.push(divider::horizontal::light().into());
children.push(menu_item(fl!("cut"), Action::Cut)); children.push(menu_item(fl!("rename"), Action::Rename).into());
children.push(menu_item(fl!("cut"), Action::Cut).into());
if modifiers.shift() && !modifiers.control() { if modifiers.shift() && !modifiers.control() {
children.push(menu_item(fl!("copy-path"), Action::CopyPath)); children.push(menu_item(fl!("copy-path"), Action::CopyPath).into());
} else { } else {
children.push(menu_item(fl!("copy"), Action::Copy)); children.push(menu_item(fl!("copy"), Action::Copy).into());
} }
// Should this simply bypass trash and remove the shortcut? // Should this simply bypass trash and remove the shortcut?
children.push(menu_item(fl!("move-to-trash"), Action::Delete)); children.push(menu_item(fl!("move-to-trash"), Action::Delete).into());
let action_items = context_action_items(selected, selected_dir); let action_items = context_action_items(selected, selected_dir);
if !action_items.is_empty() { if !action_items.is_empty() {
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
children.extend(action_items); children.extend(action_items);
} }
} else if selected > 0 { } else if selected > 0 {
if selected_dir == 1 && selected == 1 || selected_dir == 0 { if selected_dir == 1 && selected == 1 || selected_dir == 0 {
children.push(menu_item(fl!("open"), Action::Open)); children.push(menu_item(fl!("open"), Action::Open).into());
} }
if selected == 1 { if selected == 1 {
children.push(menu_item(fl!("menu-open-with"), Action::OpenWith)); children.push(menu_item(fl!("menu-open-with"), Action::OpenWith).into());
if selected_dir == 1 { if selected_dir == 1 {
children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal)); children
.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
} }
} }
if tab.location.is_recents() || matches!(tab.location, Location::Search(..)) { if tab.location.is_recents() || matches!(tab.location, Location::Search(..)) {
children.push(menu_item( children.push(
fl!("open-item-location"), menu_item(fl!("open-item-location"), Action::OpenItemLocation).into(),
Action::OpenItemLocation, );
));
} }
// All selected items are directories // All selected items are directories
if selected == selected_dir && matches!(tab.mode, tab::Mode::App) { if selected == selected_dir && matches!(tab.mode, tab::Mode::App) {
children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab)); children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab).into());
children.push(menu_item( children
fl!("open-in-new-window"), .push(menu_item(fl!("open-in-new-window"), Action::OpenInNewWindow).into());
Action::OpenInNewWindow,
));
} }
let action_items = context_action_items(selected, selected_dir); let action_items = context_action_items(selected, selected_dir);
if !action_items.is_empty() { if !action_items.is_empty() {
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
children.extend(action_items); children.extend(action_items);
} }
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
if selected_mount_point == 0 { if selected_mount_point == 0 {
children.push(menu_item(fl!("rename"), Action::Rename)); children.push(menu_item(fl!("rename"), Action::Rename).into());
children.push(menu_item(fl!("cut"), Action::Cut)); children.push(menu_item(fl!("cut"), Action::Cut).into());
} }
if modifiers.shift() && !modifiers.control() { if modifiers.shift() && !modifiers.control() {
children.push(menu_item(fl!("copy-path"), Action::CopyPath)); children.push(menu_item(fl!("copy-path"), Action::CopyPath).into());
} else { } else {
children.push(menu_item(fl!("copy"), Action::Copy)); children.push(menu_item(fl!("copy"), Action::Copy).into());
} }
if selected_mount_point == 0 { if selected_mount_point == 0 {
children.push(menu_item(fl!("move-to"), Action::MoveTo)); children.push(menu_item(fl!("move-to"), Action::MoveTo).into());
} }
children.push(menu_item(fl!("copy-to"), Action::CopyTo)); children.push(menu_item(fl!("copy-to"), Action::CopyTo).into());
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
let supported_archive_types = crate::archive::SUPPORTED_ARCHIVE_TYPES; let supported_archive_types = crate::archive::SUPPORTED_ARCHIVE_TYPES;
selected_types.retain(|t| supported_archive_types.iter().copied().all(|m| *t != m)); selected_types.retain(|t| supported_archive_types.iter().copied().all(|m| *t != m));
if selected_types.is_empty() { if selected_types.is_empty() {
children.push(menu_item(fl!("extract-here"), Action::ExtractHere)); children.push(menu_item(fl!("extract-here"), Action::ExtractHere).into());
children.push(menu_item(fl!("extract-to"), Action::ExtractTo)); children.push(menu_item(fl!("extract-to"), Action::ExtractTo).into());
} }
children.push(menu_item(fl!("compress"), Action::Compress)); children.push(menu_item(fl!("compress"), Action::Compress).into());
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
//TODO: Print? //TODO: Print?
children.push(menu_item(fl!("show-details"), Action::Preview)); children.push(menu_item(fl!("show-details"), Action::Preview).into());
if any_trash_item { if any_trash_item {
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
children.push(menu_item( children.push(
fl!("restore-from-trash"), menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into(),
Action::RestoreFromTrash, );
)); children.push(divider::horizontal::light().into());
children.push(menu::Item::Divider); children.push(menu_item(fl!("delete-permanently"), Action::Delete).into());
children.push(menu_item(fl!("delete-permanently"), Action::Delete));
} else { } else {
if matches!(tab.mode, tab::Mode::App) { if matches!(tab.mode, tab::Mode::App) {
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar)); children
.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into());
} }
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
if tab.location.is_recents() { if tab.location.is_recents() {
children.push(menu_item( children.push(
fl!("remove-from-recents"), menu_item(fl!("remove-from-recents"), Action::RemoveFromRecents).into(),
Action::RemoveFromRecents, );
)); children.push(divider::horizontal::light().into());
children.push(menu::Item::Divider);
} }
if selected_mount_point == 0 { if selected_mount_point == 0 {
if modifiers.shift() && !modifiers.control() { if modifiers.shift() && !modifiers.control() {
@ -263,59 +312,55 @@ pub fn context_menu<'a>(
.into(), .into(),
); );
} else { } else {
children.push(menu_item(fl!("move-to-trash"), Action::Delete)); children.push(menu_item(fl!("move-to-trash"), Action::Delete).into());
} }
} else if selected == 1 { } else if selected == 1 {
children.push(menu_item(fl!("eject"), Action::Eject)); children.push(menu_item(fl!("eject"), Action::Eject).into());
} }
} }
} else { } else {
//TODO: need better designs for menu with no selection //TODO: need better designs for menu with no selection
//TODO: have things like properties but they apply to the folder? //TODO: have things like properties but they apply to the folder?
if tab.location != Location::Recents { if tab.location != Location::Recents {
children.push(menu_item(fl!("new-folder"), Action::NewFolder)); children.push(menu_item(fl!("new-folder"), Action::NewFolder).into());
children.push(menu_item(fl!("new-file"), Action::NewFile)); children.push(menu_item(fl!("new-file"), Action::NewFile).into());
children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal)); children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
} }
if tab.mode.multiple() { if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll)); children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
} }
if can_paste { if can_paste {
children.push(menu_item(fl!("paste"), Action::Paste)); children.push(menu_item(fl!("paste"), Action::Paste).into());
} else { } else {
children.push(menu_item_disabled(fl!("paste"), Action::Paste)); children.push(menu_item_disabled(fl!("paste"), Action::Paste).into());
} }
//TODO: only show if cosmic-settings is found? //TODO: only show if cosmic-settings is found?
if matches!(tab.mode, tab::Mode::Desktop) { if matches!(tab.mode, tab::Mode::Desktop) {
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
children.push(menu_item( children.push(
fl!("change-wallpaper"), menu_item(fl!("change-wallpaper"), Action::CosmicSettingsWallpaper).into(),
Action::CosmicSettingsWallpaper, );
)); children.push(
children.push(menu_item( menu_item(fl!("desktop-appearance"), Action::CosmicSettingsDesktop).into(),
fl!("desktop-appearance"), );
Action::CosmicSettingsDesktop, children.push(
)); menu_item(fl!("display-settings"), Action::CosmicSettingsDisplays).into(),
children.push(menu_item( );
fl!("display-settings"),
Action::CosmicSettingsDisplays,
));
} }
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
// TODO: Nested menu // TODO: Nested menu
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name)); children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified)); children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size)); children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size));
if matches!(tab.location, Location::Desktop(..)) { if matches!(tab.location, Location::Desktop(..)) {
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
children.push(menu_item( children.push(
fl!("desktop-view-options"), menu_item(fl!("desktop-view-options"), Action::DesktopViewOptions).into(),
Action::DesktopViewOptions, );
));
} }
} }
} }
@ -330,25 +375,24 @@ pub fn context_menu<'a>(
) => { ) => {
if selected > 0 { if selected > 0 {
if selected_dir == 1 && selected == 1 || selected_dir == 0 { if selected_dir == 1 && selected == 1 || selected_dir == 0 {
children.push(menu_item(fl!("open"), Action::Open)); children.push(menu_item(fl!("open"), Action::Open).into());
} }
if matches!(tab.location, Location::Search(..)) || tab.location.is_recents() { if matches!(tab.location, Location::Search(..)) || tab.location.is_recents() {
children.push(menu_item( children.push(
fl!("open-item-location"), menu_item(fl!("open-item-location"), Action::OpenItemLocation).into(),
Action::OpenItemLocation, );
));
} }
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
children.push(menu_item(fl!("show-details"), Action::Preview)); children.push(menu_item(fl!("show-details"), Action::Preview).into());
} else { } else {
if dialog_kind.save() { if dialog_kind.save() {
children.push(menu_item(fl!("new-folder"), Action::NewFolder)); children.push(menu_item(fl!("new-folder"), Action::NewFolder).into());
} }
if tab.mode.multiple() { if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll)); children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
} }
if !children.is_empty() { if !children.is_empty() {
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
} }
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name)); children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified)); children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
@ -358,14 +402,14 @@ pub fn context_menu<'a>(
(_, Location::Network(..)) => { (_, Location::Network(..)) => {
if selected > 0 { if selected > 0 {
if selected_dir == 1 && selected == 1 || selected_dir == 0 { if selected_dir == 1 && selected == 1 || selected_dir == 0 {
children.push(menu_item(fl!("open"), Action::Open)); children.push(menu_item(fl!("open"), Action::Open).into());
} }
} else { } else {
if tab.mode.multiple() { if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll)); children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
} }
if !children.is_empty() { if !children.is_empty() {
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
} }
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name)); children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified)); children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
@ -374,26 +418,24 @@ pub fn context_menu<'a>(
} }
(_, Location::Trash | Location::Search(SearchLocation::Trash, ..)) => { (_, Location::Trash | Location::Search(SearchLocation::Trash, ..)) => {
if tab.mode.multiple() { if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll)); children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
} }
if !Trash::is_empty() { if !Trash::is_empty() {
if !children.is_empty() { if !children.is_empty() {
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
} }
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash)); children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into());
} }
if !children.is_empty() { if !children.is_empty() {
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
} }
if selected > 0 { if selected > 0 {
children.push(menu_item(fl!("show-details"), Action::Preview)); children.push(menu_item(fl!("show-details"), Action::Preview).into());
children.push(menu::Item::Divider); children.push(divider::horizontal::light().into());
children.push(menu_item( children
fl!("restore-from-trash"), .push(menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into());
Action::RestoreFromTrash, children.push(divider::horizontal::light().into());
)); children.push(menu_item(fl!("delete-permanently"), Action::Delete).into());
children.push(menu::Item::Divider);
children.push(menu_item(fl!("delete-permanently"), Action::Delete));
} else { } else {
// TODO: Nested menu // TODO: Nested menu
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name)); children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
@ -403,11 +445,26 @@ pub fn context_menu<'a>(
} }
} }
let key_binds: HashMap<KeyBind, TabAction> = key_binds container(column::with_children(children))
.iter() .padding(1)
.map(|(key_bind, action)| (key_bind.clone(), TabAction(*action))) //TODO: move style to libcosmic
.collect(); .style(|theme| {
menu::items(&key_binds, children) let cosmic = theme.cosmic();
let component = &cosmic.background(false).component;
container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),
border: Border {
radius: cosmic.radius_s().map(|x| x + 1.0).into(),
width: 1.0,
color: component.divider.into(),
},
..Default::default()
}
})
.width(Length::Fixed(360.0))
.into()
} }
pub fn dialog_menu( pub fn dialog_menu(
@ -756,33 +813,50 @@ pub fn menu_bar<'a>(
) )
} }
pub fn location_context_menu(ancestor_index: usize) -> Vec<menu::Tree<tab::Message>> { pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Message> {
//TODO: only add some of these when in App mode //TODO: only add some of these when in App mode
menu::items( let children = [
&HashMap::new(), menu_button!(text::body(fl!("open-in-new-tab")))
vec![ .on_press(tab::Message::LocationMenuAction(
menu::Item::Button(
fl!("open-in-new-tab"),
None,
LocationMenuAction::OpenInNewTab(ancestor_index), LocationMenuAction::OpenInNewTab(ancestor_index),
), ))
menu::Item::Button( .into(),
fl!("open-in-new-window"), menu_button!(text::body(fl!("open-in-new-window")))
None, .on_press(tab::Message::LocationMenuAction(
LocationMenuAction::OpenInNewWindow(ancestor_index), LocationMenuAction::OpenInNewWindow(ancestor_index),
), ))
menu::Item::Divider, .into(),
menu::Item::Button( divider::horizontal::light().into(),
fl!("show-details"), menu_button!(text::body(fl!("show-details")))
None, .on_press(tab::Message::LocationMenuAction(
LocationMenuAction::Preview(ancestor_index), LocationMenuAction::Preview(ancestor_index),
), ))
menu::Item::Divider, .into(),
menu::Item::Button( divider::horizontal::light().into(),
fl!("add-to-sidebar"), menu_button!(text::body(fl!("add-to-sidebar")))
None, .on_press(tab::Message::LocationMenuAction(
LocationMenuAction::AddToSidebar(ancestor_index), LocationMenuAction::AddToSidebar(ancestor_index),
), ))
], .into(),
) ];
container(column::with_children(children))
.padding(1)
.style(|theme| {
let cosmic = theme.cosmic();
let component = &cosmic.background(false).component;
container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),
border: Border {
radius: cosmic.radius_s().map(|x| x + 1.0).into(),
width: 1.0,
color: component.divider.into(),
},
..Default::default()
}
})
.width(Length::Fixed(360.0))
.into()
} }

View file

@ -18,15 +18,6 @@ use crate::tab::{self, ChecksumState, DirSize, ItemMetadata, ItemThumbnail, Loca
const TARGET_URI_ATTRIBUTE: &str = "standard::target-uri"; const TARGET_URI_ATTRIBUTE: &str = "standard::target-uri";
// Attributes requested when listing a network directory.
const SCAN_ATTRIBUTES: &str = "standard::name,\
standard::display-name,\
standard::type,\
standard::size,\
standard::icon,\
standard::is-hidden,\
time::modified";
fn resolve_uri(uri: &str) -> (String, gio::File) { fn resolve_uri(uri: &str) -> (String, gio::File) {
let file = gio::File::for_uri(uri); let file = gio::File::for_uri(uri);
// Resolve the target-uri if it exists // Resolve the target-uri if it exists
@ -128,27 +119,9 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
Box::from([]) Box::from([])
}; };
// `filesystem::remote` belongs to the filesystem namespace, which `enumerate_children`
// never fills in, so it has to be queried once for the directory being listed.
let remote = file
.query_filesystem_info(
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
gio::Cancellable::NONE,
)
.map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
.unwrap_or_else(|err| {
log::warn!("failed to get GIO filesystem info for {uri}: {err}");
// Assume remote, so per-entry work is skipped rather than retried
true
});
let mut items = Vec::new(); let mut items = Vec::new();
for info_res in file for info_res in file
.enumerate_children( .enumerate_children("*", gio::FileQueryInfoFlags::NONE, gio::Cancellable::NONE)
SCAN_ATTRIBUTES,
gio::FileQueryInfoFlags::NONE,
gio::Cancellable::NONE,
)
.map_err(err_str)? .map_err(err_str)?
{ {
let info = info_res.map_err(err_str)?; let info = info_res.map_err(err_str)?;
@ -160,17 +133,13 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
//TODO: what is the best way to resolve shortcuts? //TODO: what is the best way to resolve shortcuts?
let location = Location::Network(uri, display_name.clone(), file.child(&name).path()); let location = Location::Network(uri, display_name.clone(), file.child(&name).path());
let metadata = if force_dir { let metadata = if !force_dir && !info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE) {
ItemMetadata::SimpleDir { entries: 0 }
} else {
let mtime = info.attribute_uint64(gio::FILE_ATTRIBUTE_TIME_MODIFIED); let mtime = info.attribute_uint64(gio::FILE_ATTRIBUTE_TIME_MODIFIED);
let is_dir = matches!(info.file_type(), gio::FileType::Directory); let is_dir = matches!(info.file_type(), gio::FileType::Directory);
let size_opt = (!is_dir).then_some(info.size() as u64); let size_opt = (!is_dir).then_some(info.size() as u64);
let mut children_opt = None; let mut children_opt = None;
// Counting children costs a directory listing per entry, which is far too if is_dir {
// expensive on a remote filesystem
if is_dir && !remote {
if let Some(path) = file.child(&name).path() { if let Some(path) = file.child(&name).path() {
//TODO: calculate children in the background (and make it cancellable?) //TODO: calculate children in the background (and make it cancellable?)
match std::fs::read_dir(&path) { match std::fs::read_dir(&path) {
@ -190,8 +159,9 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
mtime, mtime,
size_opt, size_opt,
children_opt, children_opt,
is_dir,
} }
} else {
ItemMetadata::SimpleDir { entries: 0 }
}; };
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = { let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = {
@ -689,7 +659,7 @@ impl Mounter for Gvfs {
) )
} }
fn network_drive(&self, uri: String) -> Task<bool> { fn network_drive(&self, uri: String) -> Task<()> {
let command_tx = self.command_tx.clone(); let command_tx = self.command_tx.clone();
Task::perform( Task::perform(
async move { async move {
@ -698,15 +668,9 @@ impl Mounter for Gvfs {
command_tx.send(Cmd::NetworkDrive(uri, res_tx)).unwrap(); command_tx.send(Cmd::NetworkDrive(uri, res_tx)).unwrap();
res_rx.await res_rx.await
}, },
|result| match result { |x| {
Ok(Ok(())) => true, if let Err(err) = x {
Ok(Err(err)) => {
log::error!("{err:?}"); log::error!("{err:?}");
false
}
Err(err) => {
log::error!("{err:?}");
false
} }
}, },
) )

View file

@ -115,7 +115,7 @@ pub trait Mounter: Send + Sync {
fn items(&self, sizes: IconSizes) -> Option<MounterItems>; fn items(&self, sizes: IconSizes) -> Option<MounterItems>;
//TODO: send result //TODO: send result
fn mount(&self, item: MounterItem) -> Task<()>; fn mount(&self, item: MounterItem) -> Task<()>;
fn network_drive(&self, uri: String) -> Task<bool>; fn network_drive(&self, uri: String) -> Task<()>;
fn network_scan(&self, uri: &str, sizes: IconSizes) -> Option<Result<Vec<tab::Item>, String>>; fn network_scan(&self, uri: &str, sizes: IconSizes) -> Option<Result<Vec<tab::Item>, String>>;
fn dir_info(&self, uri: &str) -> Option<(String, String, Option<PathBuf>)>; fn dir_info(&self, uri: &str) -> Option<(String, String, Option<PathBuf>)>;
fn unmount(&self, item: MounterItem) -> Task<()>; fn unmount(&self, item: MounterItem) -> Task<()>;

View file

@ -214,9 +214,8 @@ impl<'a, Message, F> OnAutoScroll<'a, Message> for F where F: Fn(Option<f32>) ->
pub trait OnMouseButton<'a, Message>: Fn(Option<Point>) -> Message + 'a {} pub trait OnMouseButton<'a, Message>: Fn(Option<Point>) -> Message + 'a {}
impl<'a, Message, F> OnMouseButton<'a, Message> for F where F: Fn(Option<Point>) -> Message + 'a {} impl<'a, Message, F> OnMouseButton<'a, Message> for F where F: Fn(Option<Point>) -> Message + 'a {}
pub trait OnDrag<'a, Message>: Fn(Option<Rectangle>) -> Option<Message> + 'a {} pub trait OnDrag<'a, Message>: Fn(Option<Rectangle>) -> Message + 'a {}
impl<'a, Message, F> OnDrag<'a, Message> for F where F: Fn(Option<Rectangle>) -> Option<Message> + 'a impl<'a, Message, F> OnDrag<'a, Message> for F where F: Fn(Option<Rectangle>) -> Message + 'a {}
{}
pub trait OnResize<'a, Message>: Fn(Rectangle) -> Message + 'a {} pub trait OnResize<'a, Message>: Fn(Rectangle) -> Message + 'a {}
impl<'a, Message, F> OnResize<'a, Message> for F where F: Fn(Rectangle) -> Message + 'a {} impl<'a, Message, F> OnResize<'a, Message> for F where F: Fn(Rectangle) -> Message + 'a {}
@ -564,16 +563,6 @@ fn update<Message: Clone>(
state.last_position = position_in; state.last_position = position_in;
} }
if state.drag_initiated.is_some()
&& matches!(
event,
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. })
)
{
shell.request_redraw();
}
if let Event::Mouse(mouse::Event::CursorMoved { position }) = event { if let Event::Mouse(mouse::Event::CursorMoved { position }) = event {
let virtual_position = Point::new( let virtual_position = Point::new(
viewport.x - layout_bounds.x + position.x, viewport.x - layout_bounds.x + position.x,
@ -650,7 +639,6 @@ fn update<Message: Clone>(
{ {
state.drag_initiated = None; state.drag_initiated = None;
state.prev_click = None; state.prev_click = None;
shell.request_redraw();
if let Some(message) = widget.on_drag_end.as_ref() { if let Some(message) = widget.on_drag_end.as_ref() {
shell.publish(message(cursor.position_in(layout_bounds))); shell.publish(message(cursor.position_in(layout_bounds)));
} }
@ -670,9 +658,7 @@ fn update<Message: Clone>(
state.prev_click = None; state.prev_click = None;
return; return;
} }
if state.drag_initiated.take().is_some() { state.drag_initiated = None;
shell.request_redraw();
}
if let Some(message) = widget.on_release.as_ref() { if let Some(message) = widget.on_release.as_ref() {
shell.publish(message(cursor.position_in(layout_bounds))); shell.publish(message(cursor.position_in(layout_bounds)));
@ -799,13 +785,12 @@ fn update<Message: Clone>(
} }
if let Some((message, drag_rect)) = widget.on_drag.as_ref().zip(state.drag_rect(cursor)) { if let Some((message, drag_rect)) = widget.on_drag.as_ref().zip(state.drag_rect(cursor)) {
let rect_opt = drag_rect.intersection(&layout_bounds).map(|mut rect| { shell.publish(message(drag_rect.intersection(&layout_bounds).map(
rect.x -= layout_bounds.x; |mut rect| {
rect.y -= layout_bounds.y; rect.x -= layout_bounds.x;
rect rect.y -= layout_bounds.y;
}); rect
if let Some(message) = message(rect_opt) { },
shell.publish(message); )));
}
} }
} }

View file

@ -454,7 +454,7 @@ impl Op {
} }
} }
let (from_file_open_result, metadata, to_file_open_result) = cosmic::iced::futures::join!( let (from_file, metadata, to_file) = cosmic::iced::futures::join!(
async { async {
compio::fs::OpenOptions::new() compio::fs::OpenOptions::new()
.read(true) .read(true)
@ -470,30 +470,12 @@ impl Op {
.write(true) .write(true)
.open(&self.to) .open(&self.to)
.await .await
.with_context(|| format!("failed to open {} for writing", self.to.display()))
} }
); );
let from_file = from_file_open_result?; let from_file = from_file?;
let mut to_file = to_file?;
let mut to_file = match to_file_open_result {
Ok(file) => file,
#[cfg(not(feature = "gvfs"))]
Err(why) => {
_ = from_file.close().await;
return Err(why)
.with_context(|| format!("failed to open {} for writing", self.to.display()))
.map_err(Into::into);
}
#[cfg(feature = "gvfs")]
Err(_why) => {
_ = from_file.close().await;
return self
.gio_file_copy(ctx, progress)
.await
.map(|()| true)
.map_err(Into::into);
}
};
progress.total_bytes = metadata.as_ref().map(|m| m.len()); progress.total_bytes = metadata.as_ref().map(|m| m.len());
(ctx.on_progress)(self, &progress); (ctx.on_progress)(self, &progress);

View file

@ -181,9 +181,7 @@ fn button_appearance(
appearance.icon_color = Some(Color::from(cosmic.on_bg_component_color())); appearance.icon_color = Some(Color::from(cosmic.on_bg_component_color()));
appearance.text_color = Some(Color::from(cosmic.on_bg_component_color())); appearance.text_color = Some(Color::from(cosmic.on_bg_component_color()));
if cut { if cut {
appearance.text_color = Some(Color::from( appearance.text_color = Some(Color::from(cosmic.background(false).component.on_disabled));
cosmic.background(theme.transparent).component.on_disabled,
));
} else { } else {
appearance.text_color = Some(Color::from(cosmic.on_bg_component_color())); appearance.text_color = Some(Color::from(cosmic.on_bg_component_color()));
} }
@ -194,16 +192,12 @@ fn button_appearance(
appearance.background = Some(Color::from(cosmic.bg_color()).into()); appearance.background = Some(Color::from(cosmic.bg_color()).into());
appearance.icon_color = Some(Color::from(cosmic.on_bg_color())); appearance.icon_color = Some(Color::from(cosmic.on_bg_color()));
if cut { if cut {
appearance.text_color = Some(Color::from( appearance.text_color = Some(Color::from(cosmic.background(false).component.disabled));
cosmic.background(theme.transparent).component.disabled,
));
} else { } else {
appearance.text_color = Some(Color::from(cosmic.on_bg_color())); appearance.text_color = Some(Color::from(cosmic.on_bg_color()));
} }
} else if cut { } else if cut {
appearance.text_color = Some(Color::from( appearance.text_color = Some(Color::from(cosmic.background(false).component.on_disabled));
cosmic.background(theme.transparent).component.on_disabled,
));
} }
if focused && accent { if focused && accent {
appearance.outline_width = 1.0; appearance.outline_width = 1.0;
@ -458,9 +452,7 @@ impl<'a> FormatTime<'a> {
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 Ok(zoned) = jiff::Zoned::try_from(self.time) else { let zoned = jiff::Zoned::try_from(self.time).unwrap();
return Ok(());
};
let now = jiff::Zoned::now(); let now = jiff::Zoned::now();
let icu_datetime = DateTime::convert_from(zoned.datetime()); let icu_datetime = DateTime::convert_from(zoned.datetime());
if zoned.date() == now.date() { if zoned.date() == now.date() {
@ -663,41 +655,6 @@ fn display_name_for_file(path: &Path, name: &str, get_from_gvfs: bool, is_deskto
Item::display_name(name) Item::display_name(name)
} }
// Whether a dir lives on a remote filesystem, according to GIO.
#[cfg(feature = "gvfs")]
fn gvfs_dir_is_remote(dir: &Path) -> bool {
static REMOTE_CACHE: LazyLock<RwLock<FxHashMap<PathBuf, bool>>> =
LazyLock::new(|| RwLock::new(FxHashMap::default()));
if let Some(remote) = REMOTE_CACHE.read().unwrap().get(dir) {
return *remote;
}
let remote = match gio::prelude::FileExt::query_filesystem_info(
&gio::File::for_path(dir),
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
gio::Cancellable::NONE,
) {
Ok(info) => info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE),
Err(err) => {
log::warn!(
"failed to get GIO filesystem info for {}: {}",
dir.display(),
err
);
//Assume remote so that the "expensive tasks" are rather skipped then actually executed.
true
}
};
REMOTE_CACHE
.write()
.unwrap()
.insert(dir.to_path_buf(), remote);
remote
}
#[cfg(feature = "gvfs")] #[cfg(feature = "gvfs")]
pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconSizes) -> Item { pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconSizes) -> Item {
let file_name = file_info let file_name = file_info
@ -705,7 +662,7 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS
.unwrap_or_default(); .unwrap_or_default();
let mtime = file_info.attribute_uint64(gio::FILE_ATTRIBUTE_TIME_MODIFIED); let mtime = file_info.attribute_uint64(gio::FILE_ATTRIBUTE_TIME_MODIFIED);
let mut is_desktop = false; let mut is_desktop = false;
let remote = path.parent().is_none_or(gvfs_dir_is_remote); let remote = file_info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE);
let is_dir = matches!(file_info.file_type(), gio::FileType::Directory); let is_dir = matches!(file_info.file_type(), gio::FileType::Directory);
let size_opt = (!is_dir).then_some(file_info.size() as u64); let size_opt = (!is_dir).then_some(file_info.size() as u64);
@ -766,7 +723,6 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS
mtime, mtime,
size_opt, size_opt,
children_opt, children_opt,
is_dir,
}, },
hidden, hidden,
image_dimensions: (!remote && mime.type_() == mime::IMAGE) image_dimensions: (!remote && mime.type_() == mime::IMAGE)
@ -821,7 +777,23 @@ pub fn item_from_entry(
#[cfg(feature = "gvfs")] #[cfg(feature = "gvfs")]
FsKind::Gvfs => { FsKind::Gvfs => {
is_gvfs = true; is_gvfs = true;
path.parent().is_none_or(gvfs_dir_is_remote) let file = gio::File::for_path(&path);
match gio::prelude::FileExt::query_filesystem_info(
&file,
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
gio::Cancellable::NONE,
) {
Ok(info) => info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE),
Err(err) => {
log::warn!(
"failed to get GIO filesystem info for {}: {}",
path.display(),
err
);
true
}
}
} }
#[cfg(not(feature = "gvfs"))] #[cfg(not(feature = "gvfs"))]
FsKind::Gvfs => { FsKind::Gvfs => {
@ -973,24 +945,11 @@ fn item_from_trash_child(
metadata: fs::Metadata, metadata: fs::Metadata,
sizes: IconSizes, sizes: IconSizes,
) -> Option<Item> { ) -> Option<Item> {
let Some(original_path) = crate::trash::original_path_for_trash_child(&path) else { let original_path = crate::trash::original_path_for_trash_child(&path)?;
log::warn!(
"failed to resolve original path for trash item {}, skipping entry",
path.display()
);
return None;
};
let Some(original_parent) = original_path.parent() else {
log::warn!(
"trash item {} has no original parent, skipping entry",
path.display()
);
return None;
};
let entry = trash::TrashItem { let entry = trash::TrashItem {
id: path.as_os_str().to_os_string(), id: path.as_os_str().to_os_string(),
name: std::ffi::OsString::from(&name), name: std::ffi::OsString::from(&name),
original_parent: original_parent.to_path_buf(), original_parent: original_path.parent()?.to_path_buf(),
time_deleted: 0, time_deleted: 0,
}; };
let size = if metadata.is_dir() { let size = if metadata.is_dir() {
@ -1046,6 +1005,7 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
// gio crate expects a comma delimited string // gio crate expects a comma delimited string
let attr_string = [ let attr_string = [
gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME.as_str(), gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME.as_str(),
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE.as_str(),
gio::FILE_ATTRIBUTE_TIME_MODIFIED.as_str(), gio::FILE_ATTRIBUTE_TIME_MODIFIED.as_str(),
gio::FILE_ATTRIBUTE_STANDARD_SIZE.as_str(), gio::FILE_ATTRIBUTE_STANDARD_SIZE.as_str(),
gio::FILE_ATTRIBUTE_STANDARD_TYPE.as_str(), gio::FILE_ATTRIBUTE_STANDARD_TYPE.as_str(),
@ -1082,7 +1042,6 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
if !remote_scannable { if !remote_scannable {
match fs::read_dir(tab_path) { match fs::read_dir(tab_path) {
Ok(entries) => { Ok(entries) => {
let trash = crate::trash::is_trash_path(tab_path);
items = entries items = entries
.filter_map(|entry_res| { .filter_map(|entry_res| {
let entry = entry_res let entry = entry_res
@ -1123,6 +1082,7 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
}) })
.ok()?; .ok()?;
let trash = crate::trash::is_trash_path(tab_path);
if trash { if trash {
item_from_trash_child(path, name, metadata, sizes) item_from_trash_child(path, name, metadata, sizes)
} else { } else {
@ -1757,11 +1717,11 @@ impl fmt::Debug for TaskWrapper {
#[derive(Debug)] #[derive(Debug)]
pub enum Command { pub enum Command {
Action(Action), Action(Action),
Surface(cosmic::surface::Action<Message>),
AddNetworkDrive, AddNetworkDrive,
AddToSidebar(PathBuf), AddToSidebar(PathBuf),
AutoScroll(Option<f32>), AutoScroll(Option<f32>),
ChangeLocation(String, Location, Option<Vec<PathBuf>>), ChangeLocation(String, Location, Option<Vec<PathBuf>>),
ContextMenu(Option<Point>, Option<window::Id>),
Delete(Vec<PathBuf>), Delete(Vec<PathBuf>),
DropFiles(PathBuf, ClipboardPaste), DropFiles(PathBuf, ClipboardPaste),
ClearRecents, ClearRecents,
@ -1792,9 +1752,9 @@ pub enum Message {
ClickRelease(Option<usize>), ClickRelease(Option<usize>),
Config(TabConfig), Config(TabConfig),
ContextAction(Action), ContextAction(Action),
RightClickBackground, ContextMenu(Option<Point>, Option<window::Id>),
Surface(cosmic::surface::Action<Message>), LocationContextMenuPoint(Option<Point>),
LocationContextMenuIndex(Option<usize>), LocationContextMenuIndex(Option<Point>, Option<usize>),
LocationMenuAction(LocationMenuAction), LocationMenuAction(LocationMenuAction),
Drag(Option<Rectangle>), Drag(Option<Rectangle>),
DragEnd, DragEnd,
@ -1854,8 +1814,6 @@ pub enum Message {
HighlightDeactivate(usize), HighlightDeactivate(usize),
HighlightActivate(usize), HighlightActivate(usize),
DirectorySize(PathBuf, DirSize), DirectorySize(PathBuf, DirSize),
#[cfg(feature = "gvfs")]
DirectoryChildren(PathBuf, usize),
Checksums(PathBuf, ChecksumState), Checksums(PathBuf, ChecksumState),
CalculateChecksums(PathBuf), CalculateChecksums(PathBuf),
CopyChecksum(String), CopyChecksum(String),
@ -1924,7 +1882,6 @@ pub enum ItemMetadata {
mtime: u64, mtime: u64,
size_opt: Option<u64>, size_opt: Option<u64>,
children_opt: Option<usize>, children_opt: Option<usize>,
is_dir: bool,
}, },
} }
@ -1939,7 +1896,7 @@ impl ItemMetadata {
Self::SimpleDir { .. } => true, Self::SimpleDir { .. } => true,
Self::SimpleFile { .. } => false, Self::SimpleFile { .. } => false,
#[cfg(feature = "gvfs")] #[cfg(feature = "gvfs")]
Self::GvfsPath { is_dir, .. } => *is_dir, Self::GvfsPath { children_opt, .. } => children_opt.is_some(),
} }
} }
@ -1948,7 +1905,7 @@ impl ItemMetadata {
Self::Path { metadata, .. } => metadata.modified().ok(), Self::Path { metadata, .. } => metadata.modified().ok(),
#[cfg(feature = "gvfs")] #[cfg(feature = "gvfs")]
Self::GvfsPath { mtime, .. } => { Self::GvfsPath { mtime, .. } => {
SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(*mtime)) Some(SystemTime::UNIX_EPOCH + Duration::from_secs(*mtime))
} }
_ => None, _ => None,
} }
@ -2370,7 +2327,6 @@ impl Item {
) -> widget::Text<'a, cosmic::Theme, cosmic::Renderer> { ) -> widget::Text<'a, cosmic::Theme, cosmic::Renderer> {
widget::text::body(name) widget::text::body(name)
.wrapping(text::Wrapping::WordOrGlyph) .wrapping(text::Wrapping::WordOrGlyph)
.align_x(text::Alignment::Center)
.ellipsize(text::Ellipsize::Middle(text::EllipsizeHeightLimit::Lines( .ellipsize(text::Ellipsize::Middle(text::EllipsizeHeightLimit::Lines(
3, 3,
))) )))
@ -2426,8 +2382,10 @@ impl Item {
widget::image(handle.clone()).into() widget::image(handle.clone()).into()
} }
ItemThumbnail::Svg(handle) => widget::svg(handle.clone()).into(), ItemThumbnail::Svg(handle) => widget::svg(handle.clone()).into(),
ItemThumbnail::Text(content) => widget::text_editor::text_editor(content) ItemThumbnail::Text(content) => widget::text_editor(content)
.style(text_editor_class) .class(cosmic::theme::iced::TextEditor::Custom(Box::new(
text_editor_class,
)))
.width(THUMBNAIL_SIZE as f32) .width(THUMBNAIL_SIZE as f32)
.height(Length::Fixed(THUMBNAIL_SIZE as f32)) .height(Length::Fixed(THUMBNAIL_SIZE as f32))
.padding(spacing.space_xxs) .padding(spacing.space_xxs)
@ -2478,7 +2436,7 @@ impl Item {
); );
let mut details = widget::column::with_capacity(8).spacing(space_xxxs); let mut details = widget::column::with_capacity(8).spacing(space_xxxs);
details = details.push(widget::selectable_text::heading(self.name.clone())); details = details.push(widget::text::heading(self.name.clone()));
details = details.push(widget::text::body(fl!( details = details.push(widget::text::body(fl!(
"type", "type",
mime = self.mime.to_string() mime = self.mime.to_string()
@ -2535,21 +2493,21 @@ impl Item {
let time_formatter = time_formatter(military_time); let time_formatter = time_formatter(military_time);
if let Ok(time) = metadata.created() { if let Ok(time) = metadata.created() {
details = details.push(widget::selectable_text::body(fl!( details = details.push(widget::text::body(fl!(
"item-created", "item-created",
created = format_time(time, &date_time_formatter, &time_formatter).to_string() created = format_time(time, &date_time_formatter, &time_formatter).to_string()
))); )));
} }
if let Ok(time) = metadata.modified() { if let Ok(time) = metadata.modified() {
details = details.push(widget::selectable_text::body(fl!( details = details.push(widget::text::body(fl!(
"item-modified", "item-modified",
modified = format_time(time, &date_time_formatter, &time_formatter).to_string() modified = format_time(time, &date_time_formatter, &time_formatter).to_string()
))); )));
} }
if let Ok(time) = metadata.accessed() { if let Ok(time) = metadata.accessed() {
details = details.push(widget::selectable_text::body(fl!( details = details.push(widget::text::body(fl!(
"item-accessed", "item-accessed",
accessed = format_time(time, &date_time_formatter, &time_formatter).to_string() accessed = format_time(time, &date_time_formatter, &time_formatter).to_string()
))); )));
@ -2820,8 +2778,9 @@ pub struct Tab {
pub location: Location, pub location: Location,
pub location_ancestors: Vec<(Location, String)>, pub location_ancestors: Vec<(Location, String)>,
pub location_title: String, pub location_title: String,
/// Breadcrumb whose context menu is open, drawn as active while it shows pub location_context_menu_point: Option<Point>,
pub location_context_menu_index: Option<usize>, pub location_context_menu_index: Option<usize>,
pub context_menu: Option<Point>,
pub mode: Mode, pub mode: Mode,
pub scroll_opt: Option<AbsoluteOffset>, pub scroll_opt: Option<AbsoluteOffset>,
pub size_opt: Cell<Option<Size>>, pub size_opt: Cell<Option<Size>>,
@ -2849,6 +2808,7 @@ pub struct Tab {
search_context: Option<SearchContext>, search_context: Option<SearchContext>,
date_time_formatter: DateTimeFormatter<fieldsets::YMDT>, date_time_formatter: DateTimeFormatter<fieldsets::YMDT>,
time_formatter: DateTimeFormatter<fieldsets::T>, time_formatter: DateTimeFormatter<fieldsets::T>,
watch_drag: bool,
window_id: Option<window::Id>, window_id: Option<window::Id>,
large_image_manager: LargeImageManager, large_image_manager: LargeImageManager,
} }
@ -2965,6 +2925,8 @@ impl Tab {
location, location,
location_ancestors, location_ancestors,
location_title, location_title,
context_menu: None,
location_context_menu_point: None,
location_context_menu_index: None, location_context_menu_index: None,
mode: Mode::App, mode: Mode::App,
scroll_opt: None, scroll_opt: None,
@ -2993,6 +2955,7 @@ impl Tab {
search_context: None, search_context: None,
date_time_formatter: date_time_formatter(config.military_time), date_time_formatter: date_time_formatter(config.military_time),
time_formatter: time_formatter(config.military_time), time_formatter: time_formatter(config.military_time),
watch_drag: true,
window_id, window_id,
large_image_manager: LargeImageManager::new(), large_image_manager: LargeImageManager::new(),
} }
@ -3505,6 +3468,7 @@ impl Tab {
self.location = location.normalize(); self.location = location.normalize();
self.location_ancestors = self.location.ancestors(); self.location_ancestors = self.location.ancestors();
self.location_title = self.location.title(); self.location_title = self.location.title();
self.context_menu = None;
self.edit_location = None; self.edit_location = None;
self.items_opt = None; self.items_opt = None;
//TODO: remember scroll by location? //TODO: remember scroll by location?
@ -3549,6 +3513,7 @@ impl Tab {
let mut history_i_opt = None; let mut history_i_opt = None;
let mod_ctrl = modifiers.contains(Modifiers::CTRL) && self.mode.multiple(); let mod_ctrl = modifiers.contains(Modifiers::CTRL) && self.mode.multiple();
let mod_shift = modifiers.contains(Modifiers::SHIFT) && self.mode.multiple(); let mod_shift = modifiers.contains(Modifiers::SHIFT) && self.mode.multiple();
let last_context_menu = self.context_menu;
match message { match message {
Message::AddNetworkDrive => { Message::AddNetworkDrive => {
commands.push(Command::AddNetworkDrive); commands.push(Command::AddNetworkDrive);
@ -3583,6 +3548,8 @@ impl Tab {
} }
if click_i_opt != self.clicked.take() { if click_i_opt != self.clicked.take() {
self.context_menu = None;
self.location_context_menu_index = None;
if let Some(ref mut items) = self.items_opt { if let Some(ref mut items) = self.items_opt {
for (i, item) in items.iter_mut().enumerate() { for (i, item) in items.iter_mut().enumerate() {
if mod_ctrl { if mod_ctrl {
@ -3599,6 +3566,7 @@ impl Tab {
} }
Message::DragEnd => { Message::DragEnd => {
self.clicked = None; self.clicked = None;
self.watch_drag = true;
} }
Message::DoubleClick(click_i_opt) => { Message::DoubleClick(click_i_opt) => {
if let Some(clicked_item) = self if let Some(clicked_item) = self
@ -3623,7 +3591,9 @@ impl Tab {
} }
Message::Click(click_i_opt) => { Message::Click(click_i_opt) => {
self.selected_clicked = false; self.selected_clicked = false;
self.context_menu = None;
self.edit_location = None; self.edit_location = None;
self.location_context_menu_index = None;
if click_i_opt.is_none() { if click_i_opt.is_none() {
self.clicked = click_i_opt; self.clicked = click_i_opt;
} }
@ -3765,16 +3735,24 @@ impl Tab {
} }
} }
Message::ContextAction(action) => { Message::ContextAction(action) => {
// Close context menu
self.context_menu = None;
commands.push(Command::Action(action)); commands.push(Command::Action(action));
} }
Message::RunContextAction(action) => { Message::RunContextAction(action) => {
self.context_menu = None;
commands.push(Command::RunContextAction(action)); commands.push(Command::RunContextAction(action));
} }
Message::RightClickBackground => { Message::ContextMenu(point_opt, _) => {
self.edit_location = None; self.edit_location = None;
self.context_menu = point_opt;
self.location_context_menu_index = None;
//TODO: hack for clearing selecting when right clicking empty space //TODO: hack for clearing selecting when right clicking empty space
if self.last_right_click.take().is_none() if self.context_menu.is_some()
&& self.last_right_click.take().is_none()
&& let Some(ref mut items) = self.items_opt && let Some(ref mut items) = self.items_opt
{ {
for item in items.iter_mut() { for item in items.iter_mut() {
@ -3782,13 +3760,17 @@ impl Tab {
} }
} }
} }
Message::Surface(action) => { Message::LocationContextMenuPoint(point_opt) => {
commands.push(Command::Surface(action)); self.context_menu = None;
self.location_context_menu_point = point_opt;
} }
Message::LocationContextMenuIndex(index) => { Message::LocationContextMenuIndex(p, index_opt) => {
self.location_context_menu_index = index; self.context_menu = None;
self.location_context_menu_point = p;
self.location_context_menu_index = index_opt;
} }
Message::LocationMenuAction(action) => { Message::LocationMenuAction(action) => {
self.location_context_menu_index = None;
let path_for_index = |ancestor_index| { let path_for_index = |ancestor_index| {
self.location self.location
.path_opt() .path_opt()
@ -3838,7 +3820,10 @@ impl Tab {
} }
} }
Message::Drag(rect_opt) => { Message::Drag(rect_opt) => {
self.watch_drag = false;
if let Some(rect) = rect_opt { if let Some(rect) = rect_opt {
self.context_menu = None;
self.location_context_menu_index = None;
if self.mode.multiple() { if self.mode.multiple() {
self.select_rect(rect, mod_ctrl, mod_shift); self.select_rect(rect, mod_ctrl, mod_shift);
} }
@ -3874,28 +3859,6 @@ impl Tab {
} }
Message::EditLocationSubmit => { Message::EditLocationSubmit => {
if let Some(mut edit_location) = self.edit_location.take() { if let Some(mut edit_location) = self.edit_location.take() {
let typed_opt = match &edit_location.location {
Location::Path(path) => path.to_str().map(str::to_string),
Location::Network(uri, ..) => Some(uri.clone()),
_ => None,
};
let mut typed_uri = false;
if let Some(typed) = typed_opt {
match typed.trim().parse::<url::Url>() {
Ok(url) if url.scheme() != "file" && url.has_host() => {
let uri = url.as_str().to_string();
edit_location =
Location::Network(uri.clone(), uri, None).normalize().into();
typed_uri = true;
}
Err(_) if matches!(edit_location.location, Location::Network(..)) => {
edit_location =
Location::Path(PathBuf::from(typed)).normalize().into();
}
_ => {}
}
}
// Select first completion if current location does not exist // Select first completion if current location does not exist
if edit_location.selected.is_none() if edit_location.selected.is_none()
&& edit_location && edit_location
@ -3911,9 +3874,6 @@ impl Tab {
} }
cd = edit_location.resolve(); cd = edit_location.resolve();
if cd.is_none() && typed_uri {
cd = Some(edit_location.location);
}
} }
} }
Message::EditLocationTab => { Message::EditLocationTab => {
@ -4475,11 +4435,13 @@ impl Tab {
} }
} }
Message::HighlightDeactivate(i) => { Message::HighlightDeactivate(i) => {
self.watch_drag = true;
if let Some(item) = self.items_opt.as_mut().and_then(|f| f.get_mut(i)) { if let Some(item) = self.items_opt.as_mut().and_then(|f| f.get_mut(i)) {
item.highlighted = false; item.highlighted = false;
} }
} }
Message::HighlightActivate(i) => { Message::HighlightActivate(i) => {
self.watch_drag = true;
if let Some(item) = self.items_opt.as_mut().and_then(|f| f.get_mut(i)) { if let Some(item) = self.items_opt.as_mut().and_then(|f| f.get_mut(i)) {
item.highlighted = true; item.highlighted = true;
} }
@ -4505,6 +4467,7 @@ impl Tab {
} }
Message::Scroll(viewport) => { Message::Scroll(viewport) => {
self.scroll_opt = Some(viewport.absolute_offset()); self.scroll_opt = Some(viewport.absolute_offset());
self.watch_drag = true;
} }
Message::ScrollTab(scroll_speed) => { Message::ScrollTab(scroll_speed) => {
commands.push(Command::Iced( commands.push(Command::Iced(
@ -4837,20 +4800,6 @@ impl Tab {
} }
} }
} }
#[cfg(feature = "gvfs")]
Message::DirectoryChildren(path, children) => {
if let Some(ref mut items) = self.items_opt {
for item in items.iter_mut() {
if item.path_opt() == Some(&path) {
if let ItemMetadata::GvfsPath { children_opt, .. } = &mut item.metadata
{
*children_opt = Some(children);
}
break;
}
}
}
}
Message::Checksums(path, checksum_state) => { Message::Checksums(path, checksum_state) => {
let location = Location::Path(path); let location = Location::Path(path);
if let Some(ref mut item) = self.parent_item_opt if let Some(ref mut item) = self.parent_item_opt
@ -4957,6 +4906,16 @@ impl Tab {
} }
} }
// Update context menu popup
if self.context_menu != last_context_menu {
if last_context_menu.is_some() {
commands.push(Command::ContextMenu(None, self.window_id));
}
if let Some(point) = self.context_menu {
commands.push(Command::ContextMenu(Some(point), self.window_id));
}
}
commands commands
} }
@ -5002,15 +4961,11 @@ impl Tab {
ItemMetadata::GvfsPath { ItemMetadata::GvfsPath {
size_opt, size_opt,
children_opt, children_opt,
is_dir,
.. ..
} => { } => match children_opt {
if *is_dir { Some(child_count) => (true, *child_count as u64),
(true, children_opt.unwrap_or_default() as u64) None => (false, size_opt.unwrap_or_default()),
} else { },
(false, size_opt.unwrap_or_default())
}
}
}; };
let (a_is_entry, a_size) = get_size(a.1); let (a_is_entry, a_size) = get_size(a.1);
let (b_is_entry, b_size) = get_size(b.1); let (b_is_entry, b_size) = get_size(b.1);
@ -5219,11 +5174,9 @@ impl Tab {
} }
ItemThumbnail::Text(text) => { ItemThumbnail::Text(text) => {
element_opt = Some( element_opt = Some(
widget::container( widget::container(widget::text_editor(text).padding(space_xxs).class(
widget::text_editor::text_editor(text) cosmic::theme::iced::TextEditor::Custom(Box::new(text_editor_class)),
.padding(space_xxs) ))
.style(text_editor_class),
)
.center(Length::Fill) .center(Length::Fill)
.into(), .into(),
); );
@ -5567,14 +5520,10 @@ impl Tab {
} }
let location = self.location.with_path(ancestor.to_path_buf()); let location = self.location.with_path(ancestor.to_path_buf());
let mouse_area = crate::mouse_area::MouseArea::new( let mut mouse_area = crate::mouse_area::MouseArea::new(
widget::button::custom(row) widget::button::custom(row)
.padding(space_xxxs) .padding(space_xxxs)
.class(if self.location_context_menu_index == Some(index) { .class(theme::Button::Link)
theme::Button::LinkActive
} else {
theme::Button::Link
})
.on_press(if ancestor == path { .on_press(if ancestor == path {
Message::EditLocation(Some(self.location.clone().into())) Message::EditLocation(Some(self.location.clone().into()))
} else { } else {
@ -5582,6 +5531,21 @@ impl Tab {
}), }),
); );
if self.location_context_menu_index.is_some() {
mouse_area = mouse_area
.on_right_press(move |point_opt| {
Message::LocationContextMenuIndex(point_opt, None)
})
.wayland_on_right_press_window_position();
} else {
mouse_area = mouse_area
.on_right_press_no_capture()
.on_right_press(move |point_opt| {
Message::LocationContextMenuIndex(point_opt, Some(index))
})
.wayland_on_right_press_window_position();
}
let mouse_area = if let Location::Path(_) = &self.location { let mouse_area = if let Location::Path(_) = &self.location {
mouse_area mouse_area
.on_middle_press(move |_| Message::OpenInNewTab(ancestor.to_path_buf())) .on_middle_press(move |_| Message::OpenInNewTab(ancestor.to_path_buf()))
@ -5589,16 +5553,7 @@ impl Tab {
mouse_area mouse_area
}; };
// Each breadcrumb carries the menu for its own ancestor index children.push(self.dnd_dest(&location, mouse_area));
let mut context_menu =
widget::context_menu(mouse_area, Some(menu::location_context_menu(index)))
.on_open(Message::LocationContextMenuIndex(Some(index)))
.on_close(Message::LocationContextMenuIndex(None))
.on_surface_action(Message::Surface);
if let Some(window_id) = self.window_id {
context_menu = context_menu.window_id(window_id);
}
children.push(self.dnd_dest(&location, context_menu));
if found_home || overflow { if found_home || overflow {
break; break;
@ -5649,7 +5604,20 @@ impl Tab {
column = column.push(heading_rule); column = column.push(heading_rule);
} }
column.into() let mouse_area = crate::mouse_area::MouseArea::new(column)
.on_right_press(Message::LocationContextMenuPoint);
let mut popover = widget::popover(mouse_area);
if let (Some(point), Some(index)) = (
self.location_context_menu_point,
self.location_context_menu_index,
) {
popover = popover
.popup(menu::location_context_menu(index))
.position(widget::popover::Position::Point(point));
}
popover.into()
} }
pub fn empty_view(&self, has_hidden: bool) -> Element<'_, Message> { pub fn empty_view(&self, has_hidden: bool) -> Element<'_, Message> {
@ -5838,13 +5806,18 @@ impl Tab {
.height(Length::Fixed(item_height as f32)) .height(Length::Fixed(item_height as f32))
.width(Length::Fixed(item_width as f32)); .width(Length::Fixed(item_width as f32));
for button in buttons { for button in buttons {
column = column.push( if self.context_menu.is_some() {
mouse_area::MouseArea::new(button) column = column.push(button);
.on_right_press_no_capture() } else {
.on_right_press(move |point_opt| { column = column.push(
Message::RightClick(point_opt, Some(i)) mouse_area::MouseArea::new(button)
}), .on_right_press_no_capture()
); .wayland_on_right_press_window_position()
.on_right_press(move |point_opt| {
Message::RightClick(point_opt, Some(i))
}),
);
}
} }
let column: Element<Message> = let column: Element<Message> =
@ -6011,13 +5984,15 @@ impl Tab {
Element::from(dnd_grid) Element::from(dnd_grid)
}); });
let mouse_area = mouse_area::MouseArea::new(column.width(Length::Fill)) let mut mouse_area = mouse_area::MouseArea::new(column.width(Length::Fill))
.on_press(|_| Message::Click(None)) .on_press(|_| Message::Click(None))
.on_auto_scroll(Message::AutoScroll) .on_auto_scroll(Message::AutoScroll)
.on_drag(move |rect_opt| self.on_drag(rect_opt))
.on_drag_end(|_| Message::DragEnd) .on_drag_end(|_| Message::DragEnd)
.show_drag_rect(self.mode.multiple()) .show_drag_rect(self.mode.multiple())
.on_release(|_| Message::ClickRelease(None)); .on_release(|_| Message::ClickRelease(None));
if self.watch_drag {
mouse_area = mouse_area.on_drag(Message::Drag);
}
(drag_list, mouse_area.into(), true) (drag_list, mouse_area.into(), true)
} }
@ -6166,21 +6141,17 @@ impl Tab {
ItemMetadata::GvfsPath { ItemMetadata::GvfsPath {
size_opt, size_opt,
children_opt, children_opt,
is_dir,
.. ..
} => { } => match children_opt {
if *is_dir { Some(child_count) => {
// Children are not counted on remote filesystems if *child_count == 1 {
match children_opt { format!("{child_count} item")
//TODO: translate } else {
Some(1) => "1 item".to_string(), format!("{child_count} items")
Some(child_count) => format!("{child_count} items"),
None => String::new(),
} }
} else {
format_size(size_opt.unwrap_or_default())
} }
} None => format_size(size_opt.unwrap_or_default()),
},
}; };
let row = if condensed { let row = if condensed {
@ -6247,33 +6218,39 @@ impl Tab {
.spacing(space_xxs) .spacing(space_xxs)
}; };
let button = let button = |row| {
|row| { let mouse_area = crate::mouse_area::MouseArea::new(
let mouse_area = crate::mouse_area::MouseArea::new( widget::button::custom(row)
widget::button::custom(row) .width(Length::Fill)
.width(Length::Fill) .id(item.button_id.clone())
.id(item.button_id.clone()) .padding([0, space_xxs])
.padding([0, space_xxs]) .class(button_style(
.class(button_style( item.selected,
item.selected, item.highlighted,
item.highlighted, item.cut,
item.cut, true,
true, true,
true, false,
false, )),
)), )
) .on_press(move |_| Message::Click(Some(i)))
.on_press(move |_| Message::Click(Some(i))) .on_double_click(move |_| Message::DoubleClick(Some(i)))
.on_double_click(move |_| Message::DoubleClick(Some(i))) .on_release(move |_| Message::ClickRelease(Some(i)))
.on_release(move |_| Message::ClickRelease(Some(i))) .on_middle_press(move |_| Message::MiddleClick(i))
.on_middle_press(move |_| Message::MiddleClick(i)) .on_enter(move || Message::HighlightActivate(i))
.on_enter(move || Message::HighlightActivate(i)) .on_exit(move || Message::HighlightDeactivate(i));
.on_exit(move || Message::HighlightDeactivate(i));
mouse_area.on_right_press_no_capture().on_right_press( if self.context_menu.is_some() {
move |point_opt| Message::RightClick(point_opt, Some(i)), mouse_area
) } else {
}; mouse_area
.on_right_press_no_capture()
.wayland_on_right_press_window_position()
.on_right_press(move |point_opt| {
Message::RightClick(point_opt, Some(i))
})
}
};
let button_row = button(row.into()); let button_row = button(row.into());
let button_row: Element<_> = if item.metadata.is_dir() let button_row: Element<_> = if item.metadata.is_dir()
@ -6401,14 +6378,16 @@ impl Tab {
let drag_col = (!drag_items.is_empty()) let drag_col = (!drag_items.is_empty())
.then(|| Element::from(widget::column::with_children(drag_items))); .then(|| Element::from(widget::column::with_children(drag_items)));
let mouse_area = mouse_area::MouseArea::new(column.padding([0, space_s])) let mut mouse_area = mouse_area::MouseArea::new(column.padding([0, space_s]))
.with_id(Id::new("list-view")) .with_id(Id::new("list-view"))
.on_press(|_| Message::Click(None)) .on_press(|_| Message::Click(None))
.on_auto_scroll(Message::AutoScroll) .on_auto_scroll(Message::AutoScroll)
.on_drag(move |rect_opt| self.on_drag(rect_opt))
.on_drag_end(|_| Message::DragEnd) .on_drag_end(|_| Message::DragEnd)
.show_drag_rect(self.mode.multiple()) .show_drag_rect(self.mode.multiple())
.on_release(|_| Message::ClickRelease(None)); .on_release(|_| Message::ClickRelease(None));
if self.watch_drag {
mouse_area = mouse_area.on_drag(Message::Drag);
}
(drag_col, mouse_area.into(), true) (drag_col, mouse_area.into(), true)
} }
@ -6495,47 +6474,51 @@ impl Tab {
.on_back_press(move |_point_opt| Message::GoPrevious) .on_back_press(move |_point_opt| Message::GoPrevious)
.on_forward_press(move |_point_opt| Message::GoNext) .on_forward_press(move |_point_opt| Message::GoNext)
.on_scroll(|delta| respond_to_scroll_direction(delta, modifiers)) .on_scroll(|delta| respond_to_scroll_direction(delta, modifiers))
.on_right_press(|_| Message::RightClickBackground); .on_right_press(move |p| {
Message::ContextMenu(
if self.context_menu.is_some() { None } else { p },
self.window_id,
)
})
.wayland_on_right_press_window_position();
let items_area: Element<'_, Message> = if can_scroll { let mut popover = widget::popover(mouse_area);
// FIXME: new responsive widget will remove the state from the scrollable if let Some(point) = self.context_menu
// id_container with custom id forces the state to be extracted in a diff && (!cfg!(feature = "wayland") || !crate::is_wayland())
// pre-processing step {
widget::id_container( let context_menu = menu::context_menu(
widget::scrollable(mouse_area)
.id(self.scrollable_id.clone())
.on_scroll(Message::Scroll)
.width(Length::Fill)
.height(Length::Fill),
widget::Id::new(format!("{}-scrollable", self.scrollable_id)),
)
.into()
} else {
mouse_area.into()
};
// Wrap the scrollable, not its content, so the popup anchors in window coordinates
let mut context_menu = widget::context_menu(
items_area,
Some(menu::context_menu(
self, self,
key_binds, key_binds,
modifiers, modifiers,
clipboard_paste_available, clipboard_paste_available,
context_actions, context_actions,
)), );
) popover = popover
.item_width(cosmic::widget::menu::ItemWidth::Uniform(360)) .popup(context_menu)
.on_surface_action(Message::Surface); .position(widget::popover::Position::Point(point));
if let Some(window_id) = self.window_id {
context_menu = context_menu.window_id(window_id);
} }
let mut tab_column = widget::column::with_capacity(3); let mut tab_column = widget::column::with_capacity(3);
if let Some(location_view) = location_view_opt { if let Some(location_view) = location_view_opt {
tab_column = tab_column.push(location_view); tab_column = tab_column.push(location_view);
} }
tab_column = tab_column.push(context_menu); if can_scroll {
tab_column = tab_column.push(
// FIXME: new responsive widget will remove the state from the scrollable
// id_container with custom id forces the state to be extracted in a diff
// pre-processing step
widget::id_container(
widget::scrollable(popover)
.id(self.scrollable_id.clone())
.on_scroll(Message::Scroll)
.width(Length::Fill)
.height(Length::Fill),
widget::Id::new(format!("{}-scrollable", self.scrollable_id)),
),
);
} else {
tab_column = tab_column.push(popover);
}
match &self.location { match &self.location {
Location::Trash | Location::Search(SearchLocation::Trash, ..) => { Location::Trash | Location::Search(SearchLocation::Trash, ..) => {
if let Some(items) = self.items_opt() if let Some(items) = self.items_opt()
@ -6934,74 +6917,6 @@ impl Tab {
Rectangle::new(point, size) Rectangle::new(point, size)
}; };
// Count the children of visible directories in the background. Doing it while
// scanning costs one directory listing per entry, which stalls remote filesystems.
#[cfg(feature = "gvfs")]
for item in items {
let ItemMetadata::GvfsPath {
children_opt: None,
is_dir: true,
..
} = &item.metadata
else {
continue;
};
// Skip items that are not visible, or have no determined rect
match item.rect_opt.get() {
Some(rect) if rect.intersects(&visible_rect) => {}
_ => continue,
}
let Some(path) = item.path_opt().cloned() else {
continue;
};
struct ChildrenWrapper(PathBuf);
impl Hash for ChildrenWrapper {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
subscriptions.push(Subscription::run_with(
ChildrenWrapper(path),
|ChildrenWrapper(path)| {
let path = path.clone();
stream::channel(
1,
move |mut output: futures::channel::mpsc::Sender<_>| async move {
let message = {
let path = path.clone();
tokio::task::spawn_blocking(move || {
let children = match fs::read_dir(&path) {
Ok(entries) => entries.count(),
Err(err) => {
log::warn!(
"failed to read directory {}: {}",
path.display(),
err
);
0
}
};
Message::DirectoryChildren(path, children)
})
.await
.unwrap()
};
if let Err(err) = output.send(message).await {
log::warn!("failed to send directory children: {err}");
}
std::future::pending().await
},
)
},
));
}
for item in items { for item in items {
if item.thumbnail_opt.is_some() { if item.thumbnail_opt.is_some() {
// Skip items that already have a mime type and thumbnail // Skip items that already have a mime type and thumbnail
@ -7097,7 +7012,7 @@ impl Tab {
let path = path.clone(); let path = path.clone();
// Acquire semaphore permit // Acquire semaphore permit
let _permit = THUMB_SEMAPHORE.acquire().await.unwrap(); _ = THUMB_SEMAPHORE.acquire().await;
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let start = Instant::now(); let start = Instant::now();
@ -7401,19 +7316,6 @@ impl Tab {
const fn format_time(&self, time: SystemTime) -> FormatTime<'_> { const fn format_time(&self, time: SystemTime) -> FormatTime<'_> {
format_time(time, &self.date_time_formatter, &self.time_formatter) format_time(time, &self.date_time_formatter, &self.time_formatter)
} }
fn on_drag<'a>(&self, rect_opt: Option<Rectangle>) -> Option<Message> {
let rect = rect_opt?;
// We only want to publish a drag message if the overlapped items of the drag rect change,
// otherwise a view rebuild is triggered on every drag event.
let changed = self.items_opt.as_ref().is_some_and(|items| {
items.iter().any(|item| {
let overlaps = item.rect_opt.get().is_some_and(|r| r.intersects(&rect));
overlaps != item.overlaps_drag_rect
})
});
changed.then_some(Message::Drag(Some(rect)))
}
} }
pub fn respond_to_scroll_direction(delta: ScrollDelta, modifiers: &Modifiers) -> Option<Message> { pub fn respond_to_scroll_direction(delta: ScrollDelta, modifiers: &Modifiers) -> Option<Message> {

View file

@ -175,25 +175,3 @@ pub fn thumbnailer(mime: &Mime) -> Vec<Thumbnailer> {
let thumbnailer_cache = THUMBNAILER_CACHE.lock().unwrap(); let thumbnailer_cache = THUMBNAILER_CACHE.lock().unwrap();
thumbnailer_cache.get(mime) thumbnailer_cache.get(mime)
} }
#[cfg(all(test, feature = "desktop"))]
mod tests {
use super::Thumbnailer;
use std::path::Path;
#[test]
fn command_expands_thumbnailer_arguments() {
let thumbnailer = Thumbnailer {
exec: "cosmic-files-thumbnailer %o --size %s %i".to_string(),
};
let command = thumbnailer
.command(Path::new("input.exe"), Path::new("output.png"), 128)
.expect("valid thumbnailer command");
assert_eq!(command.get_program(), "cosmic-files-thumbnailer");
assert_eq!(
command.get_args().collect::<Vec<_>>(),
["output.png", "--size", "128", "input.exe"]
);
}
}

View file

@ -2,7 +2,6 @@ use cosmic::widget;
use regex::Regex; use regex::Regex;
use std::collections::HashSet; use std::collections::HashSet;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use crate::config::IconSizes; use crate::config::IconSizes;
use crate::tab::{Item, SearchItem}; use crate::tab::{Item, SearchItem};
@ -90,12 +89,11 @@ pub fn trash_item_path(item: &trash::TrashItem) -> Option<PathBuf> {
/// - Read `~/.local/share/Trash/info/folder.trashinfo` to get the original path /// - Read `~/.local/share/Trash/info/folder.trashinfo` to get the original path
/// - Compute: `<original_path>/sub/file.txt` /// - Compute: `<original_path>/sub/file.txt`
pub fn original_path_for_trash_child(p: &Path) -> Option<PathBuf> { pub fn original_path_for_trash_child(p: &Path) -> Option<PathBuf> {
let files = trash_files_dir(p)?; let files = p.ancestors().find(|a| a.ends_with("files"))?;
let root = files.parent()?; let root = files.parent()?;
let top = p.strip_prefix(files).ok()?.components().next()?; let top = p.strip_prefix(files).ok()?.components().next()?;
let mut trashinfo_name = top.as_os_str().to_os_string(); let info =
trashinfo_name.push(".trashinfo"); std::fs::read_to_string(root.join("info").join(top).with_extension("trashinfo")).ok()?;
let info = std::fs::read_to_string(root.join("info").join(trashinfo_name)).ok()?;
let orig = percent_decode(info.lines().find_map(|l| l.strip_prefix("Path="))?.trim())?; let orig = percent_decode(info.lines().find_map(|l| l.strip_prefix("Path="))?.trim())?;
let rel = p.strip_prefix(files.join(top)).ok()?; let rel = p.strip_prefix(files.join(top)).ok()?;
let mut result = PathBuf::from(&orig); let mut result = PathBuf::from(&orig);
@ -105,29 +103,9 @@ pub fn original_path_for_trash_child(p: &Path) -> Option<PathBuf> {
Some(result) Some(result)
} }
static TRASH_FOLDERS: LazyLock<HashSet<PathBuf>> = LazyLock::new(|| {
Trash::folders().unwrap_or_else(|e| {
log::warn!("failed to list trash folders: {}", e);
HashSet::new()
})
});
fn is_trash_root(root: &Path) -> bool {
if TRASH_FOLDERS.is_empty() {
root.join("info").is_dir()
} else {
TRASH_FOLDERS.contains(root)
}
}
fn trash_files_dir(path: &Path) -> Option<&Path> {
path.ancestors()
.find(|a| a.ends_with("files") && a.parent().is_some_and(is_trash_root))
}
/// Check whether a path is inside any trash `files/` directory. /// Check whether a path is inside any trash `files/` directory.
pub fn is_trash_path(path: &Path) -> bool { pub fn is_trash_path(path: &Path) -> bool {
trash_files_dir(path).is_some() path.ancestors().any(|a| a.ends_with("files"))
} }
pub struct Trash; pub struct Trash;