Fix drag-and-drop and file manager integration
This commit is contained in:
@@ -84,15 +84,14 @@ fn shell_safe(s: &str) -> String {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pixstrip_bin() -> String {
|
/// Path to the GTK binary for "Open in Pixstrip" actions.
|
||||||
// Try to find the pixstrip binary path
|
fn pixstrip_gtk_bin() -> String {
|
||||||
if let Ok(exe) = std::env::current_exe() {
|
// When running from AppImage, use the AppImage path directly
|
||||||
// If running from the GTK app, find the CLI sibling
|
if let Ok(appimage) = std::env::var("APPIMAGE") {
|
||||||
let dir = exe.parent().unwrap_or(Path::new("/usr/bin"));
|
return appimage;
|
||||||
let cli_path = dir.join("pixstrip");
|
|
||||||
if cli_path.exists() {
|
|
||||||
return cli_path.display().to_string();
|
|
||||||
}
|
}
|
||||||
|
if let Ok(exe) = std::env::current_exe() {
|
||||||
|
let dir = exe.parent().unwrap_or(Path::new("/usr/bin"));
|
||||||
let gtk_path = dir.join("pixstrip-gtk");
|
let gtk_path = dir.join("pixstrip-gtk");
|
||||||
if gtk_path.exists() {
|
if gtk_path.exists() {
|
||||||
return gtk_path.display().to_string();
|
return gtk_path.display().to_string();
|
||||||
@@ -102,6 +101,22 @@ fn pixstrip_bin() -> String {
|
|||||||
"pixstrip-gtk".into()
|
"pixstrip-gtk".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Path to the CLI binary for preset processing actions.
|
||||||
|
fn pixstrip_cli_bin() -> String {
|
||||||
|
// When running from AppImage, use the AppImage path directly
|
||||||
|
if let Ok(appimage) = std::env::var("APPIMAGE") {
|
||||||
|
return appimage;
|
||||||
|
}
|
||||||
|
if let Ok(exe) = std::env::current_exe() {
|
||||||
|
let dir = exe.parent().unwrap_or(Path::new("/usr/bin"));
|
||||||
|
let cli_path = dir.join("pixstrip");
|
||||||
|
if cli_path.exists() {
|
||||||
|
return cli_path.display().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"pixstrip".into()
|
||||||
|
}
|
||||||
|
|
||||||
fn get_preset_names() -> Vec<String> {
|
fn get_preset_names() -> Vec<String> {
|
||||||
let mut names: Vec<String> = Preset::all_builtins()
|
let mut names: Vec<String> = Preset::all_builtins()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -138,7 +153,8 @@ fn install_nautilus() -> Result<()> {
|
|||||||
let dir = nautilus_extension_dir();
|
let dir = nautilus_extension_dir();
|
||||||
std::fs::create_dir_all(&dir)?;
|
std::fs::create_dir_all(&dir)?;
|
||||||
|
|
||||||
let bin = pixstrip_bin();
|
let gtk_bin = pixstrip_gtk_bin();
|
||||||
|
let cli_bin = pixstrip_cli_bin();
|
||||||
let presets = get_preset_names();
|
let presets = get_preset_names();
|
||||||
|
|
||||||
let mut preset_items = String::new();
|
let mut preset_items = String::new();
|
||||||
@@ -156,7 +172,8 @@ fn install_nautilus() -> Result<()> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let escaped_bin = bin.replace('\\', "\\\\").replace('\'', "\\'");
|
let escaped_gtk_bin = gtk_bin.replace('\\', "\\\\").replace('\'', "\\'");
|
||||||
|
let escaped_cli_bin = cli_bin.replace('\\', "\\\\").replace('\'', "\\'");
|
||||||
let script = format!(
|
let script = format!(
|
||||||
r#"import subprocess
|
r#"import subprocess
|
||||||
from gi.repository import Nautilus, GObject
|
from gi.repository import Nautilus, GObject
|
||||||
@@ -204,14 +221,15 @@ class PixstripExtension(GObject.GObject, Nautilus.MenuProvider):
|
|||||||
|
|
||||||
def _on_open(self, menu, files):
|
def _on_open(self, menu, files):
|
||||||
paths = [f.get_location().get_path() for f in files if f.get_location()]
|
paths = [f.get_location().get_path() for f in files if f.get_location()]
|
||||||
subprocess.Popen(['{bin}', '--files'] + paths)
|
subprocess.Popen(['{gtk_bin}'] + paths)
|
||||||
|
|
||||||
def _on_preset(self, menu, preset_name, files):
|
def _on_preset(self, menu, preset_name, files):
|
||||||
paths = [f.get_location().get_path() for f in files if f.get_location()]
|
paths = [f.get_location().get_path() for f in files if f.get_location()]
|
||||||
subprocess.Popen(['{bin}', '--preset', preset_name, '--files'] + paths)
|
subprocess.Popen(['{cli_bin}', 'process', '--preset', preset_name] + paths)
|
||||||
"#,
|
"#,
|
||||||
preset_items = preset_items,
|
preset_items = preset_items,
|
||||||
bin = escaped_bin,
|
gtk_bin = escaped_gtk_bin,
|
||||||
|
cli_bin = escaped_cli_bin,
|
||||||
);
|
);
|
||||||
|
|
||||||
atomic_write(&nautilus_extension_path(), &script)?;
|
atomic_write(&nautilus_extension_path(), &script)?;
|
||||||
@@ -245,19 +263,20 @@ fn install_nemo() -> Result<()> {
|
|||||||
let dir = nemo_action_dir();
|
let dir = nemo_action_dir();
|
||||||
std::fs::create_dir_all(&dir)?;
|
std::fs::create_dir_all(&dir)?;
|
||||||
|
|
||||||
let bin = pixstrip_bin();
|
let gtk_bin = pixstrip_gtk_bin();
|
||||||
|
let cli_bin = pixstrip_cli_bin();
|
||||||
|
|
||||||
// Main "Open in Pixstrip" action
|
// Main "Open in Pixstrip" action
|
||||||
let open_action = format!(
|
let open_action = format!(
|
||||||
"[Nemo Action]\n\
|
"[Nemo Action]\n\
|
||||||
Name=Open in Pixstrip...\n\
|
Name=Open in Pixstrip...\n\
|
||||||
Comment=Process images with Pixstrip\n\
|
Comment=Process images with Pixstrip\n\
|
||||||
Exec={bin} --files %F\n\
|
Exec={gtk_bin} %F\n\
|
||||||
Icon-Name=applications-graphics-symbolic\n\
|
Icon-Name=applications-graphics-symbolic\n\
|
||||||
Selection=Any\n\
|
Selection=Any\n\
|
||||||
Extensions=jpg;jpeg;png;webp;gif;tiff;tif;avif;bmp;\n\
|
Extensions=jpg;jpeg;png;webp;gif;tiff;tif;avif;bmp;\n\
|
||||||
Mimetypes=image/*;\n",
|
Mimetypes=image/*;\n",
|
||||||
bin = bin,
|
gtk_bin = gtk_bin,
|
||||||
);
|
);
|
||||||
atomic_write(&nemo_action_path(), &open_action)?;
|
atomic_write(&nemo_action_path(), &open_action)?;
|
||||||
|
|
||||||
@@ -270,14 +289,14 @@ fn install_nemo() -> Result<()> {
|
|||||||
"[Nemo Action]\n\
|
"[Nemo Action]\n\
|
||||||
Name=Pixstrip: {name}\n\
|
Name=Pixstrip: {name}\n\
|
||||||
Comment=Process with {name} preset\n\
|
Comment=Process with {name} preset\n\
|
||||||
Exec={bin} --preset \"{safe_label}\" --files %F\n\
|
Exec={cli_bin} process --preset \"{safe_label}\" %F\n\
|
||||||
Icon-Name=applications-graphics-symbolic\n\
|
Icon-Name=applications-graphics-symbolic\n\
|
||||||
Selection=Any\n\
|
Selection=Any\n\
|
||||||
Extensions=jpg;jpeg;png;webp;gif;tiff;tif;avif;bmp;\n\
|
Extensions=jpg;jpeg;png;webp;gif;tiff;tif;avif;bmp;\n\
|
||||||
Mimetypes=image/*;\n",
|
Mimetypes=image/*;\n",
|
||||||
name = name,
|
name = name,
|
||||||
safe_label = shell_safe(name),
|
safe_label = shell_safe(name),
|
||||||
bin = bin,
|
cli_bin = cli_bin,
|
||||||
);
|
);
|
||||||
atomic_write(&action_path, &action)?;
|
atomic_write(&action_path, &action)?;
|
||||||
}
|
}
|
||||||
@@ -324,7 +343,8 @@ fn install_thunar() -> Result<()> {
|
|||||||
let dir = thunar_action_dir();
|
let dir = thunar_action_dir();
|
||||||
std::fs::create_dir_all(&dir)?;
|
std::fs::create_dir_all(&dir)?;
|
||||||
|
|
||||||
let bin = pixstrip_bin();
|
let gtk_bin = pixstrip_gtk_bin();
|
||||||
|
let cli_bin = pixstrip_cli_bin();
|
||||||
let presets = get_preset_names();
|
let presets = get_preset_names();
|
||||||
|
|
||||||
let mut actions = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<actions>\n");
|
let mut actions = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<actions>\n");
|
||||||
@@ -334,13 +354,13 @@ fn install_thunar() -> Result<()> {
|
|||||||
" <action>\n\
|
" <action>\n\
|
||||||
\x20 <icon>applications-graphics-symbolic</icon>\n\
|
\x20 <icon>applications-graphics-symbolic</icon>\n\
|
||||||
\x20 <name>Open in Pixstrip...</name>\n\
|
\x20 <name>Open in Pixstrip...</name>\n\
|
||||||
\x20 <command>{bin} --files %F</command>\n\
|
\x20 <command>{gtk_bin} %F</command>\n\
|
||||||
\x20 <description>Process images with Pixstrip</description>\n\
|
\x20 <description>Process images with Pixstrip</description>\n\
|
||||||
\x20 <patterns>*.jpg;*.jpeg;*.png;*.webp;*.gif;*.tiff;*.tif;*.avif;*.bmp</patterns>\n\
|
\x20 <patterns>*.jpg;*.jpeg;*.png;*.webp;*.gif;*.tiff;*.tif;*.avif;*.bmp</patterns>\n\
|
||||||
\x20 <image-files/>\n\
|
\x20 <image-files/>\n\
|
||||||
\x20 <directories/>\n\
|
\x20 <directories/>\n\
|
||||||
</action>\n",
|
</action>\n",
|
||||||
bin = bin,
|
gtk_bin = gtk_bin,
|
||||||
));
|
));
|
||||||
|
|
||||||
for name in &presets {
|
for name in &presets {
|
||||||
@@ -348,7 +368,7 @@ fn install_thunar() -> Result<()> {
|
|||||||
" <action>\n\
|
" <action>\n\
|
||||||
\x20 <icon>applications-graphics-symbolic</icon>\n\
|
\x20 <icon>applications-graphics-symbolic</icon>\n\
|
||||||
\x20 <name>Pixstrip: {xml_name}</name>\n\
|
\x20 <name>Pixstrip: {xml_name}</name>\n\
|
||||||
\x20 <command>{bin} --preset \"{safe_label}\" --files %F</command>\n\
|
\x20 <command>{cli_bin} process --preset \"{safe_label}\" %F</command>\n\
|
||||||
\x20 <description>Process with {xml_name} preset</description>\n\
|
\x20 <description>Process with {xml_name} preset</description>\n\
|
||||||
\x20 <patterns>*.jpg;*.jpeg;*.png;*.webp;*.gif;*.tiff;*.tif;*.avif;*.bmp</patterns>\n\
|
\x20 <patterns>*.jpg;*.jpeg;*.png;*.webp;*.gif;*.tiff;*.tif;*.avif;*.bmp</patterns>\n\
|
||||||
\x20 <image-files/>\n\
|
\x20 <image-files/>\n\
|
||||||
@@ -356,7 +376,7 @@ fn install_thunar() -> Result<()> {
|
|||||||
</action>\n",
|
</action>\n",
|
||||||
xml_name = name.replace('&', "&").replace('<', "<").replace('>', ">").replace('"', """),
|
xml_name = name.replace('&', "&").replace('<', "<").replace('>', ">").replace('"', """),
|
||||||
safe_label = shell_safe(name),
|
safe_label = shell_safe(name),
|
||||||
bin = bin,
|
cli_bin = cli_bin,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +412,8 @@ fn install_dolphin() -> Result<()> {
|
|||||||
let dir = dolphin_service_dir();
|
let dir = dolphin_service_dir();
|
||||||
std::fs::create_dir_all(&dir)?;
|
std::fs::create_dir_all(&dir)?;
|
||||||
|
|
||||||
let bin = pixstrip_bin();
|
let gtk_bin = pixstrip_gtk_bin();
|
||||||
|
let cli_bin = pixstrip_cli_bin();
|
||||||
let presets = get_preset_names();
|
let presets = get_preset_names();
|
||||||
|
|
||||||
let mut desktop = format!(
|
let mut desktop = format!(
|
||||||
@@ -406,8 +427,8 @@ fn install_dolphin() -> Result<()> {
|
|||||||
[Desktop Action Open]\n\
|
[Desktop Action Open]\n\
|
||||||
Name=Open in Pixstrip...\n\
|
Name=Open in Pixstrip...\n\
|
||||||
Icon=applications-graphics-symbolic\n\
|
Icon=applications-graphics-symbolic\n\
|
||||||
Exec={bin} --files %F\n\n",
|
Exec={gtk_bin} %F\n\n",
|
||||||
bin = bin,
|
gtk_bin = gtk_bin,
|
||||||
preset_actions = presets
|
preset_actions = presets
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -421,11 +442,11 @@ fn install_dolphin() -> Result<()> {
|
|||||||
"[Desktop Action Preset{i}]\n\
|
"[Desktop Action Preset{i}]\n\
|
||||||
Name={name}\n\
|
Name={name}\n\
|
||||||
Icon=applications-graphics-symbolic\n\
|
Icon=applications-graphics-symbolic\n\
|
||||||
Exec={bin} --preset \"{safe_label}\" --files %F\n\n",
|
Exec={cli_bin} process --preset \"{safe_label}\" %F\n\n",
|
||||||
i = i,
|
i = i,
|
||||||
name = name,
|
name = name,
|
||||||
safe_label = shell_safe(name),
|
safe_label = shell_safe(name),
|
||||||
bin = bin,
|
cli_bin = cli_bin,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,10 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
let subfolder_choice: Rc<RefCell<Option<bool>>> = Rc::new(RefCell::new(None));
|
let subfolder_choice: Rc<RefCell<Option<bool>>> = Rc::new(RefCell::new(None));
|
||||||
|
|
||||||
// Set up drag-and-drop on the entire page
|
// Set up drag-and-drop on the entire page
|
||||||
let drop_target = gtk::DropTarget::new(gtk::gio::File::static_type(), gtk::gdk::DragAction::COPY);
|
// Accept both FileList (from file managers) and single File
|
||||||
drop_target.set_types(&[gtk::gio::File::static_type()]);
|
let drop_target = gtk::DropTarget::new(gtk::gdk::FileList::static_type(), gtk::gdk::DragAction::COPY | gtk::gdk::DragAction::MOVE);
|
||||||
|
drop_target.set_types(&[gtk::gdk::FileList::static_type(), gtk::gio::File::static_type(), glib::GString::static_type()]);
|
||||||
|
drop_target.set_preload(true);
|
||||||
|
|
||||||
{
|
{
|
||||||
let loaded_files = state.loaded_files.clone();
|
let loaded_files = state.loaded_files.clone();
|
||||||
@@ -40,40 +42,97 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
let stack_ref = stack.clone();
|
let stack_ref = stack.clone();
|
||||||
let subfolder_choice = subfolder_choice.clone();
|
let subfolder_choice = subfolder_choice.clone();
|
||||||
drop_target.connect_drop(move |target, value, _x, _y| {
|
drop_target.connect_drop(move |target, value, _x, _y| {
|
||||||
if let Ok(file) = value.get::<gtk::gio::File>()
|
// Collect paths from FileList, single File, or URI text
|
||||||
&& let Some(path) = file.path()
|
let mut paths: Vec<PathBuf> = Vec::new();
|
||||||
{
|
if let Ok(file_list) = value.get::<gtk::gdk::FileList>() {
|
||||||
|
for file in file_list.files() {
|
||||||
|
if let Some(path) = file.path() {
|
||||||
|
paths.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Ok(file) = value.get::<gtk::gio::File>() {
|
||||||
|
if let Some(path) = file.path() {
|
||||||
|
paths.push(path);
|
||||||
|
}
|
||||||
|
} else if let Ok(text) = value.get::<glib::GString>() {
|
||||||
|
// Handle URI text drops (from web browsers)
|
||||||
|
let text = text.trim().to_string();
|
||||||
|
let lower = text.to_lowercase();
|
||||||
|
let is_image_url = (lower.starts_with("http://") || lower.starts_with("https://"))
|
||||||
|
&& (lower.ends_with(".jpg")
|
||||||
|
|| lower.ends_with(".jpeg")
|
||||||
|
|| lower.ends_with(".png")
|
||||||
|
|| lower.ends_with(".webp")
|
||||||
|
|| lower.ends_with(".gif")
|
||||||
|
|| lower.ends_with(".avif")
|
||||||
|
|| lower.ends_with(".tiff")
|
||||||
|
|| lower.ends_with(".bmp")
|
||||||
|
|| lower.contains(".jpg?")
|
||||||
|
|| lower.contains(".jpeg?")
|
||||||
|
|| lower.contains(".png?")
|
||||||
|
|| lower.contains(".webp?"));
|
||||||
|
|
||||||
|
if is_image_url {
|
||||||
|
let loaded = loaded_files.clone();
|
||||||
|
let excl = excluded.clone();
|
||||||
|
let sz = sizes.clone();
|
||||||
|
let sr = stack_ref.clone();
|
||||||
|
let (tx, rx) = std::sync::mpsc::channel::<Option<std::path::PathBuf>>();
|
||||||
|
let url = text.clone();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let result = download_image_url(&url);
|
||||||
|
let _ = tx.send(result);
|
||||||
|
});
|
||||||
|
glib::timeout_add_local(std::time::Duration::from_millis(100), move || {
|
||||||
|
match rx.try_recv() {
|
||||||
|
Ok(Some(path)) => {
|
||||||
|
let mut files = loaded.borrow_mut();
|
||||||
|
if !files.contains(&path) {
|
||||||
|
files.push(path);
|
||||||
|
}
|
||||||
|
let count = files.len();
|
||||||
|
drop(files);
|
||||||
|
refresh_grid(&sr, &loaded, &excl, &sz, count);
|
||||||
|
glib::ControlFlow::Break
|
||||||
|
}
|
||||||
|
Ok(None) => glib::ControlFlow::Break,
|
||||||
|
Err(std::sync::mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue,
|
||||||
|
Err(_) => glib::ControlFlow::Break,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if paths.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for path in paths {
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
let has_subdirs = has_subfolders(&path);
|
let has_subdirs = has_subfolders(&path);
|
||||||
if !has_subdirs {
|
if !has_subdirs {
|
||||||
let mut files = loaded_files.borrow_mut();
|
let mut files = loaded_files.borrow_mut();
|
||||||
add_images_flat(&path, &mut files);
|
add_images_flat(&path, &mut files);
|
||||||
let count = files.len();
|
|
||||||
drop(files);
|
drop(files);
|
||||||
refresh_grid(&stack_ref, &loaded_files, &excluded, &sizes, count);
|
|
||||||
} else {
|
} else {
|
||||||
let choice = *subfolder_choice.borrow();
|
let choice = *subfolder_choice.borrow();
|
||||||
match choice {
|
match choice {
|
||||||
Some(true) => {
|
Some(true) => {
|
||||||
let mut files = loaded_files.borrow_mut();
|
let mut files = loaded_files.borrow_mut();
|
||||||
add_images_from_dir(&path, &mut files);
|
add_images_from_dir(&path, &mut files);
|
||||||
let count = files.len();
|
|
||||||
drop(files);
|
drop(files);
|
||||||
refresh_grid(&stack_ref, &loaded_files, &excluded, &sizes, count);
|
|
||||||
}
|
}
|
||||||
Some(false) => {
|
Some(false) => {
|
||||||
let mut files = loaded_files.borrow_mut();
|
let mut files = loaded_files.borrow_mut();
|
||||||
add_images_flat(&path, &mut files);
|
add_images_flat(&path, &mut files);
|
||||||
let count = files.len();
|
|
||||||
drop(files);
|
drop(files);
|
||||||
refresh_grid(&stack_ref, &loaded_files, &excluded, &sizes, count);
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let mut files = loaded_files.borrow_mut();
|
let mut files = loaded_files.borrow_mut();
|
||||||
add_images_flat(&path, &mut files);
|
add_images_flat(&path, &mut files);
|
||||||
let count = files.len();
|
|
||||||
drop(files);
|
drop(files);
|
||||||
refresh_grid(&stack_ref, &loaded_files, &excluded, &sizes, count);
|
|
||||||
|
|
||||||
let loaded_files = loaded_files.clone();
|
let loaded_files = loaded_files.clone();
|
||||||
let excluded = excluded.clone();
|
let excluded = excluded.clone();
|
||||||
@@ -98,88 +157,23 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} else if is_image_file(&path) {
|
} else if is_image_file(&path) {
|
||||||
let mut files = loaded_files.borrow_mut();
|
let mut files = loaded_files.borrow_mut();
|
||||||
if !files.contains(&path) {
|
if !files.contains(&path) {
|
||||||
files.push(path);
|
files.push(path);
|
||||||
}
|
}
|
||||||
let count = files.len();
|
|
||||||
drop(files);
|
drop(files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = loaded_files.borrow().len();
|
||||||
refresh_grid(&stack_ref, &loaded_files, &excluded, &sizes, count);
|
refresh_grid(&stack_ref, &loaded_files, &excluded, &sizes, count);
|
||||||
return true;
|
true
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.add_controller(drop_target);
|
stack.add_controller(drop_target);
|
||||||
|
|
||||||
// Also accept URI text drops (from web browsers)
|
|
||||||
let uri_drop = gtk::DropTarget::new(glib::GString::static_type(), gtk::gdk::DragAction::COPY);
|
|
||||||
{
|
|
||||||
let loaded_files = state.loaded_files.clone();
|
|
||||||
let excluded = state.excluded_files.clone();
|
|
||||||
let sizes = state.file_sizes.clone();
|
|
||||||
let stack_ref = stack.clone();
|
|
||||||
uri_drop.connect_drop(move |_target, value, _x, _y| {
|
|
||||||
if let Ok(text) = value.get::<glib::GString>() {
|
|
||||||
let text = text.trim().to_string();
|
|
||||||
// Check if it looks like an image URL
|
|
||||||
let lower = text.to_lowercase();
|
|
||||||
let is_image_url = (lower.starts_with("http://") || lower.starts_with("https://"))
|
|
||||||
&& (lower.ends_with(".jpg")
|
|
||||||
|| lower.ends_with(".jpeg")
|
|
||||||
|| lower.ends_with(".png")
|
|
||||||
|| lower.ends_with(".webp")
|
|
||||||
|| lower.ends_with(".gif")
|
|
||||||
|| lower.ends_with(".avif")
|
|
||||||
|| lower.ends_with(".tiff")
|
|
||||||
|| lower.ends_with(".bmp")
|
|
||||||
|| lower.contains(".jpg?")
|
|
||||||
|| lower.contains(".jpeg?")
|
|
||||||
|| lower.contains(".png?")
|
|
||||||
|| lower.contains(".webp?"));
|
|
||||||
|
|
||||||
if is_image_url {
|
|
||||||
let loaded = loaded_files.clone();
|
|
||||||
let excl = excluded.clone();
|
|
||||||
let sz = sizes.clone();
|
|
||||||
let sr = stack_ref.clone();
|
|
||||||
// Download in background thread
|
|
||||||
let (tx, rx) = std::sync::mpsc::channel::<Option<std::path::PathBuf>>();
|
|
||||||
let url = text.clone();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
let result = download_image_url(&url);
|
|
||||||
let _ = tx.send(result);
|
|
||||||
});
|
|
||||||
|
|
||||||
glib::timeout_add_local(std::time::Duration::from_millis(100), move || {
|
|
||||||
match rx.try_recv() {
|
|
||||||
Ok(Some(path)) => {
|
|
||||||
let mut files = loaded.borrow_mut();
|
|
||||||
if !files.contains(&path) {
|
|
||||||
files.push(path);
|
|
||||||
}
|
|
||||||
let count = files.len();
|
|
||||||
drop(files);
|
|
||||||
refresh_grid(&sr, &loaded, &excl, &sz, count);
|
|
||||||
glib::ControlFlow::Break
|
|
||||||
}
|
|
||||||
Ok(None) => glib::ControlFlow::Break,
|
|
||||||
Err(std::sync::mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue,
|
|
||||||
Err(_) => glib::ControlFlow::Break,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
stack.add_controller(uri_drop);
|
|
||||||
|
|
||||||
adw::NavigationPage::builder()
|
adw::NavigationPage::builder()
|
||||||
.title("Add Images")
|
.title("Add Images")
|
||||||
.tag("step-images")
|
.tag("step-images")
|
||||||
|
|||||||
Reference in New Issue
Block a user