Skip to content

Commit

Permalink
Merge branch 'huanie-huan/listview'
Browse files Browse the repository at this point in the history
  • Loading branch information
nik012003 committed Aug 7, 2023
2 parents aff0142 + 1797c3e commit cfc07dc
Show file tree
Hide file tree
Showing 8 changed files with 891 additions and 474 deletions.
514 changes: 302 additions & 212 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ exclude = ["/.vscode"]

[dependencies]
clap = { version = "4.1.8", features = ["derive"] }
gtk = { version = "0.6.2", package = "gtk4", features = ["v4_6"] }
gtk = { version = "0.6.6", package = "gtk4", features = ["v4_6"] }
glib-macros = "0.18.0"
infer = "0.13.0"
opener = "0.5.2"
url = "2.3.1"
Expand Down
111 changes: 111 additions & 0 deletions src/compact_view.rs
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(
&gtk::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 {}
}
134 changes: 134 additions & 0 deletions src/file_object.rs
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 {}
}

0 comments on commit cfc07dc

Please sign in to comment.