Compare commits
	
		
			2 Commits
		
	
	
		
			b87a109d61
			...
			de27dce09c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| de27dce09c | |||
| a1b2225750 | 
							
								
								
									
										190
									
								
								fiv-browser.c
									
									
									
									
									
								
							
							
						
						
									
										190
									
								
								fiv-browser.c
									
									
									
									
									
								
							| @ -21,6 +21,7 @@ | |||||||
| #include "config.h" | #include "config.h" | ||||||
| 
 | 
 | ||||||
| #include "fiv-browser.h" | #include "fiv-browser.h" | ||||||
|  | #include "fiv-context-menu.h" | ||||||
| #include "fiv-io.h" | #include "fiv-io.h" | ||||||
| #include "fiv-thumbnail.h" | #include "fiv-thumbnail.h" | ||||||
| #include "fiv-view.h" | #include "fiv-view.h" | ||||||
| @ -729,187 +730,6 @@ thumbnailers_start(FivBrowser *self) | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // --- Context menu-------------------------------------------------------------
 |  | ||||||
| 
 |  | ||||||
| typedef struct _OpenContext { |  | ||||||
| 	GWeakRef widget; |  | ||||||
| 	GFile *file; |  | ||||||
| 	gchar *content_type; |  | ||||||
| 	GAppInfo *app_info; |  | ||||||
| } OpenContext; |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| open_context_notify(gpointer data, G_GNUC_UNUSED GClosure *closure) |  | ||||||
| { |  | ||||||
| 	OpenContext *self = data; |  | ||||||
| 	g_weak_ref_clear(&self->widget); |  | ||||||
| 	g_clear_object(&self->app_info); |  | ||||||
| 	g_clear_object(&self->file); |  | ||||||
| 	g_free(self->content_type); |  | ||||||
| 	g_free(self); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| open_context_launch(GtkWidget *widget, OpenContext *self) |  | ||||||
| { |  | ||||||
| 	GdkAppLaunchContext *context = |  | ||||||
| 		gdk_display_get_app_launch_context(gtk_widget_get_display(widget)); |  | ||||||
| 	gdk_app_launch_context_set_screen(context, gtk_widget_get_screen(widget)); |  | ||||||
| 	gdk_app_launch_context_set_timestamp(context, gtk_get_current_event_time()); |  | ||||||
| 
 |  | ||||||
| 	// TODO(p): Display errors.
 |  | ||||||
| 	GList *files = g_list_append(NULL, self->file); |  | ||||||
| 	if (g_app_info_launch( |  | ||||||
| 			self->app_info, files, G_APP_LAUNCH_CONTEXT(context), NULL)) { |  | ||||||
| 		g_app_info_set_as_last_used_for_type( |  | ||||||
| 			self->app_info, self->content_type, NULL); |  | ||||||
| 	} |  | ||||||
| 	g_list_free(files); |  | ||||||
| 	g_object_unref(context); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| append_opener(GtkWidget *menu, GAppInfo *opener, const OpenContext *template) |  | ||||||
| { |  | ||||||
| 	OpenContext *ctx = g_malloc0(sizeof *ctx); |  | ||||||
| 	g_weak_ref_init(&ctx->widget, NULL); |  | ||||||
| 	ctx->file = g_object_ref(template->file); |  | ||||||
| 	ctx->content_type = g_strdup(template->content_type); |  | ||||||
| 	ctx->app_info = opener; |  | ||||||
| 
 |  | ||||||
| 	// On Linux, this prefers the obsoleted X-GNOME-FullName.
 |  | ||||||
| 	gchar *name = |  | ||||||
| 		g_strdup_printf("Open With %s", g_app_info_get_display_name(opener)); |  | ||||||
| 
 |  | ||||||
| 	// It's documented that we can touch the child, if we want to use markup.
 |  | ||||||
| #if 0 |  | ||||||
| 	GtkWidget *item = gtk_menu_item_new_with_label(name); |  | ||||||
| #else |  | ||||||
| 	// GtkImageMenuItem overrides the toggle_size_request class method
 |  | ||||||
| 	// to get the image shown in the "margin"--too much work to duplicate.
 |  | ||||||
| 	G_GNUC_BEGIN_IGNORE_DEPRECATIONS; |  | ||||||
| 
 |  | ||||||
| 	GtkWidget *item = gtk_image_menu_item_new_with_label(name); |  | ||||||
| 	GIcon *icon = g_app_info_get_icon(opener); |  | ||||||
| 	if (icon) { |  | ||||||
| 		GtkWidget *image = gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_MENU); |  | ||||||
| 		gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image); |  | ||||||
| 		gtk_image_menu_item_set_always_show_image( |  | ||||||
| 			GTK_IMAGE_MENU_ITEM(item), TRUE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	G_GNUC_END_IGNORE_DEPRECATIONS; |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| 	g_free(name); |  | ||||||
| 	g_signal_connect_data(item, "activate", G_CALLBACK(open_context_launch), |  | ||||||
| 		ctx, open_context_notify, 0); |  | ||||||
| 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| on_chooser_activate(GtkMenuItem *item, gpointer user_data) |  | ||||||
| { |  | ||||||
| 	OpenContext *ctx = user_data; |  | ||||||
| 	GtkWindow *window = NULL; |  | ||||||
| 	GtkWidget *widget = g_weak_ref_get(&ctx->widget); |  | ||||||
| 	if (widget) { |  | ||||||
| 		if (GTK_IS_WINDOW((widget = gtk_widget_get_toplevel(widget)))) |  | ||||||
| 			window = GTK_WINDOW(widget); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	GtkWidget *dialog = gtk_app_chooser_dialog_new_for_content_type(window, |  | ||||||
| 		GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, ctx->content_type); |  | ||||||
| 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { |  | ||||||
| 		ctx->app_info = gtk_app_chooser_get_app_info(GTK_APP_CHOOSER(dialog)); |  | ||||||
| 		open_context_launch(GTK_WIDGET(item), ctx); |  | ||||||
| 	} |  | ||||||
| 	gtk_widget_destroy(dialog); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static gboolean |  | ||||||
| destroy_widget_idle_source_func(GtkWidget *widget) |  | ||||||
| { |  | ||||||
| 	// The whole menu is deactivated /before/ any item is activated,
 |  | ||||||
| 	// and a destroyed child item will not activate.
 |  | ||||||
| 	gtk_widget_destroy(widget); |  | ||||||
| 	return FALSE; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static GtkMenu * |  | ||||||
| make_context_menu(GtkWidget *widget, GFile *file) |  | ||||||
| { |  | ||||||
| 	GFileInfo *info = g_file_query_info(file, |  | ||||||
| 		G_FILE_ATTRIBUTE_STANDARD_NAME |  | ||||||
| 		"," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, |  | ||||||
| 		G_FILE_QUERY_INFO_NONE, NULL, NULL); |  | ||||||
| 	if (!info) |  | ||||||
| 		return NULL; |  | ||||||
| 
 |  | ||||||
| 	// This will have no application pre-assigned, for use with GTK+'s dialog.
 |  | ||||||
| 	OpenContext *ctx = g_malloc0(sizeof *ctx); |  | ||||||
| 	g_weak_ref_init(&ctx->widget, widget); |  | ||||||
| 	ctx->file = g_object_ref(file); |  | ||||||
| 	ctx->content_type = g_strdup(g_file_info_get_content_type(info)); |  | ||||||
| 	g_object_unref(info); |  | ||||||
| 
 |  | ||||||
| 	GAppInfo *default_ = |  | ||||||
| 		g_app_info_get_default_for_type(ctx->content_type, FALSE); |  | ||||||
| 	GList *recommended = g_app_info_get_recommended_for_type(ctx->content_type); |  | ||||||
| 	GList *fallback = g_app_info_get_fallback_for_type(ctx->content_type); |  | ||||||
| 
 |  | ||||||
| 	GtkWidget *menu = gtk_menu_new(); |  | ||||||
| 	if (default_) { |  | ||||||
| 		append_opener(menu, default_, ctx); |  | ||||||
| 		gtk_menu_shell_append( |  | ||||||
| 			GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for (GList *iter = recommended; iter; iter = iter->next) { |  | ||||||
| 		if (!default_ || !g_app_info_equal(iter->data, default_)) |  | ||||||
| 			append_opener(menu, iter->data, ctx); |  | ||||||
| 		else |  | ||||||
| 			g_object_unref(iter->data); |  | ||||||
| 	} |  | ||||||
| 	if (recommended) { |  | ||||||
| 		g_list_free(recommended); |  | ||||||
| 		gtk_menu_shell_append( |  | ||||||
| 			GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for (GList *iter = fallback; iter; iter = iter->next) { |  | ||||||
| 		if (!default_ || !g_app_info_equal(iter->data, default_)) |  | ||||||
| 			append_opener(menu, iter->data, ctx); |  | ||||||
| 		else |  | ||||||
| 			g_object_unref(iter->data); |  | ||||||
| 	} |  | ||||||
| 	if (fallback) { |  | ||||||
| 		g_list_free(fallback); |  | ||||||
| 		gtk_menu_shell_append( |  | ||||||
| 			GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	GtkWidget *item = gtk_menu_item_new_with_label("Open With..."); |  | ||||||
| 	g_signal_connect_data(item, "activate", G_CALLBACK(on_chooser_activate), |  | ||||||
| 		ctx, open_context_notify, 0); |  | ||||||
| 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); |  | ||||||
| 
 |  | ||||||
| 	// As per GTK+ 3 Common Questions, 1.5.
 |  | ||||||
| 	g_object_ref_sink(menu); |  | ||||||
| 	g_signal_connect_swapped(menu, "deactivate", |  | ||||||
| 		G_CALLBACK(g_idle_add), destroy_widget_idle_source_func); |  | ||||||
| 	g_signal_connect(menu, "destroy", G_CALLBACK(g_object_unref), NULL); |  | ||||||
| 
 |  | ||||||
| 	gtk_widget_show_all(menu); |  | ||||||
| 	return GTK_MENU(menu); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| show_context_menu(GtkWidget *widget, GFile *file) |  | ||||||
| { |  | ||||||
| 	gtk_menu_popup_at_pointer(make_context_menu(widget, file), NULL); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // --- Boilerplate -------------------------------------------------------------
 | // --- Boilerplate -------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| G_DEFINE_TYPE_EXTENDED(FivBrowser, fiv_browser, GTK_TYPE_WIDGET, 0, | G_DEFINE_TYPE_EXTENDED(FivBrowser, fiv_browser, GTK_TYPE_WIDGET, 0, | ||||||
| @ -1199,6 +1019,12 @@ open_entry(GtkWidget *self, const Entry *entry, gboolean new_window) | |||||||
| 	return TRUE; | 	return TRUE; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void | ||||||
|  | show_context_menu(GtkWidget *widget, GFile *file) | ||||||
|  | { | ||||||
|  | 	gtk_menu_popup_at_pointer(fiv_context_menu_new(widget, file), NULL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void | static void | ||||||
| abort_button_tracking(FivBrowser *self) | abort_button_tracking(FivBrowser *self) | ||||||
| { | { | ||||||
| @ -1600,7 +1426,7 @@ fiv_browser_popup_menu(GtkWidget *widget) | |||||||
| 		file = g_object_ref(fiv_io_model_get_location(self->model)); | 		file = g_object_ref(fiv_io_model_get_location(self->model)); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	gtk_menu_popup_at_rect(make_context_menu(widget, file), | 	gtk_menu_popup_at_rect(fiv_context_menu_new(widget, file), | ||||||
| 		gtk_widget_get_window(widget), &rect, GDK_GRAVITY_NORTH_WEST, | 		gtk_widget_get_window(widget), &rect, GDK_GRAVITY_NORTH_WEST, | ||||||
| 		GDK_GRAVITY_NORTH_WEST, NULL); | 		GDK_GRAVITY_NORTH_WEST, NULL); | ||||||
| 	g_object_unref(file); | 	g_object_unref(file); | ||||||
|  | |||||||
							
								
								
									
										193
									
								
								fiv-context-menu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								fiv-context-menu.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,193 @@ | |||||||
|  | //
 | ||||||
|  | // fiv-context-menu.c: popup menu
 | ||||||
|  | //
 | ||||||
|  | // Copyright (c) 2021 - 2022, 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 "config.h" | ||||||
|  | 
 | ||||||
|  | #include "fiv-context-menu.h" | ||||||
|  | 
 | ||||||
|  | typedef struct _OpenContext { | ||||||
|  | 	GWeakRef widget; | ||||||
|  | 	GFile *file; | ||||||
|  | 	gchar *content_type; | ||||||
|  | 	GAppInfo *app_info; | ||||||
|  | } OpenContext; | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | open_context_notify(gpointer data, G_GNUC_UNUSED GClosure *closure) | ||||||
|  | { | ||||||
|  | 	OpenContext *self = data; | ||||||
|  | 	g_weak_ref_clear(&self->widget); | ||||||
|  | 	g_clear_object(&self->app_info); | ||||||
|  | 	g_clear_object(&self->file); | ||||||
|  | 	g_free(self->content_type); | ||||||
|  | 	g_free(self); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | open_context_launch(GtkWidget *widget, OpenContext *self) | ||||||
|  | { | ||||||
|  | 	GdkAppLaunchContext *context = | ||||||
|  | 		gdk_display_get_app_launch_context(gtk_widget_get_display(widget)); | ||||||
|  | 	gdk_app_launch_context_set_screen(context, gtk_widget_get_screen(widget)); | ||||||
|  | 	gdk_app_launch_context_set_timestamp(context, gtk_get_current_event_time()); | ||||||
|  | 
 | ||||||
|  | 	// TODO(p): Display errors.
 | ||||||
|  | 	GList *files = g_list_append(NULL, self->file); | ||||||
|  | 	if (g_app_info_launch( | ||||||
|  | 			self->app_info, files, G_APP_LAUNCH_CONTEXT(context), NULL)) { | ||||||
|  | 		g_app_info_set_as_last_used_for_type( | ||||||
|  | 			self->app_info, self->content_type, NULL); | ||||||
|  | 	} | ||||||
|  | 	g_list_free(files); | ||||||
|  | 	g_object_unref(context); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | append_opener(GtkWidget *menu, GAppInfo *opener, const OpenContext *template) | ||||||
|  | { | ||||||
|  | 	OpenContext *ctx = g_malloc0(sizeof *ctx); | ||||||
|  | 	g_weak_ref_init(&ctx->widget, NULL); | ||||||
|  | 	ctx->file = g_object_ref(template->file); | ||||||
|  | 	ctx->content_type = g_strdup(template->content_type); | ||||||
|  | 	ctx->app_info = opener; | ||||||
|  | 
 | ||||||
|  | 	// On Linux, this prefers the obsoleted X-GNOME-FullName.
 | ||||||
|  | 	gchar *name = | ||||||
|  | 		g_strdup_printf("Open With %s", g_app_info_get_display_name(opener)); | ||||||
|  | 
 | ||||||
|  | 	// It's documented that we can touch the child, if we want to use markup.
 | ||||||
|  | #if 0 | ||||||
|  | 	GtkWidget *item = gtk_menu_item_new_with_label(name); | ||||||
|  | #else | ||||||
|  | 	// GtkImageMenuItem overrides the toggle_size_request class method
 | ||||||
|  | 	// to get the image shown in the "margin"--too much work to duplicate.
 | ||||||
|  | 	G_GNUC_BEGIN_IGNORE_DEPRECATIONS; | ||||||
|  | 
 | ||||||
|  | 	GtkWidget *item = gtk_image_menu_item_new_with_label(name); | ||||||
|  | 	GIcon *icon = g_app_info_get_icon(opener); | ||||||
|  | 	if (icon) { | ||||||
|  | 		GtkWidget *image = gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_MENU); | ||||||
|  | 		gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image); | ||||||
|  | 		gtk_image_menu_item_set_always_show_image( | ||||||
|  | 			GTK_IMAGE_MENU_ITEM(item), TRUE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	G_GNUC_END_IGNORE_DEPRECATIONS; | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | 	g_free(name); | ||||||
|  | 	g_signal_connect_data(item, "activate", G_CALLBACK(open_context_launch), | ||||||
|  | 		ctx, open_context_notify, 0); | ||||||
|  | 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | on_chooser_activate(GtkMenuItem *item, gpointer user_data) | ||||||
|  | { | ||||||
|  | 	OpenContext *ctx = user_data; | ||||||
|  | 	GtkWindow *window = NULL; | ||||||
|  | 	GtkWidget *widget = g_weak_ref_get(&ctx->widget); | ||||||
|  | 	if (widget) { | ||||||
|  | 		if (GTK_IS_WINDOW((widget = gtk_widget_get_toplevel(widget)))) | ||||||
|  | 			window = GTK_WINDOW(widget); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	GtkWidget *dialog = gtk_app_chooser_dialog_new_for_content_type(window, | ||||||
|  | 		GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, ctx->content_type); | ||||||
|  | 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { | ||||||
|  | 		ctx->app_info = gtk_app_chooser_get_app_info(GTK_APP_CHOOSER(dialog)); | ||||||
|  | 		open_context_launch(GTK_WIDGET(item), ctx); | ||||||
|  | 	} | ||||||
|  | 	gtk_widget_destroy(dialog); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static gboolean | ||||||
|  | destroy_widget_idle_source_func(GtkWidget *widget) | ||||||
|  | { | ||||||
|  | 	// The whole menu is deactivated /before/ any item is activated,
 | ||||||
|  | 	// and a destroyed child item will not activate.
 | ||||||
|  | 	gtk_widget_destroy(widget); | ||||||
|  | 	return FALSE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | GtkMenu * | ||||||
|  | fiv_context_menu_new(GtkWidget *widget, GFile *file) | ||||||
|  | { | ||||||
|  | 	GFileInfo *info = g_file_query_info(file, | ||||||
|  | 		G_FILE_ATTRIBUTE_STANDARD_NAME | ||||||
|  | 		"," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, | ||||||
|  | 		G_FILE_QUERY_INFO_NONE, NULL, NULL); | ||||||
|  | 	if (!info) | ||||||
|  | 		return NULL; | ||||||
|  | 
 | ||||||
|  | 	// This will have no application pre-assigned, for use with GTK+'s dialog.
 | ||||||
|  | 	OpenContext *ctx = g_malloc0(sizeof *ctx); | ||||||
|  | 	g_weak_ref_init(&ctx->widget, widget); | ||||||
|  | 	ctx->file = g_object_ref(file); | ||||||
|  | 	ctx->content_type = g_strdup(g_file_info_get_content_type(info)); | ||||||
|  | 	g_object_unref(info); | ||||||
|  | 
 | ||||||
|  | 	GAppInfo *default_ = | ||||||
|  | 		g_app_info_get_default_for_type(ctx->content_type, FALSE); | ||||||
|  | 	GList *recommended = g_app_info_get_recommended_for_type(ctx->content_type); | ||||||
|  | 	GList *fallback = g_app_info_get_fallback_for_type(ctx->content_type); | ||||||
|  | 
 | ||||||
|  | 	GtkWidget *menu = gtk_menu_new(); | ||||||
|  | 	if (default_) { | ||||||
|  | 		append_opener(menu, default_, ctx); | ||||||
|  | 		gtk_menu_shell_append( | ||||||
|  | 			GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for (GList *iter = recommended; iter; iter = iter->next) { | ||||||
|  | 		if (!default_ || !g_app_info_equal(iter->data, default_)) | ||||||
|  | 			append_opener(menu, iter->data, ctx); | ||||||
|  | 		else | ||||||
|  | 			g_object_unref(iter->data); | ||||||
|  | 	} | ||||||
|  | 	if (recommended) { | ||||||
|  | 		g_list_free(recommended); | ||||||
|  | 		gtk_menu_shell_append( | ||||||
|  | 			GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for (GList *iter = fallback; iter; iter = iter->next) { | ||||||
|  | 		if (!default_ || !g_app_info_equal(iter->data, default_)) | ||||||
|  | 			append_opener(menu, iter->data, ctx); | ||||||
|  | 		else | ||||||
|  | 			g_object_unref(iter->data); | ||||||
|  | 	} | ||||||
|  | 	if (fallback) { | ||||||
|  | 		g_list_free(fallback); | ||||||
|  | 		gtk_menu_shell_append( | ||||||
|  | 			GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	GtkWidget *item = gtk_menu_item_new_with_label("Open With..."); | ||||||
|  | 	g_signal_connect_data(item, "activate", G_CALLBACK(on_chooser_activate), | ||||||
|  | 		ctx, open_context_notify, 0); | ||||||
|  | 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); | ||||||
|  | 
 | ||||||
|  | 	// As per GTK+ 3 Common Questions, 1.5.
 | ||||||
|  | 	g_object_ref_sink(menu); | ||||||
|  | 	g_signal_connect_swapped(menu, "deactivate", | ||||||
|  | 		G_CALLBACK(g_idle_add), destroy_widget_idle_source_func); | ||||||
|  | 	g_signal_connect(menu, "destroy", G_CALLBACK(g_object_unref), NULL); | ||||||
|  | 
 | ||||||
|  | 	gtk_widget_show_all(menu); | ||||||
|  | 	return GTK_MENU(menu); | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								fiv-context-menu.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								fiv-context-menu.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | //
 | ||||||
|  | // fiv-context-menu.h: popup menu
 | ||||||
|  | //
 | ||||||
|  | // Copyright (c) 2022, 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> | ||||||
|  | 
 | ||||||
|  | GtkMenu *fiv_context_menu_new(GtkWidget *widget, GFile *file); | ||||||
| @ -17,6 +17,7 @@ | |||||||
| 
 | 
 | ||||||
| #include <gtk/gtk.h> | #include <gtk/gtk.h> | ||||||
| 
 | 
 | ||||||
|  | #include "fiv-context-menu.h" | ||||||
| #include "fiv-io.h" | #include "fiv-io.h" | ||||||
| #include "fiv-sidebar.h" | #include "fiv-sidebar.h" | ||||||
| 
 | 
 | ||||||
| @ -99,7 +100,7 @@ on_rowlabel_query_tooltip(GtkWidget *widget, | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static gboolean | static gboolean | ||||||
| on_breadcrumb_release( | on_breadcrumb_button_release( | ||||||
| 	G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, gpointer user_data) | 	G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, gpointer user_data) | ||||||
| { | { | ||||||
| 	// This also prevents unwanted primary button click handling in GtkListBox.
 | 	// This also prevents unwanted primary button click handling in GtkListBox.
 | ||||||
| @ -147,6 +148,30 @@ on_breadcrumb_drag_end(G_GNUC_UNUSED GtkWidget *widget, | |||||||
| 	gtk_places_sidebar_set_drop_targets_visible(user_data, FALSE, context); | 	gtk_places_sidebar_set_drop_targets_visible(user_data, FALSE, context); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static gboolean | ||||||
|  | on_breadcrumb_button_press(GtkWidget *widget, GdkEventButton *event, | ||||||
|  | 	G_GNUC_UNUSED gpointer user_data) | ||||||
|  | { | ||||||
|  | 	if (event->type != GDK_BUTTON_PRESS || | ||||||
|  | 		event->button != GDK_BUTTON_SECONDARY) | ||||||
|  | 		return FALSE; | ||||||
|  | 
 | ||||||
|  | 	GFile *location = | ||||||
|  | 		g_object_get_qdata(G_OBJECT(widget), fiv_sidebar_location_quark()); | ||||||
|  | 	gtk_menu_popup_at_pointer(fiv_context_menu_new(widget, location), NULL); | ||||||
|  | 	return TRUE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static gboolean | ||||||
|  | on_breadcrumb_popup_menu(GtkWidget *widget, G_GNUC_UNUSED gpointer user_data) | ||||||
|  | { | ||||||
|  | 	GFile *location = | ||||||
|  | 		g_object_get_qdata(G_OBJECT(widget), fiv_sidebar_location_quark()); | ||||||
|  | 	gtk_menu_popup_at_widget(fiv_context_menu_new(widget, location), widget, | ||||||
|  | 		GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL); | ||||||
|  | 	return TRUE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static GtkWidget * | static GtkWidget * | ||||||
| create_row(FivSidebar *self, GFile *file, const char *icon_name) | create_row(FivSidebar *self, GFile *file, const char *icon_name) | ||||||
| { | { | ||||||
| @ -198,8 +223,15 @@ create_row(FivSidebar *self, GFile *file, const char *icon_name) | |||||||
| 		g_object_ref(file), (GDestroyNotify) g_object_unref); | 		g_object_ref(file), (GDestroyNotify) g_object_unref); | ||||||
| 	g_object_set_qdata_full(G_OBJECT(row), fiv_sidebar_self_quark(), | 	g_object_set_qdata_full(G_OBJECT(row), fiv_sidebar_self_quark(), | ||||||
| 		g_object_ref(self), (GDestroyNotify) g_object_unref); | 		g_object_ref(self), (GDestroyNotify) g_object_unref); | ||||||
|  | 	g_signal_connect(row, "button-press-event", | ||||||
|  | 		G_CALLBACK(on_breadcrumb_button_press), NULL); | ||||||
|  | 	g_signal_connect(row, "popup-menu", | ||||||
|  | 		G_CALLBACK(on_breadcrumb_popup_menu), NULL); | ||||||
|  | 
 | ||||||
|  | 	// TODO(p): Why do we hook to the revealer, and not the row itself?
 | ||||||
|  | 	// Is it due to some kind of margin or padding?
 | ||||||
| 	g_signal_connect(revealer, "button-release-event", | 	g_signal_connect(revealer, "button-release-event", | ||||||
| 		G_CALLBACK(on_breadcrumb_release), row); | 		G_CALLBACK(on_breadcrumb_button_release), row); | ||||||
| 	g_signal_connect(revealer, "drag-data-get", | 	g_signal_connect(revealer, "drag-data-get", | ||||||
| 		G_CALLBACK(on_breadcrumb_drag_data_get), row); | 		G_CALLBACK(on_breadcrumb_drag_data_get), row); | ||||||
| 	g_signal_connect(revealer, "drag-begin", | 	g_signal_connect(revealer, "drag-begin", | ||||||
|  | |||||||
| @ -103,7 +103,7 @@ tiff_tables = custom_target('tiff-tables.h', | |||||||
| 	capture : true, | 	capture : true, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| exe = executable('fiv', 'fiv.c', 'fiv-view.c', 'fiv-io.c', | exe = executable('fiv', 'fiv.c', 'fiv-view.c', 'fiv-io.c', 'fiv-context-menu.c', | ||||||
| 	'fiv-browser.c', 'fiv-sidebar.c', 'fiv-thumbnail.c', 'xdg.c', resources, | 	'fiv-browser.c', 'fiv-sidebar.c', 'fiv-thumbnail.c', 'xdg.c', resources, | ||||||
| 	install : true, | 	install : true, | ||||||
| 	dependencies : [dependencies]) | 	dependencies : [dependencies]) | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user