Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Use FileChooserNative #400

Closed
iicurtis opened this issue May 8, 2019 · 6 comments
Closed

Feature Request: Use FileChooserNative #400

iicurtis opened this issue May 8, 2019 · 6 comments

Comments

@iicurtis
Copy link

iicurtis commented May 8, 2019

System information

SolveSpace version: 3.0~e7b75f19

Operating system: Arch Linux 5.0.13

Expected behavior

It would be nice if Solvespace used the native file chooser dialog for other desktop environments (such as KDE).

Actual behavior

The default gtk dialog is used. The default gtk dialog leaves much to be desired, and is often painful to use. (i.e. by default, typing doesn't rename the file, but rather opens up a search dialog)

Additional information

There is a fix for this: GTK has introduced Gtk::FileChooserNative. Unfortunately, although it is supposed to be nearly a drop-in replacement, in this instance it is not. The Gtk team decided to make the default constructors protected. Already this conflicts with how Gtk::FileChooserDialog is being used. The alternative, Gtk::FileChooserNative::create() returns the world's worst smart pointer Glib::RefPtr which cannot be de-referenced.

Current state in src/platform/guigtk.cpp:

FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) {
    Gtk::Window &gtkParent = std::static_pointer_cast<WindowImplGtk>(parentWindow)->gtkWindow;
    Gtk::FileChooserDialog gtkDialog(gtkParent, C_("title", "Open File"),
                                     Gtk::FILE_CHOOSER_ACTION_OPEN);
    gtkDialog.add_button(C_("button", "_Cancel"), Gtk::RESPONSE_CANCEL);
    gtkDialog.add_button(C_("button", "_Open"), Gtk::RESPONSE_OK);
    gtkDialog.set_default_response(Gtk::RESPONSE_OK);
    return std::make_shared<FileDialogImplGtk>(std::move(gtkDialog));

}
@whitequark
Copy link
Contributor

Thanks, I wasn't aware that FileChooserNative was a thing.

@whitequark
Copy link
Contributor

whitequark commented May 8, 2019

Well, I've implemented this. Unfortunately I have absolutely no idea how to get it to work, so now you're on your own; I have the xdg-desktop-portal and xdg-desktop-portal-kde packages installed, the services are running, but GTK isn't even trying to query them according to dbus-monitor. It's just displaying the normal dialog.

patch
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 874e88d..91a01c4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -13,6 +13,14 @@ endif()
 
 set(HAVE_SPACEWARE ${SPACEWARE_FOUND})
 
+if(NOT WIN32 OR APPLE)
+    if(GTKMM_gtkmm-3.0_VERSION VERSION_LESS "3.24.0")
+        set(HAVE_GTK_FILECHOOSERNATIVE 0)
+    else()
+        set(HAVE_GTK_FILECHOOSERNATIVE 1)
+    endif()
+endif()
+
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
                ${CMAKE_CURRENT_BINARY_DIR}/config.h)
 
diff --git a/src/config.h.in b/src/config.h.in
index 2f210d7..475b973 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -16,4 +16,7 @@
 #cmakedefine HAVE_BACKTRACE
 #define BACKTRACE_HEADER @BACKTRACE_HEADER@
 
+/* If we use GTK, can we use the native file chooser? */
+#cmakedefine HAVE_GTK_FILECHOOSERNATIVE
+
 #endif
diff --git a/src/platform/guigtk.cpp b/src/platform/guigtk.cpp
index 8f57afd..d8aeb24 100644
--- a/src/platform/guigtk.cpp
+++ b/src/platform/guigtk.cpp
@@ -15,6 +15,10 @@
 #include <gtkmm/cssprovider.h>
 #include <gtkmm/entry.h>
 #include <gtkmm/filechooserdialog.h>
+#define HAVE_GTK_FILECHOOSERNATIVE
+#if defined(HAVE_GTK_FILECHOOSERNATIVE)
+#   include <gtkmm/filechoosernative.h>
+#endif
 #include <gtkmm/fixed.h>
 #include <gtkmm/glarea.h>
 #include <gtkmm/main.h>
@@ -1187,32 +1191,27 @@ MessageDialogRef CreateMessageDialog(WindowRef parentWindow) {
 // File dialogs
 //-----------------------------------------------------------------------------
 
-class FileDialogImplGtk final : public FileDialog {
+class FileDialogImplGtk : public FileDialog {
 public:
-    Gtk::FileChooserDialog      gtkDialog;
+    Gtk::FileChooser           *gtkChooser;
     std::vector<std::string>    extensions;
 
-    FileDialogImplGtk(Gtk::FileChooserDialog &&dialog)
-        : gtkDialog(std::move(dialog))
-    {
-        gtkDialog.property_filter().signal_changed().
+    void InitFileChooser(Gtk::FileChooser &chooser) {
+        gtkChooser = &chooser;
+        gtkChooser->property_filter().signal_changed().
             connect(sigc::mem_fun(this, &FileDialogImplGtk::FilterChanged));
     }
 
-    void SetTitle(std::string title) override {
-        gtkDialog.set_title(PrepareTitle(title));
-    }
-
     void SetCurrentName(std::string name) override {
-        gtkDialog.set_current_name(name);
+        gtkChooser->set_current_name(name);
     }
 
     Platform::Path GetFilename() override {
-        return Path::From(gtkDialog.get_filename());
+        return Path::From(gtkChooser->get_filename());
     }
 
     void SetFilename(Platform::Path path) override {
-        gtkDialog.set_filename(path.raw);
+        gtkChooser->set_filename(path.raw);
     }
 
     void AddFilter(std::string name, std::vector<std::string> extensions) override {
@@ -1233,13 +1232,13 @@ public:
         gtkFilter->set_name(name + " (" + desc + ")");
 
         this->extensions.push_back(extensions.front());
-        gtkDialog.add_filter(gtkFilter);
+        gtkChooser->add_filter(gtkFilter);
     }
 
     std::string GetExtension() {
-        auto filters = gtkDialog.list_filters();
+        auto filters = gtkChooser->list_filters();
         size_t filterIndex =
-            std::find(filters.begin(), filters.end(), gtkDialog.get_filter()) -
+            std::find(filters.begin(), filters.end(), gtkChooser->get_filter()) -
             filters.begin();
         if(filterIndex < extensions.size()) {
             return extensions[filterIndex];
@@ -1249,14 +1248,14 @@ public:
     }
 
     void SetExtension(std::string extension) {
-        auto filters = gtkDialog.list_filters();
+        auto filters = gtkChooser->list_filters();
         size_t extensionIndex =
             std::find(extensions.begin(), extensions.end(), extension) -
             extensions.begin();
         if(extensionIndex < filters.size()) {
-            gtkDialog.set_filter(filters[extensionIndex]);
+            gtkChooser->set_filter(filters[extensionIndex]);
         } else {
-            gtkDialog.set_filter(filters.front());
+            gtkChooser->set_filter(filters.front());
         }
     }
 
@@ -1270,21 +1269,46 @@ public:
 
     void FreezeChoices(SettingsRef settings, const std::string &key) override {
         settings->FreezeString("Dialog_" + key + "_Folder",
-                               gtkDialog.get_current_folder());
+                               gtkChooser->get_current_folder());
         settings->FreezeString("Dialog_" + key + "_Filter", GetExtension());
     }
 
     void ThawChoices(SettingsRef settings, const std::string &key) override {
-        gtkDialog.set_current_folder(settings->ThawString("Dialog_" + key + "_Folder"));
+        gtkChooser->set_current_folder(settings->ThawString("Dialog_" + key + "_Folder"));
         SetExtension(settings->ThawString("Dialog_" + key + "_Filter"));
     }
 
-    bool RunModal() override {
-        if(gtkDialog.get_action() == Gtk::FILE_CHOOSER_ACTION_SAVE &&
-                Path::From(gtkDialog.get_current_name()).FileStem().empty()) {
-            gtkDialog.set_current_name(std::string(_("untitled")) + "." + GetExtension());
+    void CheckForUntitledFile() {
+        if(gtkChooser->get_action() == Gtk::FILE_CHOOSER_ACTION_SAVE &&
+                Path::From(gtkChooser->get_current_name()).FileStem().empty()) {
+            gtkChooser->set_current_name(std::string(_("untitled")) + "." + GetExtension());
         }
+    }
+};
 
+class FileDialogGtkImplGtk final : public FileDialogImplGtk {
+public:
+    Gtk::FileChooserDialog      gtkDialog;
+
+    FileDialogGtkImplGtk(Gtk::Window &gtkParent, bool isSave)
+        : gtkDialog(gtkParent,
+                    isSave ? C_("title", "Save File")
+                           : C_("title", "Open File"),
+                    isSave ? Gtk::FILE_CHOOSER_ACTION_SAVE
+                           : Gtk::FILE_CHOOSER_ACTION_OPEN) {
+        gtkDialog.add_button(C_("button", "_Cancel"), Gtk::RESPONSE_CANCEL);
+        gtkDialog.add_button(isSave ? C_("button", "_Save")
+                                    : C_("button", "_Open"), Gtk::RESPONSE_OK);
+        gtkDialog.set_default_response(Gtk::RESPONSE_OK);
+        InitFileChooser(gtkDialog);
+    }
+
+    void SetTitle(std::string title) override {
+        gtkDialog.set_title(PrepareTitle(title));
+    }
+
+    bool RunModal() override {
+        CheckForUntitledFile();
         if(gtkDialog.run() == Gtk::RESPONSE_OK) {
             return true;
         } else {
@@ -1293,26 +1317,56 @@ public:
     }
 };
 
+#if defined(HAVE_GTK_FILECHOOSERNATIVE)
+
+class FileDialogNativeImplGtk final : public FileDialogImplGtk {
+public:
+    Glib::RefPtr<Gtk::FileChooserNative> gtkNative;
+
+    FileDialogNativeImplGtk(Gtk::Window &gtkParent, bool isSave) {
+        gtkNative = Gtk::FileChooserNative::create(
+            isSave ? C_("title", "Save File")
+                   : C_("title", "Open File"),
+            gtkParent,
+            isSave ? Gtk::FILE_CHOOSER_ACTION_SAVE
+                   : Gtk::FILE_CHOOSER_ACTION_OPEN,
+            isSave ? C_("button", "_Save")
+                   : C_("button", "_Open"),
+            C_("button", "_Cancel"));
+        // Seriously, GTK?!
+        InitFileChooser(*gtkNative.operator->());
+    }
+
+    void SetTitle(std::string title) override {
+        gtkNative->set_title(PrepareTitle(title));
+    }
+
+    bool RunModal() override {
+        CheckForUntitledFile();
+        if(gtkNative->run() == Gtk::RESPONSE_OK) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+};
+
+#endif
+
+#if defined(HAVE_GTK_FILECHOOSERNATIVE)
+#   define FILE_DIALOG_IMPL FileDialogNativeImplGtk
+#else
+#   define FILE_DIALOG_IMPL FileDialogGtkImplGtk
+#endif
+
 FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) {
     Gtk::Window &gtkParent = std::static_pointer_cast<WindowImplGtk>(parentWindow)->gtkWindow;
-    Gtk::FileChooserDialog gtkDialog(gtkParent, C_("title", "Open File"),
-                                     Gtk::FILE_CHOOSER_ACTION_OPEN);
-    gtkDialog.add_button(C_("button", "_Cancel"), Gtk::RESPONSE_CANCEL);
-    gtkDialog.add_button(C_("button", "_Open"), Gtk::RESPONSE_OK);
-    gtkDialog.set_default_response(Gtk::RESPONSE_OK);
-    return std::make_shared<FileDialogImplGtk>(std::move(gtkDialog));
-
+    return std::make_shared<FILE_DIALOG_IMPL>(gtkParent, /*isSave=*/false);
 }
 
 FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) {
     Gtk::Window &gtkParent = std::static_pointer_cast<WindowImplGtk>(parentWindow)->gtkWindow;
-    Gtk::FileChooserDialog gtkDialog(gtkParent, C_("title", "Save File"),
-                                     Gtk::FILE_CHOOSER_ACTION_SAVE);
-    gtkDialog.set_do_overwrite_confirmation(true);
-    gtkDialog.add_button(C_("button", "_Cancel"), Gtk::RESPONSE_CANCEL);
-    gtkDialog.add_button(C_("button", "_Save"), Gtk::RESPONSE_OK);
-    gtkDialog.set_default_response(Gtk::RESPONSE_OK);
-    return std::make_shared<FileDialogImplGtk>(std::move(gtkDialog));
+    return std::make_shared<FILE_DIALOG_IMPL>(gtkParent, /*isSave=*/true);
 }
 
 //-----------------------------------------------------------------------------

@iicurtis
Copy link
Author

iicurtis commented May 8, 2019

Huh, well your patch looks pretty similar to mine. Mine segfaults but it does bring up the native dialog.

I think this is going to change at some point, but until then, you need to have the environment variable GTK_USE_PORTAL=1

I'm running it with GTK_USE_PORTAL=1 ./bin/solvespace

@whitequark
Copy link
Contributor

OK, it works with that environment variable set.

@whitequark
Copy link
Contributor

Fixed in bc3e09e.

@whitequark
Copy link
Contributor

The alternative, Gtk::FileChooserNative::create() returns the world's worst smart pointer Glib::RefPtr which cannot be de-referenced.

I just found out that gtkmm 4 dispenses with that and uses std::shared_ptr. https://www.murrayc.com/permalink/2017/04/20/gtkmm-4-progress/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants