Repository: pop-os/popsicle (language: rust)

Issue:
Drag and Drop Support
It should be possible to drag an ISO directly into the image view to designate it as the ISO to flash with.


Discussion / hints:

Fix patch:
diff --git a/gtk/src/app/misc.rs b/gtk/src/app/misc.rs
index 6187c9a..7679ed4 100644
--- a/gtk/src/app/misc.rs
+++ b/gtk/src/app/misc.rs
@@ -1,4 +1,6 @@
-use gtk::WidgetExt;
+// use gdk::{self, DragContextExt, DragContextExtManual};
+use gdk::{self, DragContextExt, DragContextExtManual};
+use gtk::{self, Inhibit, SelectionData, WidgetExt, WidgetExtManual};
 
 pub fn set_margins<W: WidgetExt>(widget: &W, value: i32) {
     widget.set_margin_top(value);
@@ -6,3 +8,31 @@ pub fn set_margins<W: WidgetExt>(widget: &W, value: i32) {
     widget.set_margin_left(value);
     widget.set_margin_right(value);
 }
+
+// Implements drag and drop support for a GTK widget.
+pub fn drag_and_drop<W, F>(widget: &W, action: F)
+where W: WidgetExt + WidgetExtManual,
+      F: 'static + Fn(&SelectionData)
+{
+    // Configure the view as a possible drop destination.
+    widget.drag_dest_set(gtk::DestDefaults::empty(), &[], gdk::DragAction::empty());
+
+    // Then actually handle drags that are inside the view.
+    widget.connect_drag_motion(|_view, ctx, _x, _y, time| {
+        ctx.drag_status(gdk::DragAction::COPY, time);
+        Inhibit(true)
+    });
+
+    // Get the dropped data, if possible, when the active drag is valid.
+    widget.connect_drag_drop(|view, ctx, _x, _y, time| {
+        Inhibit(ctx.list_targets().last().map_or(false, |ref target| {
+            view.drag_get_data(ctx, target, time);
+            true
+        }))
+    });
+
+    // Then handle the dropped data, setting the image if the dropped data is valid.
+    widget.connect_drag_data_received(move |_view, _ctx, _x, _y, data, _info, _time| {
+        action(data)
+    });
+}
diff --git a/gtk/src/app/mod.rs b/gtk/src/app/mod.rs
index d13ee8c..31b6728 100644
--- a/gtk/src/app/mod.rs
+++ b/gtk/src/app/mod.rs
@@ -100,6 +100,24 @@ pub struct AppWidgets {
 }
 
 impl AppWidgets {
+    pub fn set_image(&self, state: &State, image: &Path) {
+        let next = self.header.next.clone();
+        let image_label = self.content.image_view.image_path.clone();
+        let hash_button = self.content.image_view.hash.clone();
+
+        // TODO: Write an error message on failure.
+        if let Ok(file) = File::open(image) {
+            if let Ok(size) = file.metadata().map(|m| m.len() as usize) {
+                image_label.set_text(&image.file_name()
+                    .expect("file chooser can't select directories")
+                    .to_string_lossy());
+                *state.image.write().unwrap() = Some((image.to_path_buf(), size));
+                next.set_sensitive(true);
+                hash_button.set_sensitive(true);
+            }
+        }
+    }
+
     pub fn switch_to_main(&self, state: &State) {
         let stack = &self.content.container;
         let back = &self.header.back;
diff --git a/gtk/src/app/state.rs b/gtk/src/app/state.rs
index f7ddfbf..fc1bdf1 100644
--- a/gtk/src/app/state.rs
+++ b/gtk/src/app/state.rs
@@ -1,13 +1,13 @@
 use flash::FlashRequest;
 
 use super::{App, OpenDialog};
-use app::AppWidgets;
+use app::{misc, AppWidgets};
 use hash::HashState;
 use std::io;
 use std::fs::File;
 use std::cell::{Cell, RefCell};
 use std::mem;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::mpsc::{Sender, Receiver};
 use std::sync::{Arc, Mutex, RwLock};
@@ -116,6 +116,9 @@ pub trait Connect {
     /// Programs the button for selecting an image.
     fn connect_image_chooser(&self);
 
+    /// Sets the image via a drag and drop.
+    fn connect_image_drag_and_drop(&self);
+
     /// Programs the combo box which generates the hash sum for initial image selection view.
     fn connect_hash_generator(&self);
 
@@ -137,6 +140,7 @@ pub trait Connect {
 impl Connect for App {
     fn connect_events(self) -> Connected {
         self.connect_image_chooser();
+        self.connect_image_drag_and_drop();
         self.connect_hash_generator();
         self.connect_back_button();
         self.connect_next_button();
@@ -148,20 +152,25 @@ impl Connect for App {
 
     fn connect_image_chooser(&self) {
         let state = self.state.clone();
-        let next = self.widgets.header.next.clone();
-        let image_label = self.widgets.content.image_view.image_path.clone();
-        let hash_button = self.widgets.content.image_view.hash.clone();
+        let widgets = self.widgets.clone();
         self.widgets.content.image_view.chooser.connect_clicked(move |_| {
             if let Some(path) = OpenDialog::new(None).run() {
-                // TODO: Write an error message on failure.
-                if let Ok(file) = File::open(&path) {
-                    if let Ok(size) = file.metadata().map(|m| m.len() as usize) {
-                        image_label.set_text(&path.file_name()
-                            .expect("file chooser can't select directories")
-                            .to_string_lossy());
-                        *state.image.write().unwrap() = Some((path, size));
-                        next.set_sensitive(true);
-                        hash_button.set_sensitive(true);
+                widgets.set_image(&state, &path);
+            }
+        });
+    }
+
+    fn connect_image_drag_and_drop(&self) {
+        let state = self.state.clone();
+        let widgets = self.widgets.clone();
+        let image_view = widgets.content.image_view.view.container.clone();
+
+        misc::drag_and_drop(&image_view, move |data| {
+            if let Some(uri) = data.get_text() {
+                if uri.starts_with("file://") {
+                    let path = Path::new(&uri[7..uri.len() - 1]);
+                    if path.extension().map_or(false, |ext| ext == "iso" || ext == "img") && path.exists() {
+                        widgets.set_image(&state, path);
                     }
                 }
             }
diff --git a/gtk/src/main.rs b/gtk/src/main.rs
index 16dc877..f53b06c 100644
--- a/gtk/src/main.rs
+++ b/gtk/src/main.rs
@@ -21,7 +21,7 @@ mod hash;
 use app::{App, Connect};
 use hash::HashState;
 use std::env;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use std::thread;
 use std::io;
 use std::time::Duration;
@@ -73,12 +73,9 @@ fn main() {
     let app = App::new(hash_state, hash_tx, devices_request, devices_response, flash_request, flash_response);
 
     if let Some(iso_argument) = env::args().nth(1) {
-        let path = PathBuf::from(iso_argument);
-        // TODO: Write an error message on failure.
-        if let Ok(file) = File::open(&path) {
-            if let Ok(size) = file.metadata().map(|m| m.len() as usize) {
-                *app.state.image.write().unwrap() = Some((path, size));
-            }
+        let path: &Path = &PathBuf::from(iso_argument);
+        if path.extension().map_or(false, |ext| ext == "iso" || ext == "img") && path.exists() {
+            app.widgets.set_image(&app.state, path);
         }
     }
 


Test patch: