aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fiv-browser.c190
-rw-r--r--fiv-context-menu.c193
-rw-r--r--fiv-context-menu.h20
-rw-r--r--meson.build2
4 files changed, 222 insertions, 183 deletions
diff --git a/fiv-browser.c b/fiv-browser.c
index 10cb17a..2f43d51 100644
--- a/fiv-browser.c
+++ b/fiv-browser.c
@@ -21,6 +21,7 @@
#include "config.h"
#include "fiv-browser.h"
+#include "fiv-context-menu.h"
#include "fiv-io.h"
#include "fiv-thumbnail.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 -------------------------------------------------------------
G_DEFINE_TYPE_EXTENDED(FivBrowser, fiv_browser, GTK_TYPE_WIDGET, 0,
@@ -1200,6 +1020,12 @@ open_entry(GtkWidget *self, const Entry *entry, gboolean new_window)
}
static void
+show_context_menu(GtkWidget *widget, GFile *file)
+{
+ gtk_menu_popup_at_pointer(fiv_context_menu_new(widget, file), NULL);
+}
+
+static void
abort_button_tracking(FivBrowser *self)
{
self->tracked_button = 0;
@@ -1600,7 +1426,7 @@ fiv_browser_popup_menu(GtkWidget *widget)
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,
GDK_GRAVITY_NORTH_WEST, NULL);
g_object_unref(file);
diff --git a/fiv-context-menu.c b/fiv-context-menu.c
new file mode 100644
index 0000000..387969b
--- /dev/null
+++ b/fiv-context-menu.c
@@ -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);
+}
diff --git a/fiv-context-menu.h b/fiv-context-menu.h
new file mode 100644
index 0000000..a226d0a
--- /dev/null
+++ b/fiv-context-menu.h
@@ -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);
diff --git a/meson.build b/meson.build
index c511d6a..689fa2d 100644
--- a/meson.build
+++ b/meson.build
@@ -103,7 +103,7 @@ tiff_tables = custom_target('tiff-tables.h',
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,
install : true,
dependencies : [dependencies])