[nautilus/wip/antoniof/new_open_with: 8/9] app-chooser: Implement new dialog




commit 43b1c1a1476dccdbd3020692d33d3b3d005f6d12
Author: António Fernandes <antoniof gnome org>
Date:   Fri Jul 22 20:05:50 2022 +0200

    app-chooser: Implement new dialog
    
    When choosing to open a file in another application, the user may want
    to set it as the new default.
    
    Currently we require going to the Properties, which is not intuitive,
    and one may think it's going to affect only one file.
    
    Introduce a GtkAppChooserDialog replacement which provides the means to
    set as default and reset as part of "Open with Another Application...".
    
    Compared to the Properties "Open with" tab, the "Add" action is not
    present, because opening the file with an app adds it implicitly. The
    "Forget" action is not included either because it lacks its "Add"
    counterpart and because such fine grained control is not essential. We
    have Reset anyway.

 po/POTFILES.in                           |   1 +
 src/meson.build                          |   2 +
 src/nautilus-app-chooser.c               | 219 +++++++++++++++++++++++++++++++
 src/nautilus-app-chooser.h               |  22 ++++
 src/nautilus-files-view.c                |   9 +-
 src/resources/css/Adwaita.css            |   5 +
 src/resources/nautilus.gresource.xml     |   1 +
 src/resources/ui/nautilus-app-chooser.ui |  94 +++++++++++++
 8 files changed, 347 insertions(+), 6 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7b2248f19..09a343255 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -16,6 +16,7 @@ libnautilus-extension/nautilus-column.c
 libnautilus-extension/nautilus-menu-item.c
 libnautilus-extension/nautilus-property-page.c
 src/nautilus-application.c
+src/nautilus-app-chooser.c
 src/nautilus-autorun-software.c
 src/nautilus-batch-rename-dialog.c
 src/nautilus-batch-rename-dialog.h
diff --git a/src/meson.build b/src/meson.build
index e76fe7c25..2772ab6be 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -67,6 +67,8 @@ libnautilus_sources = [
   'gtk/nautilusgtkplacesviewrowprivate.h',
   'nautilus-application.c',
   'nautilus-application.h',
+  'nautilus-app-chooser.c',
+  'nautilus-app-chooser.h',
   'nautilus-bookmark-list.c',
   'nautilus-bookmark-list.h',
   'nautilus-dbus-manager.c',
diff --git a/src/nautilus-app-chooser.c b/src/nautilus-app-chooser.c
new file mode 100644
index 000000000..f9704d689
--- /dev/null
+++ b/src/nautilus-app-chooser.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2022 António Fernandes <antoniof gnome org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "nautilus-app-chooser.h"
+
+#include <libadwaita-1/adwaita.h>
+#include <glib/gi18n.h>
+
+#include "nautilus-signaller.h"
+
+struct _NautilusAppChooser
+{
+    GtkDialog parent_instance;
+
+    gchar *content_type;
+
+    GtkWidget *app_chooser_widget_box;
+    GtkWidget *reset_button;
+    GtkWidget *set_as_default_button;
+
+    GtkWidget *app_chooser_widget;
+};
+
+G_DEFINE_TYPE (NautilusAppChooser, nautilus_app_chooser, GTK_TYPE_DIALOG)
+
+enum
+{
+    PROP_0,
+    PROP_CONTENT_TYPE,
+    LAST_PROP
+};
+
+static void
+reset_clicked_cb (GtkButton *button,
+                  gpointer   user_data)
+{
+    NautilusAppChooser *self = NAUTILUS_APP_CHOOSER (user_data);
+
+    g_app_info_reset_type_associations (self->content_type);
+    gtk_app_chooser_refresh (GTK_APP_CHOOSER (self->app_chooser_widget));
+    gtk_widget_set_sensitive (self->reset_button, FALSE);
+
+    g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed");
+}
+
+static void
+set_as_default_clicked_cb (GtkButton *button,
+                           gpointer   user_data)
+{
+    NautilusAppChooser *self = NAUTILUS_APP_CHOOSER (user_data);
+    g_autoptr (GAppInfo) info = NULL;
+    g_autoptr (GError) error = NULL;
+
+    info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self->app_chooser_widget));
+
+    g_app_info_set_as_default_for_type (info, self->content_type,
+                                        &error);
+
+    if (error != NULL)
+    {
+        g_autofree gchar *message = NULL;
+        GtkWidget *message_dialog;
+
+        message = g_strdup_printf (_("Error while setting “%s” as default application: %s"),
+                                   g_app_info_get_display_name (info), error->message);
+        message_dialog = adw_message_dialog_new (GTK_WINDOW (self),
+                                                 _("Could not set as default"),
+                                                 message);
+        adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (message_dialog), "close", _("OK"));
+        gtk_window_present (GTK_WINDOW (message_dialog));
+    }
+
+    gtk_app_chooser_refresh (GTK_APP_CHOOSER (self->app_chooser_widget));
+    gtk_widget_set_sensitive (self->reset_button, TRUE);
+    g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed");
+}
+
+static void
+on_application_selected (GtkAppChooserWidget *widget,
+                         GAppInfo            *info,
+                         gpointer             user_data)
+{
+    NautilusAppChooser *self = NAUTILUS_APP_CHOOSER (user_data);
+    g_autoptr (GAppInfo) default_app = NULL;
+    gboolean is_default;
+
+    gtk_dialog_set_response_sensitive (GTK_DIALOG (self), GTK_RESPONSE_OK, info != NULL);
+
+    default_app = g_app_info_get_default_for_type (self->content_type, FALSE);
+    is_default = default_app != NULL && g_app_info_equal (info, default_app);
+
+    gtk_widget_set_sensitive (self->set_as_default_button, !is_default);
+}
+
+static void
+nautilus_app_chooser_set_property (GObject      *object,
+                                   guint         param_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+    NautilusAppChooser *self = NAUTILUS_APP_CHOOSER (object);
+
+    switch (param_id)
+    {
+        case PROP_CONTENT_TYPE:
+        {
+            self->content_type = g_value_dup_string (value);
+        }
+        break;
+
+        default:
+        {
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+        }
+        break;
+    }
+}
+
+static void
+nautilus_app_chooser_init (NautilusAppChooser *self)
+{
+    gtk_widget_init_template (GTK_WIDGET (self));
+
+    gtk_widget_set_name (GTK_WIDGET (self), "NautilusAppChooser");
+}
+
+static void
+nautilus_app_chooser_constructed (GObject *object)
+{
+    NautilusAppChooser *self = NAUTILUS_APP_CHOOSER (object);
+    g_autoptr (GAppInfo) info = NULL;
+
+    G_OBJECT_CLASS (nautilus_app_chooser_parent_class)->constructed (object);
+
+    self->app_chooser_widget = gtk_app_chooser_widget_new (self->content_type);
+    gtk_widget_set_vexpand (self->app_chooser_widget, TRUE);
+    gtk_widget_add_css_class (self->app_chooser_widget, "lowres-icon");
+    gtk_box_append (GTK_BOX (self->app_chooser_widget_box), self->app_chooser_widget);
+
+    gtk_app_chooser_widget_set_show_default (GTK_APP_CHOOSER_WIDGET (self->app_chooser_widget), TRUE);
+    gtk_app_chooser_widget_set_show_fallback (GTK_APP_CHOOSER_WIDGET (self->app_chooser_widget), TRUE);
+    gtk_app_chooser_widget_set_show_other (GTK_APP_CHOOSER_WIDGET (self->app_chooser_widget), TRUE);
+    g_signal_connect (self->reset_button, "clicked",
+                      G_CALLBACK (reset_clicked_cb),
+                      self);
+    g_signal_connect (self->set_as_default_button, "clicked",
+                      G_CALLBACK (set_as_default_clicked_cb),
+                      self);
+
+    /* initialize sensitivity */
+    info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self->app_chooser_widget));
+    if (info != NULL)
+    {
+        on_application_selected (GTK_APP_CHOOSER_WIDGET (self->app_chooser_widget),
+                                 info, self);
+    }
+
+    g_signal_connect (self->app_chooser_widget,
+                      "application-selected",
+                      G_CALLBACK (on_application_selected),
+                      self);
+
+    gtk_header_bar_set_title_widget (GTK_HEADER_BAR (gtk_dialog_get_header_bar (GTK_DIALOG (self))),
+                                     adw_window_title_new (gtk_window_get_title (GTK_WINDOW (self)),
+                                                           self->content_type));
+}
+
+static void
+nautilus_app_chooser_finalize (GObject *object)
+{
+    NautilusAppChooser *self = (NautilusAppChooser *) object;
+
+    g_clear_pointer (&self->content_type, g_free);
+
+    G_OBJECT_CLASS (nautilus_app_chooser_parent_class)->finalize (object);
+}
+
+static void
+nautilus_app_chooser_class_init (NautilusAppChooserClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+    object_class->finalize = nautilus_app_chooser_finalize;
+    object_class->constructed = nautilus_app_chooser_constructed;
+    object_class->set_property = nautilus_app_chooser_set_property;
+
+    gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/nautilus/ui/nautilus-app-chooser.ui");
+
+    gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, app_chooser_widget_box);
+    gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, reset_button);
+    gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, set_as_default_button);
+
+    g_object_class_install_property (object_class,
+                                     PROP_CONTENT_TYPE,
+                                     g_param_spec_string ("content-type", "", "",
+                                                          NULL,
+                                                          G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+}
+
+NautilusAppChooser *
+nautilus_app_chooser_new (const char *content_type,
+                          GtkWindow  *parent_window)
+{
+    return NAUTILUS_APP_CHOOSER (g_object_new (NAUTILUS_TYPE_APP_CHOOSER,
+                                               "transient-for", parent_window,
+                                               "content-type", content_type,
+                                               "use-header-bar", TRUE,
+                                               NULL));
+}
+
+GAppInfo *
+nautilus_app_chooser_get_app_info (NautilusAppChooser *self)
+{
+    return gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self->app_chooser_widget));
+}
diff --git a/src/nautilus-app-chooser.h b/src/nautilus-app-chooser.h
new file mode 100644
index 000000000..bab09845d
--- /dev/null
+++ b/src/nautilus-app-chooser.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 António Fernandes <antoniof gnome org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_APP_CHOOSER (nautilus_app_chooser_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusAppChooser, nautilus_app_chooser, NAUTILUS, APP_CHOOSER, GtkDialog)
+
+NautilusAppChooser *nautilus_app_chooser_new (const char *content_type,
+                                              GtkWindow  *parent_window);
+
+GAppInfo           *nautilus_app_chooser_get_app_info (NautilusAppChooser *self);
+
+G_END_DECLS
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index 5c1e3eb5b..87af936dc 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -50,6 +50,7 @@
 #include "nautilus-debug.h"
 
 #include "nautilus-application.h"
+#include "nautilus-app-chooser.h"
 #include "nautilus-batch-rename-dialog.h"
 #include "nautilus-batch-rename-utilities.h"
 #include "nautilus-clipboard.h"
@@ -1467,7 +1468,7 @@ app_chooser_dialog_response_cb (GtkDialog *dialog,
         goto out;
     }
 
-    info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog));
+    info = nautilus_app_chooser_get_app_info (NAUTILUS_APP_CHOOSER (dialog));
 
     g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed");
 
@@ -1491,11 +1492,7 @@ choose_program (NautilusFilesView *view,
     mime_type = nautilus_file_get_mime_type (files->data);
     parent_window = nautilus_files_view_get_containing_window (view);
 
-    dialog = gtk_app_chooser_dialog_new_for_content_type (parent_window,
-                                                          GTK_DIALOG_MODAL |
-                                                          GTK_DIALOG_DESTROY_WITH_PARENT |
-                                                          GTK_DIALOG_USE_HEADER_BAR,
-                                                          mime_type);
+    dialog = GTK_WIDGET (nautilus_app_chooser_new (mime_type, parent_window));
     g_object_set_data_full (G_OBJECT (dialog),
                             "directory-view:files",
                             files,
diff --git a/src/resources/css/Adwaita.css b/src/resources/css/Adwaita.css
index 6723554bd..f2628f3a9 100644
--- a/src/resources/css/Adwaita.css
+++ b/src/resources/css/Adwaita.css
@@ -246,3 +246,8 @@
 .view image.star.added {
   animation: rotate_star 0.4s ease;
 }
+
+#NautilusAppChooser treeview {
+  min-height: 36px;
+  -gtk-icon-size: 32px;
+}
diff --git a/src/resources/nautilus.gresource.xml b/src/resources/nautilus.gresource.xml
index ba2b4a43e..3a6abce9a 100644
--- a/src/resources/nautilus.gresource.xml
+++ b/src/resources/nautilus.gresource.xml
@@ -3,6 +3,7 @@
   <gresource prefix="/org/gnome/nautilus">
     <file compressed="true">ui/nautilus-preferences-window.ui</file>
     <file compressed="true">ui/nautilus-search-popover.ui</file>
+    <file>ui/nautilus-app-chooser.ui</file>
     <file>ui/nautilus-pathbar-context-menu.ui</file>
     <file>ui/nautilus-toolbar.ui</file>
     <file>ui/nautilus-toolbar-view-menu.ui</file>
diff --git a/src/resources/ui/nautilus-app-chooser.ui b/src/resources/ui/nautilus-app-chooser.ui
new file mode 100644
index 000000000..5c2eb95f5
--- /dev/null
+++ b/src/resources/ui/nautilus-app-chooser.ui
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk" version="4.0"/>
+  <template class="NautilusAppChooser" parent="GtkDialog">
+    <property name="title" translatable="yes">Open With</property>
+    <property name="focusable">False</property>
+    <property name="destroy-with-parent">True</property>
+    <property name="modal">True</property>
+    <property name="default-width">420</property>
+    <property name="default-height">560</property>
+    <child internal-child="content_area">
+      <object class="GtkBox" id="content_area">
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkStack">
+            <property name="hexpand">True</property>
+            <child>
+              <object class="GtkStackPage">
+                <property name="name">list</property>
+                <property name="child">
+                  <object class="GtkScrolledWindow">
+                    <property name="hscrollbar-policy">never</property>
+                    <property name="vexpand">true</property>
+                    <property name="child">
+                      <object class="AdwClamp">
+                        <property name="margin-top">12</property>
+                        <property name="margin-bottom">12</property>
+                        <property name="margin-start">12</property>
+                        <property name="margin-end">12</property>
+                        <style>
+                          <class name="background"/>
+                        </style>
+                        <property name="child">
+                          <object class="GtkBox">
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">12</property>
+                            <child>
+                              <object class="GtkBox" id="app_chooser_widget_box">
+                                <property name="vexpand">True</property>
+                                <property name="orientation">vertical</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="spacing">6</property>
+                                <child>
+                                  <object class="GtkButton" id="reset_button">
+                                    <property name="label" translatable="yes">_Reset</property>
+                                    <property name="use-underline">True</property>
+                                    <property name="focusable">True</property>
+                                    <property name="receives_default">True</property>
+                                    <property name="hexpand">True</property>
+                                    <property name="halign">start</property>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkButton" id="set_as_default_button">
+                                    <property name="label" translatable="yes">Set as _Default</property>
+                                    <property name="use-underline">True</property>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </property>
+                  </object>
+                </property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="cancel_button">
+        <property name="label" translatable="yes">_Cancel</property>
+        <property name="use-underline">True</property>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="ok_button">
+        <property name="label" translatable="yes">_Open</property>
+        <property name="use-underline">True</property>
+        <property name="sensitive">False</property>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="ok" default="true">ok_button</action-widget>
+      <action-widget response="cancel">cancel_button</action-widget>
+    </action-widgets>
+  </template>
+</interface>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]