-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
891 additions
and
474 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
use crate::ARGS; | ||
use glib::Object; | ||
use gtk::gio::ListStore; | ||
use gtk::prelude::*; | ||
use gtk::subclass::prelude::*; | ||
use gtk::{glib, CssProvider, DragSource, Label, Widget}; | ||
|
||
use crate::util::{generate_file_model, setup_drag_source_all, setup_drop_target, ListWidget}; | ||
|
||
pub fn generate_compact_view() -> ListWidget { | ||
let file_model = generate_file_model(); | ||
|
||
let drag_source = DragSource::new(); | ||
setup_drag_source_all(&drag_source, &file_model); | ||
|
||
let obj = CompactLabel::new(file_model); | ||
let model = obj.model(); | ||
let obj = obj.upcast::<Widget>(); | ||
|
||
// styling | ||
let provider = CssProvider::new(); | ||
provider.load_from_data(include_str!("style.css")); | ||
gtk::style_context_add_provider_for_display( | ||
>k::gdk::Display::default().expect("Could not connect to a display."), | ||
&provider, | ||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, | ||
); | ||
obj.add_css_class("drag"); | ||
obj.set_cursor_from_name(Some("grab")); | ||
|
||
obj.add_controller(drag_source); | ||
if ARGS.get().unwrap().target { | ||
setup_drop_target(&model, &obj); | ||
} | ||
|
||
ListWidget { | ||
list_model: model, | ||
widget: obj, | ||
} | ||
} | ||
|
||
glib::wrapper! { | ||
pub struct CompactLabel(ObjectSubclass<imp::CompactLabel>) | ||
@extends gtk::Box, gtk::Widget, | ||
@implements gtk::Accessible, gtk::Orientable, gtk::Buildable, gtk::ConstraintTarget; | ||
} | ||
|
||
// This is necessary to keep the model alive, otherwise it will be dropped. | ||
impl CompactLabel { | ||
pub fn new(model: ListStore) -> Self { | ||
let create_string = |arg| format!("{} elements", arg); | ||
let obj: Self = Object::builder().property("model", model).build(); | ||
let label = Label::builder() | ||
.label(create_string(obj.model().n_items())) | ||
.ellipsize(gtk::pango::EllipsizeMode::End) | ||
.tooltip_text(format!("Drag {}", create_string(obj.model().n_items()))) | ||
.vexpand(true) | ||
.hexpand(true) | ||
.build(); | ||
obj.set_label(label); | ||
|
||
obj.append(&obj.label()); | ||
obj.model() | ||
.bind_property("n-items", &obj.label(), "label") | ||
.transform_to(|_, item_count: u32| Some(format!("{} elements", item_count))) | ||
.build(); | ||
obj | ||
} | ||
} | ||
|
||
mod imp { | ||
use std::cell::RefCell; | ||
|
||
use gtk::glib::Properties; | ||
use gtk::{Align, Orientation}; | ||
|
||
use super::*; | ||
|
||
#[derive(Default, Properties)] | ||
#[properties(wrapper_type = super::CompactLabel)] | ||
pub struct CompactLabel { | ||
#[property(get, construct_only)] | ||
pub model: RefCell<ListStore>, | ||
#[property(get, set)] | ||
pub label: RefCell<Label>, | ||
} | ||
|
||
#[glib::object_subclass] | ||
impl ObjectSubclass for CompactLabel { | ||
const NAME: &'static str = "RipDragCompactLabel"; | ||
type Type = super::CompactLabel; | ||
type ParentType = gtk::Box; | ||
} | ||
|
||
#[glib_macros::derived_properties] | ||
impl ObjectImpl for CompactLabel { | ||
fn constructed(&self) { | ||
self.parent_constructed(); | ||
let obj = self.obj(); | ||
obj.set_halign(Align::Fill); | ||
obj.set_valign(Align::Fill); | ||
obj.set_hexpand(true); | ||
obj.set_vexpand(true); | ||
obj.set_orientation(Orientation::Vertical); | ||
} | ||
} | ||
|
||
impl WidgetImpl for CompactLabel {} | ||
|
||
impl BoxImpl for CompactLabel {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
use gio::FileQueryInfoFlags; | ||
use glib::{GString, MainContext, Object, Priority, Properties}; | ||
use glib_macros::clone; | ||
use gtk::gdk_pixbuf::Pixbuf; | ||
use gtk::prelude::*; | ||
use gtk::subclass::prelude::*; | ||
use gtk::{gdk, gdk_pixbuf, gio, glib}; | ||
|
||
use crate::ARGS; | ||
glib::wrapper! { | ||
pub struct FileObject(ObjectSubclass<imp::FileObject>); | ||
} | ||
|
||
trait MimeType { | ||
fn mime_type(&self) -> GString; | ||
} | ||
|
||
impl MimeType for gio::File { | ||
fn mime_type(&self) -> GString { | ||
let file_type = self.query_info( | ||
gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, | ||
FileQueryInfoFlags::NONE, | ||
gio::Cancellable::NONE, | ||
); | ||
|
||
file_type | ||
.unwrap_or_default() | ||
.content_type() | ||
.unwrap_or(GString::format(format_args!("text/plain"))) | ||
} | ||
} | ||
|
||
impl FileObject { | ||
pub fn new(file: &gio::File) -> Self { | ||
let obj = Object::builder().property("file", file); | ||
let icon_name = gio::content_type_get_generic_icon_name(&file.mime_type()); | ||
// use the default thumbnail | ||
let image = gtk::Image::builder() | ||
.icon_name(icon_name.unwrap_or(glib::GString::format(format_args!("text/default")))) | ||
.pixel_size(ARGS.get().unwrap().icon_size) | ||
.build(); | ||
let obj = obj.property("thumbnail", image).build(); | ||
let file = file.clone(); | ||
|
||
// For every image a thumbnail of the image is sent. When it is not an image a None is sent. | ||
// There is no thumbnail for GIFs. | ||
let (sender, receiver) = MainContext::channel(Priority::default()); | ||
gio::spawn_blocking(move || { | ||
let print_err = |err| eprintln!("{}", err); | ||
let mime_type = file.mime_type(); | ||
// this only works for images | ||
if !ARGS.get().unwrap().disable_thumbnails | ||
&& gio::content_type_is_mime_type(&mime_type, "image/*") | ||
{ | ||
let image = gdk_pixbuf::Pixbuf::from_file_at_scale( | ||
file.path().unwrap(), | ||
ARGS.get().unwrap().icon_size, | ||
-1, | ||
true, | ||
); | ||
if let Ok(image) = image { | ||
let _ = sender | ||
.send(Some(( | ||
image.read_pixel_bytes(), | ||
image.colorspace(), | ||
image.has_alpha(), | ||
image.bits_per_sample(), | ||
image.width(), | ||
image.height(), | ||
image.rowstride(), | ||
))) | ||
.map_err(print_err); | ||
} else { | ||
eprintln!("{}", image.unwrap_err()); | ||
let _ = sender.send(None).map_err(print_err); | ||
} | ||
} else { | ||
let _ = sender.send(None).map_err(print_err); | ||
} | ||
}); | ||
// Sets the thumbnail and closes the receiver channel regardless of what was sent. | ||
receiver.attach( | ||
None, | ||
clone!(@weak obj => @default-return | ||
glib::Continue(false), move |image| { | ||
if let Some(image) = image { | ||
let obj: FileObject = obj; | ||
let thumbnail = obj.thumbnail(); | ||
// (apply gdk_pixbuf::Pixbuf::from_bytes image) | ||
let image = Pixbuf::from_bytes(&image.0, image.1, image.2, image.3, image.4, image.5, image.6); | ||
thumbnail.set_from_paintable(Some(&gdk::Texture::for_pixbuf(&image))); | ||
} | ||
glib::Continue(false) | ||
}), | ||
); | ||
|
||
obj | ||
} | ||
} | ||
|
||
mod imp { | ||
use std::cell::RefCell; | ||
|
||
use super::*; | ||
|
||
#[derive(Properties)] | ||
#[properties(wrapper_type = super::FileObject)] | ||
pub struct FileObject { | ||
#[property(get, construct_only)] | ||
file: RefCell<gio::File>, | ||
#[property(get, construct_only)] | ||
thumbnail: RefCell<gtk::Image>, | ||
} | ||
|
||
impl Default for FileObject { | ||
fn default() -> Self { | ||
Self { | ||
file: RefCell::new(gio::File::for_path("/does-not-exist")), | ||
thumbnail: RefCell::new(gtk::Image::default()), | ||
} | ||
} | ||
} | ||
|
||
#[glib::object_subclass] | ||
impl ObjectSubclass for FileObject { | ||
const NAME: &'static str = "RipDragFileObject"; | ||
type Type = super::FileObject; | ||
type ParentType = glib::Object; | ||
} | ||
|
||
// Trait shared by all GObjects | ||
#[glib_macros::derived_properties] | ||
impl ObjectImpl for FileObject {} | ||
} |
Oops, something went wrong.