Add a customized sidebar widget
Slowly eliminating all potential uses of GTK+'s standalone file open dialog, which is highly duplicitous.
This commit is contained in:
		
							
								
								
									
										351
									
								
								fastiv-sidebar.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										351
									
								
								fastiv-sidebar.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,351 @@
 | 
			
		||||
//
 | 
			
		||||
// fastiv-sidebar.c: molesting GtkPlacesSidebar
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2021, Přemysl Eric Janouch <p@janouch.name>
 | 
			
		||||
//
 | 
			
		||||
// Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		||||
// purpose with or without fee is hereby granted.
 | 
			
		||||
//
 | 
			
		||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 | 
			
		||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 | 
			
		||||
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 | 
			
		||||
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 | 
			
		||||
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <gtk/gtk.h>
 | 
			
		||||
 | 
			
		||||
#include "fastiv-sidebar.h"
 | 
			
		||||
 | 
			
		||||
struct _FastivSidebar {
 | 
			
		||||
	GtkScrolledWindow parent_instance;
 | 
			
		||||
	GtkPlacesSidebar *places;
 | 
			
		||||
	GtkWidget *listbox;
 | 
			
		||||
	GFile *location;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
G_DEFINE_TYPE(FastivSidebar, fastiv_sidebar, GTK_TYPE_SCROLLED_WINDOW)
 | 
			
		||||
 | 
			
		||||
G_DEFINE_QUARK(fastiv-sidebar-location-quark, fastiv_sidebar_location)
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
	OPEN_LOCATION,
 | 
			
		||||
	LAST_SIGNAL,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Globals are, sadly, the canonical way of storing signal numbers.
 | 
			
		||||
static guint sidebar_signals[LAST_SIGNAL];
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
fastiv_sidebar_dispose(GObject *gobject)
 | 
			
		||||
{
 | 
			
		||||
	FastivSidebar *self = FASTIV_SIDEBAR(gobject);
 | 
			
		||||
	g_clear_object(&self->location);
 | 
			
		||||
 | 
			
		||||
	G_OBJECT_CLASS(fastiv_sidebar_parent_class)->dispose(gobject);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
fastiv_sidebar_class_init(FastivSidebarClass *klass)
 | 
			
		||||
{
 | 
			
		||||
	GObjectClass *object_class = G_OBJECT_CLASS(klass);
 | 
			
		||||
	object_class->dispose = fastiv_sidebar_dispose;
 | 
			
		||||
 | 
			
		||||
	// You're giving me no choice, Adwaita.
 | 
			
		||||
	// Your style is hardcoded to match against the class' CSS name.
 | 
			
		||||
	// And I need replicate the internal widget structure.
 | 
			
		||||
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
 | 
			
		||||
	gtk_widget_class_set_css_name(widget_class, "placessidebar");
 | 
			
		||||
 | 
			
		||||
	// TODO(p): Consider a return value, and using it.
 | 
			
		||||
	sidebar_signals[OPEN_LOCATION] =
 | 
			
		||||
		g_signal_new("open_location", G_TYPE_FROM_CLASS(klass), 0, 0,
 | 
			
		||||
			NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_FILE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static GtkWidget *
 | 
			
		||||
create_row(GFile *file, const char *icon_name)
 | 
			
		||||
{
 | 
			
		||||
	// TODO(p): Handle errors better.
 | 
			
		||||
	GFileInfo *info =
 | 
			
		||||
		g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
 | 
			
		||||
			G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
 | 
			
		||||
	if (!info)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	const char *name = g_file_info_get_display_name(info);
 | 
			
		||||
	GtkWidget *rowbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *rowimage =
 | 
			
		||||
		gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_MENU);
 | 
			
		||||
	gtk_style_context_add_class(
 | 
			
		||||
		gtk_widget_get_style_context(rowimage), "sidebar-icon");
 | 
			
		||||
	gtk_container_add(GTK_CONTAINER(rowbox), rowimage);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *rowlabel = gtk_label_new(name);
 | 
			
		||||
	gtk_label_set_ellipsize(GTK_LABEL(rowlabel), PANGO_ELLIPSIZE_END);
 | 
			
		||||
	gtk_style_context_add_class(
 | 
			
		||||
		gtk_widget_get_style_context(rowlabel), "sidebar-label");
 | 
			
		||||
	gtk_container_add(GTK_CONTAINER(rowbox), rowlabel);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *revealer = gtk_revealer_new();
 | 
			
		||||
	gtk_revealer_set_reveal_child(
 | 
			
		||||
		GTK_REVEALER(revealer), TRUE);
 | 
			
		||||
	gtk_revealer_set_transition_type(
 | 
			
		||||
		GTK_REVEALER(revealer), GTK_REVEALER_TRANSITION_TYPE_NONE);
 | 
			
		||||
	gtk_container_add(GTK_CONTAINER(revealer), rowbox);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *row = gtk_list_box_row_new();
 | 
			
		||||
	g_object_set_qdata_full(G_OBJECT(row), fastiv_sidebar_location_quark(),
 | 
			
		||||
		g_object_ref(file), (GDestroyNotify) g_object_unref);
 | 
			
		||||
	gtk_container_add(GTK_CONTAINER(row), revealer);
 | 
			
		||||
	gtk_widget_show_all(row);
 | 
			
		||||
	return row;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
update_location(FastivSidebar *self, GFile *location)
 | 
			
		||||
{
 | 
			
		||||
	if (location) {
 | 
			
		||||
		g_clear_object(&self->location);
 | 
			
		||||
		self->location = g_object_ref(location);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gtk_places_sidebar_set_location(self->places, self->location);
 | 
			
		||||
	gtk_container_foreach(GTK_CONTAINER(self->listbox),
 | 
			
		||||
		(GtkCallback) gtk_widget_destroy, NULL);
 | 
			
		||||
	g_return_if_fail(self->location != NULL);
 | 
			
		||||
 | 
			
		||||
	GFile *iter = g_object_ref(self->location);
 | 
			
		||||
	while (TRUE) {
 | 
			
		||||
		GFile *parent = g_file_get_parent(iter);
 | 
			
		||||
		g_object_unref(iter);
 | 
			
		||||
		if (!(iter = parent))
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		gtk_list_box_prepend(GTK_LIST_BOX(self->listbox),
 | 
			
		||||
			create_row(parent, "go-up-symbolic"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Another option would be "folder-open-symbolic",
 | 
			
		||||
	// "*-visiting-*" is mildly inappropriate (means: open in another window).
 | 
			
		||||
	// TODO(p): Try out "circle-filled-symbolic".
 | 
			
		||||
	gtk_container_add(GTK_CONTAINER(self->listbox),
 | 
			
		||||
		create_row(self->location, "folder-visiting-symbolic"));
 | 
			
		||||
 | 
			
		||||
	GFileEnumerator *enumerator = g_file_enumerate_children(self->location,
 | 
			
		||||
		G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME
 | 
			
		||||
		"," G_FILE_ATTRIBUTE_STANDARD_NAME
 | 
			
		||||
		"," G_FILE_ATTRIBUTE_STANDARD_TYPE
 | 
			
		||||
		"," G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
 | 
			
		||||
		G_FILE_QUERY_INFO_NONE, NULL, NULL);
 | 
			
		||||
	if (!enumerator)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	// TODO(p): gtk_list_box_set_sort_func(), gtk_list_box_set_filter_func(),
 | 
			
		||||
	// or even use a model.
 | 
			
		||||
	while (TRUE) {
 | 
			
		||||
		GFileInfo *info = NULL;
 | 
			
		||||
		GFile *child = NULL;
 | 
			
		||||
		if (!g_file_enumerator_iterate(enumerator, &info, &child, NULL, NULL) ||
 | 
			
		||||
			!info)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		if (g_file_info_get_file_type(info) == G_FILE_TYPE_DIRECTORY &&
 | 
			
		||||
			!g_file_info_get_is_hidden(info))
 | 
			
		||||
			gtk_container_add(GTK_CONTAINER(self->listbox),
 | 
			
		||||
				create_row(child, "go-down-symbolic"));
 | 
			
		||||
	}
 | 
			
		||||
	g_object_unref(enumerator);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_open_breadcrumb(
 | 
			
		||||
	G_GNUC_UNUSED GtkListBox *listbox, GtkListBoxRow *row, gpointer user_data)
 | 
			
		||||
{
 | 
			
		||||
	FastivSidebar *self = FASTIV_SIDEBAR(user_data);
 | 
			
		||||
	GFile *location =
 | 
			
		||||
		g_object_get_qdata(G_OBJECT(row), fastiv_sidebar_location_quark());
 | 
			
		||||
	g_signal_emit(self, sidebar_signals[OPEN_LOCATION], 0, location);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_open_location(G_GNUC_UNUSED GtkPlacesSidebar *sidebar, GFile *location,
 | 
			
		||||
	G_GNUC_UNUSED GtkPlacesOpenFlags flags, gpointer user_data)
 | 
			
		||||
{
 | 
			
		||||
	FastivSidebar *self = FASTIV_SIDEBAR(user_data);
 | 
			
		||||
	g_signal_emit(self, sidebar_signals[OPEN_LOCATION], 0, location);
 | 
			
		||||
 | 
			
		||||
	// Deselect the item in GtkPlacesSidebar, if unsuccessful.
 | 
			
		||||
	update_location(self, NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
complete_path(GFile *location, GtkListStore *model)
 | 
			
		||||
{
 | 
			
		||||
	GFile *parent =
 | 
			
		||||
		g_file_query_file_type(location, G_FILE_QUERY_INFO_NONE, NULL)
 | 
			
		||||
		? g_object_ref(location)
 | 
			
		||||
		: g_file_get_parent(location);
 | 
			
		||||
	if (!parent)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	GFileEnumerator *enumerator = g_file_enumerate_children(parent,
 | 
			
		||||
		G_FILE_ATTRIBUTE_STANDARD_NAME,
 | 
			
		||||
		G_FILE_QUERY_INFO_NONE, NULL, NULL);
 | 
			
		||||
	if (!enumerator)
 | 
			
		||||
		goto fail_enumerator;
 | 
			
		||||
 | 
			
		||||
	// TODO(p): Resolve ~ paths a bit better.
 | 
			
		||||
	while (TRUE) {
 | 
			
		||||
		GFileInfo *info = NULL;
 | 
			
		||||
		GFile *child = NULL;
 | 
			
		||||
		if (!g_file_enumerator_iterate(enumerator, &info, &child, NULL, NULL) ||
 | 
			
		||||
			!info)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		GtkTreeIter iter;
 | 
			
		||||
		gtk_list_store_append(model, &iter);
 | 
			
		||||
		gtk_list_store_set(
 | 
			
		||||
			model, &iter, 0, g_file_get_parse_name(child), -1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g_object_unref(enumerator);
 | 
			
		||||
fail_enumerator:
 | 
			
		||||
	g_object_unref(parent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_enter_location_changed(GtkEntry *entry, G_GNUC_UNUSED gpointer user_data)
 | 
			
		||||
{
 | 
			
		||||
	const char *text = gtk_entry_get_text(entry);
 | 
			
		||||
	GFile *location = g_file_parse_name(text);
 | 
			
		||||
 | 
			
		||||
	// Don't touch the network, URIs are a no-no.
 | 
			
		||||
	// FIXME: This uses a different relative root from the fastiv.c opener.
 | 
			
		||||
	GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(entry));
 | 
			
		||||
	if (g_uri_is_valid(text, G_URI_FLAGS_PARSE_RELAXED, NULL) ||
 | 
			
		||||
		g_file_query_exists(location, NULL))
 | 
			
		||||
		gtk_style_context_remove_class(style, GTK_STYLE_CLASS_WARNING);
 | 
			
		||||
	else
 | 
			
		||||
		gtk_style_context_add_class(style, GTK_STYLE_CLASS_WARNING);
 | 
			
		||||
 | 
			
		||||
	GtkListStore *model = gtk_list_store_new(1, G_TYPE_STRING);
 | 
			
		||||
	gtk_tree_sortable_set_sort_column_id(
 | 
			
		||||
		GTK_TREE_SORTABLE(model), 0, GTK_SORT_ASCENDING);
 | 
			
		||||
	if (!g_uri_is_valid(text, G_URI_FLAGS_PARSE_RELAXED, NULL))
 | 
			
		||||
		complete_path(location, model);
 | 
			
		||||
 | 
			
		||||
	// TODO(p): Try to make this not be as jumpy.
 | 
			
		||||
	GtkEntryCompletion *completion = gtk_entry_get_completion(entry);
 | 
			
		||||
	gtk_entry_completion_set_model(completion, NULL);
 | 
			
		||||
	gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model));
 | 
			
		||||
	gtk_entry_completion_set_text_column(completion, 0);
 | 
			
		||||
	gtk_entry_completion_set_inline_completion(completion, TRUE);
 | 
			
		||||
	gtk_entry_completion_set_match_func(
 | 
			
		||||
		completion, (GtkEntryCompletionMatchFunc) gtk_true, NULL, NULL);
 | 
			
		||||
	gtk_entry_completion_complete(completion);
 | 
			
		||||
	g_object_unref(model);
 | 
			
		||||
 | 
			
		||||
	g_object_unref(location);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_show_enter_location(G_GNUC_UNUSED GtkPlacesSidebar *sidebar,
 | 
			
		||||
	G_GNUC_UNUSED gpointer user_data)
 | 
			
		||||
{
 | 
			
		||||
	FastivSidebar *self = FASTIV_SIDEBAR(user_data);
 | 
			
		||||
	GtkWidget *dialog = gtk_dialog_new_with_buttons("Enter location",
 | 
			
		||||
		GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(self))),
 | 
			
		||||
		GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL |
 | 
			
		||||
			GTK_DIALOG_USE_HEADER_BAR,
 | 
			
		||||
		"_Open", GTK_RESPONSE_ACCEPT, "_Cancel", GTK_RESPONSE_CANCEL, NULL);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *entry = gtk_entry_new();
 | 
			
		||||
	GtkEntryCompletion *completion = gtk_entry_completion_new();
 | 
			
		||||
	gtk_entry_set_completion(GTK_ENTRY(entry), completion);
 | 
			
		||||
	gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
 | 
			
		||||
	g_signal_connect(entry, "changed",
 | 
			
		||||
		G_CALLBACK(on_enter_location_changed), self);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
 | 
			
		||||
	gtk_container_add(GTK_CONTAINER(content), entry);
 | 
			
		||||
	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
 | 
			
		||||
	gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), TRUE);
 | 
			
		||||
	gtk_widget_show_all(dialog);
 | 
			
		||||
 | 
			
		||||
	GdkGeometry geometry = {.max_width = G_MAXSHORT, .max_height = -1};
 | 
			
		||||
	gtk_window_set_geometry_hints(
 | 
			
		||||
		GTK_WINDOW(dialog), NULL, &geometry, GDK_HINT_MAX_SIZE);
 | 
			
		||||
 | 
			
		||||
	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
 | 
			
		||||
		const char *text = gtk_entry_get_text(GTK_ENTRY(entry));
 | 
			
		||||
		GFile *location = g_file_parse_name(text);
 | 
			
		||||
		g_signal_emit(self, sidebar_signals[OPEN_LOCATION], 0, location);
 | 
			
		||||
		g_object_unref(location);
 | 
			
		||||
	}
 | 
			
		||||
	gtk_widget_destroy(dialog);
 | 
			
		||||
 | 
			
		||||
	// Deselect the item in GtkPlacesSidebar, if unsuccessful.
 | 
			
		||||
	update_location(self, NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
fastiv_sidebar_init(FastivSidebar *self)
 | 
			
		||||
{
 | 
			
		||||
	// TODO(p): Transplant functionality from the shitty GtkPlacesSidebar.
 | 
			
		||||
	// We cannot reasonably place any new items within its own GtkListBox,
 | 
			
		||||
	// so we need to replicate the style hierarchy to some extent.
 | 
			
		||||
	self->places = GTK_PLACES_SIDEBAR(gtk_places_sidebar_new());
 | 
			
		||||
	gtk_places_sidebar_set_show_recent(self->places, FALSE);
 | 
			
		||||
	gtk_places_sidebar_set_show_trash(self->places, FALSE);
 | 
			
		||||
	gtk_places_sidebar_set_open_flags(self->places,
 | 
			
		||||
		GTK_PLACES_OPEN_NORMAL | GTK_PLACES_OPEN_NEW_WINDOW);
 | 
			
		||||
	g_signal_connect(self->places, "open-location",
 | 
			
		||||
		G_CALLBACK(on_open_location), self);
 | 
			
		||||
 | 
			
		||||
	gtk_places_sidebar_set_show_enter_location(self->places, TRUE);
 | 
			
		||||
	g_signal_connect(self->places, "show-enter-location",
 | 
			
		||||
		G_CALLBACK(on_show_enter_location), self);
 | 
			
		||||
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self->places),
 | 
			
		||||
		GTK_POLICY_NEVER, GTK_POLICY_NEVER);
 | 
			
		||||
 | 
			
		||||
	// Fill up what would otherwise be wasted space,
 | 
			
		||||
	// as it is in the example of Nautilus and Thunar.
 | 
			
		||||
	GtkWidget *superbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
 | 
			
		||||
	gtk_container_add(
 | 
			
		||||
		GTK_CONTAINER(superbox), GTK_WIDGET(self->places));
 | 
			
		||||
	gtk_container_add(
 | 
			
		||||
		GTK_CONTAINER(superbox), gtk_separator_new(GTK_ORIENTATION_VERTICAL));
 | 
			
		||||
 | 
			
		||||
	self->listbox = gtk_list_box_new();
 | 
			
		||||
	gtk_list_box_set_selection_mode(
 | 
			
		||||
		GTK_LIST_BOX(self->listbox), GTK_SELECTION_NONE);
 | 
			
		||||
	g_signal_connect(self->listbox, "row-activated",
 | 
			
		||||
		G_CALLBACK(on_open_breadcrumb), self);
 | 
			
		||||
	gtk_container_add(GTK_CONTAINER(superbox), self->listbox);
 | 
			
		||||
 | 
			
		||||
	gtk_scrolled_window_set_policy(
 | 
			
		||||
		GTK_SCROLLED_WINDOW(self), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
 | 
			
		||||
	gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(self)),
 | 
			
		||||
		GTK_STYLE_CLASS_SIDEBAR);
 | 
			
		||||
	gtk_container_add(GTK_CONTAINER(self), superbox);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Public interface --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
fastiv_sidebar_set_location(FastivSidebar *self, GFile *location)
 | 
			
		||||
{
 | 
			
		||||
	g_return_if_fail(FASTIV_IS_SIDEBAR(self));
 | 
			
		||||
	update_location(self, location);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
fastiv_sidebar_show_enter_location(FastivSidebar *self)
 | 
			
		||||
{
 | 
			
		||||
	g_return_if_fail(FASTIV_IS_SIDEBAR(self));
 | 
			
		||||
	g_signal_emit_by_name(self->places, "show-enter-location");
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								fastiv-sidebar.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								fastiv-sidebar.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
//
 | 
			
		||||
// fastiv-sidebar.h: molesting GtkPlacesSidebar
 | 
			
		||||
//
 | 
			
		||||
// Copyright (c) 2021, Přemysl Eric Janouch <p@janouch.name>
 | 
			
		||||
//
 | 
			
		||||
// Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		||||
// purpose with or without fee is hereby granted.
 | 
			
		||||
//
 | 
			
		||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 | 
			
		||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 | 
			
		||||
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 | 
			
		||||
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 | 
			
		||||
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <gtk/gtk.h>
 | 
			
		||||
 | 
			
		||||
#define FASTIV_TYPE_SIDEBAR (fastiv_sidebar_get_type())
 | 
			
		||||
G_DECLARE_FINAL_TYPE(
 | 
			
		||||
	FastivSidebar, fastiv_sidebar, FASTIV, SIDEBAR, GtkScrolledWindow)
 | 
			
		||||
 | 
			
		||||
void fastiv_sidebar_set_location(FastivSidebar *self, GFile *location);
 | 
			
		||||
void fastiv_sidebar_show_enter_location(FastivSidebar *self);
 | 
			
		||||
							
								
								
									
										97
									
								
								fastiv.c
									
									
									
									
									
								
							
							
						
						
									
										97
									
								
								fastiv.c
									
									
									
									
									
								
							@@ -29,6 +29,7 @@
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "fastiv-browser.h"
 | 
			
		||||
#include "fastiv-io.h"
 | 
			
		||||
#include "fastiv-sidebar.h"
 | 
			
		||||
#include "fastiv-view.h"
 | 
			
		||||
#include "xdg.h"
 | 
			
		||||
 | 
			
		||||
@@ -106,20 +107,21 @@ show_error_dialog(GError *error)
 | 
			
		||||
static void
 | 
			
		||||
load_directory(const gchar *dirname)
 | 
			
		||||
{
 | 
			
		||||
	free(g.directory);
 | 
			
		||||
	g.directory = g_strdup(dirname);
 | 
			
		||||
	if (dirname) {
 | 
			
		||||
		free(g.directory);
 | 
			
		||||
		g.directory = g_strdup(dirname);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g_ptr_array_set_size(g.files, 0);
 | 
			
		||||
	g.files_index = -1;
 | 
			
		||||
 | 
			
		||||
	GFile *file = g_file_new_for_path(dirname);
 | 
			
		||||
	gtk_places_sidebar_set_location(
 | 
			
		||||
		GTK_PLACES_SIDEBAR(g.browser_sidebar), file);
 | 
			
		||||
	GFile *file = g_file_new_for_path(g.directory);
 | 
			
		||||
	fastiv_sidebar_set_location(FASTIV_SIDEBAR(g.browser_sidebar), file);
 | 
			
		||||
	g_object_unref(file);
 | 
			
		||||
 | 
			
		||||
	fastiv_browser_load(FASTIV_BROWSER(g.browser), dirname);
 | 
			
		||||
	fastiv_browser_load(FASTIV_BROWSER(g.browser), g.directory);
 | 
			
		||||
 | 
			
		||||
	GError *error = NULL;
 | 
			
		||||
	GDir *dir = g_dir_open(dirname, 0, &error);
 | 
			
		||||
	GDir *dir = g_dir_open(g.directory, 0, &error);
 | 
			
		||||
	if (dir) {
 | 
			
		||||
		for (const gchar *name = NULL; (name = g_dir_read_name(dir)); ) {
 | 
			
		||||
			// This really wants to make you use readdir() directly.
 | 
			
		||||
@@ -280,6 +282,30 @@ spawn_path(const char *path)
 | 
			
		||||
	g_clear_error(&error);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static gboolean
 | 
			
		||||
open_any_path(const char *path)
 | 
			
		||||
{
 | 
			
		||||
	GStatBuf st;
 | 
			
		||||
	gchar *canonical = g_canonicalize_filename(path, g.directory);
 | 
			
		||||
	gboolean success = !g_stat(canonical, &st);
 | 
			
		||||
	if (!success)
 | 
			
		||||
		show_error_dialog(g_error_new(G_FILE_ERROR,
 | 
			
		||||
			g_file_error_from_errno(errno), "%s: %s", path, g_strerror(errno)));
 | 
			
		||||
	else if (S_ISDIR(st.st_mode))
 | 
			
		||||
		load_directory(canonical);
 | 
			
		||||
	else
 | 
			
		||||
		open(canonical);
 | 
			
		||||
 | 
			
		||||
	g_free(canonical);
 | 
			
		||||
	if (g.files_index < 0) {
 | 
			
		||||
		gtk_stack_set_visible_child(GTK_STACK(g.stack), g.browser_paned);
 | 
			
		||||
		gtk_widget_grab_focus(g.browser_scroller);
 | 
			
		||||
	} else {
 | 
			
		||||
		gtk_stack_set_visible_child(GTK_STACK(g.stack), g.view_scroller);
 | 
			
		||||
	}
 | 
			
		||||
	return success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_open_location(G_GNUC_UNUSED GtkPlacesSidebar *sidebar, GFile *location,
 | 
			
		||||
	G_GNUC_UNUSED GtkPlacesOpenFlags flags, G_GNUC_UNUSED gpointer user_data)
 | 
			
		||||
@@ -289,7 +315,7 @@ on_open_location(G_GNUC_UNUSED GtkPlacesSidebar *sidebar, GFile *location,
 | 
			
		||||
		if (flags & GTK_PLACES_OPEN_NEW_WINDOW)
 | 
			
		||||
			spawn_path(path);
 | 
			
		||||
		else
 | 
			
		||||
			load_directory(path);
 | 
			
		||||
			open_any_path(path);
 | 
			
		||||
		g_free(path);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -315,6 +341,10 @@ on_key_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,
 | 
			
		||||
		case GDK_KEY_o:
 | 
			
		||||
			on_open();
 | 
			
		||||
			return TRUE;
 | 
			
		||||
		case GDK_KEY_l:
 | 
			
		||||
			fastiv_sidebar_show_enter_location(
 | 
			
		||||
				FASTIV_SIDEBAR(g.browser_sidebar));
 | 
			
		||||
			return TRUE;
 | 
			
		||||
		case GDK_KEY_n:
 | 
			
		||||
			spawn_path(g.directory);
 | 
			
		||||
			return TRUE;
 | 
			
		||||
@@ -332,12 +362,9 @@ on_key_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,
 | 
			
		||||
			return TRUE;
 | 
			
		||||
 | 
			
		||||
		case GDK_KEY_F5:
 | 
			
		||||
		case GDK_KEY_r: {
 | 
			
		||||
			char *copy = g_strdup(g.directory);
 | 
			
		||||
			load_directory(copy);
 | 
			
		||||
			g_free(copy);
 | 
			
		||||
		case GDK_KEY_r:
 | 
			
		||||
			load_directory(NULL);
 | 
			
		||||
			return TRUE;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		case GDK_KEY_F9:
 | 
			
		||||
			if (gtk_widget_is_visible(g.browser_sidebar))
 | 
			
		||||
@@ -506,16 +533,11 @@ main(int argc, char *argv[])
 | 
			
		||||
		G_CALLBACK(on_button_press_browser), NULL);
 | 
			
		||||
	gtk_container_add(GTK_CONTAINER(g.browser_scroller), g.browser);
 | 
			
		||||
 | 
			
		||||
	// TODO(p): Put a GtkListBox underneath, but with subdirectories.
 | 
			
		||||
	//  - Set the scrolled window's vertical policy to nope,
 | 
			
		||||
	//    and put it inside another scrolled window.
 | 
			
		||||
	g.browser_sidebar = gtk_places_sidebar_new();
 | 
			
		||||
	gtk_places_sidebar_set_show_recent(
 | 
			
		||||
		GTK_PLACES_SIDEBAR(g.browser_sidebar), FALSE);
 | 
			
		||||
	gtk_places_sidebar_set_show_trash(
 | 
			
		||||
		GTK_PLACES_SIDEBAR(g.browser_sidebar), FALSE);
 | 
			
		||||
	gtk_places_sidebar_set_open_flags(GTK_PLACES_SIDEBAR(g.browser_sidebar),
 | 
			
		||||
		GTK_PLACES_OPEN_NORMAL | GTK_PLACES_OPEN_NEW_WINDOW);
 | 
			
		||||
	// TODO(p): As with GtkFileChooserWidget, bind:
 | 
			
		||||
	//  - C-h to filtering,
 | 
			
		||||
	//  - M-Up to going a level above,
 | 
			
		||||
	//  - mayhaps forward the rest to the sidebar, somehow.
 | 
			
		||||
	g.browser_sidebar = g_object_new(FASTIV_TYPE_SIDEBAR, NULL);
 | 
			
		||||
	g_signal_connect(g.browser_sidebar, "open-location",
 | 
			
		||||
		G_CALLBACK(on_open_location), NULL);
 | 
			
		||||
 | 
			
		||||
@@ -544,30 +566,9 @@ main(int argc, char *argv[])
 | 
			
		||||
	g_strfreev(types);
 | 
			
		||||
 | 
			
		||||
	g.files = g_ptr_array_new_full(16, g_free);
 | 
			
		||||
	gchar *cwd = g_get_current_dir();
 | 
			
		||||
 | 
			
		||||
	GStatBuf st;
 | 
			
		||||
	if (!path_arg) {
 | 
			
		||||
		load_directory(cwd);
 | 
			
		||||
	} else if (g_stat(path_arg, &st)) {
 | 
			
		||||
		show_error_dialog(
 | 
			
		||||
			g_error_new(G_FILE_ERROR, g_file_error_from_errno(errno), "%s: %s",
 | 
			
		||||
				path_arg, g_strerror(errno)));
 | 
			
		||||
		load_directory(cwd);
 | 
			
		||||
	} else {
 | 
			
		||||
		gchar *path_arg_absolute = g_canonicalize_filename(path_arg, cwd);
 | 
			
		||||
		if (S_ISDIR(st.st_mode))
 | 
			
		||||
			load_directory(path_arg_absolute);
 | 
			
		||||
		else
 | 
			
		||||
			open(path_arg_absolute);
 | 
			
		||||
		g_free(path_arg_absolute);
 | 
			
		||||
	}
 | 
			
		||||
	g_free(cwd);
 | 
			
		||||
 | 
			
		||||
	if (g.files_index < 0) {
 | 
			
		||||
		gtk_stack_set_visible_child(GTK_STACK(g.stack), g.browser_paned);
 | 
			
		||||
		gtk_widget_grab_focus(g.browser_scroller);
 | 
			
		||||
	}
 | 
			
		||||
	g.directory = g_get_current_dir();
 | 
			
		||||
	if (!path_arg || !open_any_path(path_arg))
 | 
			
		||||
		open_any_path(g.directory);
 | 
			
		||||
 | 
			
		||||
	// Try to get half of the screen vertically, in 4:3 aspect ratio.
 | 
			
		||||
	//
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ configure_file(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
exe = executable('fastiv', 'fastiv.c', 'fastiv-view.c', 'fastiv-io.c',
 | 
			
		||||
	'fastiv-browser.c', 'xdg.c',
 | 
			
		||||
	'fastiv-browser.c', 'fastiv-sidebar.c', 'xdg.c',
 | 
			
		||||
	install : true,
 | 
			
		||||
	dependencies : [dependencies])
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user