Turn the browser into a DnD source
The destination does all the work of handling file operations. Also, add some missing logic for horizontal scrolling.
This commit is contained in:
parent
31f9feab7b
commit
580b68789b
113
fiv-browser.c
113
fiv-browser.c
|
@ -68,6 +68,10 @@ struct _FivBrowser {
|
||||||
GArray *layouted_rows; ///< []Row
|
GArray *layouted_rows; ///< []Row
|
||||||
const Entry *selected; ///< Selected entry or NULL
|
const Entry *selected; ///< Selected entry or NULL
|
||||||
|
|
||||||
|
guint tracked_button; ///< Pressed mouse button number or 0
|
||||||
|
double drag_begin_x; ///< Viewport start X coordinate or -1
|
||||||
|
double drag_begin_y; ///< Viewport start Y coordinate or -1
|
||||||
|
|
||||||
Thumbnailer *thumbnailers; ///< Parallelized thumbnailers
|
Thumbnailer *thumbnailers; ///< Parallelized thumbnailers
|
||||||
size_t thumbnailers_len; ///< Thumbnailers array size
|
size_t thumbnailers_len; ///< Thumbnailers array size
|
||||||
GList *thumbnailers_queue; ///< Queued up Entry pointers
|
GList *thumbnailers_queue; ///< Queued up Entry pointers
|
||||||
|
@ -132,6 +136,13 @@ append_row(FivBrowser *self, int *y, int x, GArray *items_array)
|
||||||
*y += self->item_border_y;
|
*y += self->item_border_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
abort_button_tracking(FivBrowser *self)
|
||||||
|
{
|
||||||
|
self->tracked_button = 0;
|
||||||
|
self->drag_begin_x = self->drag_begin_y = -1;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
relayout(FivBrowser *self, int width)
|
relayout(FivBrowser *self, int width)
|
||||||
{
|
{
|
||||||
|
@ -143,6 +154,8 @@ relayout(FivBrowser *self, int width)
|
||||||
int available_width = width - padding.left - padding.right;
|
int available_width = width - padding.left - padding.right;
|
||||||
|
|
||||||
g_array_set_size(self->layouted_rows, 0);
|
g_array_set_size(self->layouted_rows, 0);
|
||||||
|
// Whatever these used to point at might no longer be there.
|
||||||
|
abort_button_tracking(self);
|
||||||
|
|
||||||
GArray *items = g_array_new(TRUE, TRUE, sizeof(Item));
|
GArray *items = g_array_new(TRUE, TRUE, sizeof(Item));
|
||||||
int x = 0, y = padding.top;
|
int x = 0, y = padding.top;
|
||||||
|
@ -242,6 +255,8 @@ item_extents(FivBrowser *self, const Item *item, const Row *row)
|
||||||
static const Entry *
|
static const Entry *
|
||||||
entry_at(FivBrowser *self, int x, int y)
|
entry_at(FivBrowser *self, int x, int y)
|
||||||
{
|
{
|
||||||
|
if (self->hadjustment)
|
||||||
|
x += round(gtk_adjustment_get_value(self->hadjustment));
|
||||||
if (self->vadjustment)
|
if (self->vadjustment)
|
||||||
y += round(gtk_adjustment_get_value(self->vadjustment));
|
y += round(gtk_adjustment_get_value(self->vadjustment));
|
||||||
|
|
||||||
|
@ -272,6 +287,8 @@ entry_rect(FivBrowser *self, const Entry *entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (self->hadjustment)
|
||||||
|
rect.x -= round(gtk_adjustment_get_value(self->hadjustment));
|
||||||
if (self->vadjustment)
|
if (self->vadjustment)
|
||||||
rect.y -= round(gtk_adjustment_get_value(self->vadjustment));
|
rect.y -= round(gtk_adjustment_get_value(self->vadjustment));
|
||||||
return rect;
|
return rect;
|
||||||
|
@ -1088,11 +1105,12 @@ fiv_browser_draw(GtkWidget *widget, cairo_t *cr)
|
||||||
gtk_render_background(gtk_widget_get_style_context(widget), cr, 0, 0,
|
gtk_render_background(gtk_widget_get_style_context(widget), cr, 0, 0,
|
||||||
allocation.width, allocation.height);
|
allocation.width, allocation.height);
|
||||||
|
|
||||||
// TODO(p): self->hadjustment as well, and test it.
|
if (self->hadjustment)
|
||||||
if (self->vadjustment) {
|
cairo_translate(
|
||||||
gdouble y = round(gtk_adjustment_get_value(self->vadjustment));
|
cr, -round(gtk_adjustment_get_value(self->hadjustment)), 0);
|
||||||
cairo_translate(cr, 0, -y);
|
if (self->vadjustment)
|
||||||
}
|
cairo_translate(
|
||||||
|
cr, 0, -round(gtk_adjustment_get_value(self->vadjustment)));
|
||||||
|
|
||||||
GdkRectangle clip = {};
|
GdkRectangle clip = {};
|
||||||
gboolean have_clip = gdk_cairo_get_clip_rectangle(cr, &clip);
|
gboolean have_clip = gdk_cairo_get_clip_rectangle(cr, &clip);
|
||||||
|
@ -1124,10 +1142,15 @@ open_entry(GtkWidget *self, const Entry *entry, gboolean new_window)
|
||||||
static gboolean
|
static gboolean
|
||||||
fiv_browser_button_press_event(GtkWidget *widget, GdkEventButton *event)
|
fiv_browser_button_press_event(GtkWidget *widget, GdkEventButton *event)
|
||||||
{
|
{
|
||||||
|
FivBrowser *self = FIV_BROWSER(widget);
|
||||||
GTK_WIDGET_CLASS(fiv_browser_parent_class)
|
GTK_WIDGET_CLASS(fiv_browser_parent_class)
|
||||||
->button_press_event(widget, event);
|
->button_press_event(widget, event);
|
||||||
|
|
||||||
FivBrowser *self = FIV_BROWSER(widget);
|
// Make pressing multiple mouse buttons at once cancel a click.
|
||||||
|
if (self->tracked_button) {
|
||||||
|
abort_button_tracking(self);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
if (event->type != GDK_BUTTON_PRESS)
|
if (event->type != GDK_BUTTON_PRESS)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
|
@ -1166,18 +1189,33 @@ fiv_browser_button_press_event(GtkWidget *widget, GdkEventButton *event)
|
||||||
g_object_unref(file);
|
g_object_unref(file);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gtk_drag_source_set() would span the whole widget area, we'd have to
|
||||||
|
// un/set it as needed, in particular to handle empty space.
|
||||||
|
// It might be a good idea to use GtkGestureDrag instead.
|
||||||
|
if (event->button == GDK_BUTTON_PRIMARY ||
|
||||||
|
event->button == GDK_BUTTON_MIDDLE) {
|
||||||
|
self->tracked_button = event->button;
|
||||||
|
self->drag_begin_x = event->x;
|
||||||
|
self->drag_begin_y = event->y;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
fiv_browser_button_release_event(GtkWidget *widget, GdkEventButton *event)
|
fiv_browser_button_release_event(GtkWidget *widget, GdkEventButton *event)
|
||||||
{
|
{
|
||||||
|
FivBrowser *self = FIV_BROWSER(widget);
|
||||||
GTK_WIDGET_CLASS(fiv_browser_parent_class)
|
GTK_WIDGET_CLASS(fiv_browser_parent_class)
|
||||||
->button_release_event(widget, event);
|
->button_release_event(widget, event);
|
||||||
|
if (event->button != self->tracked_button)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
FivBrowser *self = FIV_BROWSER(widget);
|
// Middle clicks should only work on the starting entry.
|
||||||
const Entry *entry = entry_at(self, event->x, event->y);
|
const Entry *entry = entry_at(self, self->drag_begin_x, self->drag_begin_y);
|
||||||
if (!entry)
|
abort_button_tracking(self);
|
||||||
|
if (!entry || entry != entry_at(self, event->x, event->y))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
guint state = event->state & gtk_accelerator_get_default_mod_mask();
|
guint state = event->state & gtk_accelerator_get_default_mod_mask();
|
||||||
|
@ -1192,16 +1230,37 @@ fiv_browser_button_release_event(GtkWidget *widget, GdkEventButton *event)
|
||||||
static gboolean
|
static gboolean
|
||||||
fiv_browser_motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
|
fiv_browser_motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
|
||||||
{
|
{
|
||||||
|
FivBrowser *self = FIV_BROWSER(widget);
|
||||||
GTK_WIDGET_CLASS(fiv_browser_parent_class)
|
GTK_WIDGET_CLASS(fiv_browser_parent_class)
|
||||||
->motion_notify_event(widget, event);
|
->motion_notify_event(widget, event);
|
||||||
|
|
||||||
FivBrowser *self = FIV_BROWSER(widget);
|
guint state = event->state & gtk_accelerator_get_default_mod_mask();
|
||||||
if (event->state != 0)
|
if (state != 0 || self->tracked_button != GDK_BUTTON_PRIMARY ||
|
||||||
|
!gtk_drag_check_threshold(widget, self->drag_begin_x,
|
||||||
|
self->drag_begin_y, event->x, event->y)) {
|
||||||
|
const Entry *entry = entry_at(self, event->x, event->y);
|
||||||
|
GdkWindow *window = gtk_widget_get_window(widget);
|
||||||
|
gdk_window_set_cursor(window, entry ? self->pointer : NULL);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
const Entry *entry = entry_at(self, event->x, event->y);
|
// The "correct" behaviour is to set the selection on a left mouse button
|
||||||
GdkWindow *window = gtk_widget_get_window(widget);
|
// press immediately, but that is regarded as visual noise.
|
||||||
gdk_window_set_cursor(window, entry ? self->pointer : NULL);
|
const Entry *entry = entry_at(self, self->drag_begin_x, self->drag_begin_y);
|
||||||
|
abort_button_tracking(self);
|
||||||
|
if (!entry)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
self->selected = entry;
|
||||||
|
gtk_widget_queue_draw(widget);
|
||||||
|
|
||||||
|
GtkTargetList *target_list = gtk_target_list_new(NULL, 0);
|
||||||
|
gtk_target_list_add_uri_targets(target_list, 0);
|
||||||
|
GdkDragAction actions = GDK_ACTION_COPY | GDK_ACTION_MOVE |
|
||||||
|
GDK_ACTION_LINK | GDK_ACTION_ASK;
|
||||||
|
gtk_drag_begin_with_coordinates(widget, target_list, actions,
|
||||||
|
self->tracked_button, (GdkEvent *) event, event->x, event->y);
|
||||||
|
gtk_target_list_unref(target_list);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1209,6 +1268,7 @@ static gboolean
|
||||||
fiv_browser_scroll_event(GtkWidget *widget, GdkEventScroll *event)
|
fiv_browser_scroll_event(GtkWidget *widget, GdkEventScroll *event)
|
||||||
{
|
{
|
||||||
FivBrowser *self = FIV_BROWSER(widget);
|
FivBrowser *self = FIV_BROWSER(widget);
|
||||||
|
abort_button_tracking(self);
|
||||||
if ((event->state & gtk_accelerator_get_default_mod_mask()) !=
|
if ((event->state & gtk_accelerator_get_default_mod_mask()) !=
|
||||||
GDK_CONTROL_MASK)
|
GDK_CONTROL_MASK)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
@ -1227,6 +1287,28 @@ fiv_browser_scroll_event(GtkWidget *widget, GdkEventScroll *event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fiv_browser_drag_begin(GtkWidget *widget, GdkDragContext *context)
|
||||||
|
{
|
||||||
|
FivBrowser *self = FIV_BROWSER(widget);
|
||||||
|
if (self->selected) {
|
||||||
|
// There doesn't seem to be a size limit.
|
||||||
|
gtk_drag_set_icon_surface(context, self->selected->thumbnail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fiv_browser_drag_data_get(GtkWidget *widget,
|
||||||
|
G_GNUC_UNUSED GdkDragContext *context, GtkSelectionData *data,
|
||||||
|
G_GNUC_UNUSED guint info, G_GNUC_UNUSED guint time)
|
||||||
|
{
|
||||||
|
FivBrowser *self = FIV_BROWSER(widget);
|
||||||
|
if (self->selected) {
|
||||||
|
(void) gtk_selection_data_set_uris(
|
||||||
|
data, (gchar *[]){self->selected->uri, NULL});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
select_closest(FivBrowser *self, const Row *row, int target)
|
select_closest(FivBrowser *self, const Row *row, int target)
|
||||||
{
|
{
|
||||||
|
@ -1561,6 +1643,8 @@ fiv_browser_class_init(FivBrowserClass *klass)
|
||||||
widget_class->button_press_event = fiv_browser_button_press_event;
|
widget_class->button_press_event = fiv_browser_button_press_event;
|
||||||
widget_class->button_release_event = fiv_browser_button_release_event;
|
widget_class->button_release_event = fiv_browser_button_release_event;
|
||||||
widget_class->motion_notify_event = fiv_browser_motion_notify_event;
|
widget_class->motion_notify_event = fiv_browser_motion_notify_event;
|
||||||
|
widget_class->drag_begin = fiv_browser_drag_begin;
|
||||||
|
widget_class->drag_data_get = fiv_browser_drag_data_get;
|
||||||
widget_class->scroll_event = fiv_browser_scroll_event;
|
widget_class->scroll_event = fiv_browser_scroll_event;
|
||||||
widget_class->key_press_event = fiv_browser_key_press_event;
|
widget_class->key_press_event = fiv_browser_key_press_event;
|
||||||
widget_class->query_tooltip = fiv_browser_query_tooltip;
|
widget_class->query_tooltip = fiv_browser_query_tooltip;
|
||||||
|
@ -1588,6 +1672,7 @@ fiv_browser_init(FivBrowser *self)
|
||||||
g_array_set_clear_func(self->entries, (GDestroyNotify) entry_free);
|
g_array_set_clear_func(self->entries, (GDestroyNotify) entry_free);
|
||||||
self->layouted_rows = g_array_new(FALSE, TRUE, sizeof(Row));
|
self->layouted_rows = g_array_new(FALSE, TRUE, sizeof(Row));
|
||||||
g_array_set_clear_func(self->layouted_rows, (GDestroyNotify) row_free);
|
g_array_set_clear_func(self->layouted_rows, (GDestroyNotify) row_free);
|
||||||
|
abort_button_tracking(self);
|
||||||
|
|
||||||
self->thumbnailers_len = g_get_num_processors();
|
self->thumbnailers_len = g_get_num_processors();
|
||||||
self->thumbnailers =
|
self->thumbnailers =
|
||||||
|
|
Loading…
Reference in New Issue