From 91dd41bf63b677f039e0f0cf9b0f78d0ed177dcc Mon Sep 17 00:00:00 2001 From: Benjamin Hennion <17818041+bhennion@users.noreply.github.com> Date: Wed, 14 Feb 2024 05:03:32 +0100 Subject: [PATCH] Add non-blocking gtk4-ready XojMsgBox::ReplaceFileQuestion() --- src/core/control/Control.cpp | 24 ++++++-------- src/core/control/jobs/BaseExportJob.cpp | 38 +++++++++++++---------- src/core/control/jobs/CustomExportJob.cpp | 4 ++- src/util/XojMsgBox.cpp | 27 ++++++++++++++++ src/util/gtk4_helper.cpp | 5 +++ src/util/include/util/XojMsgBox.h | 5 +++ src/util/include/util/gtk4_helper.h | 1 + 7 files changed, 73 insertions(+), 31 deletions(-) diff --git a/src/core/control/Control.cpp b/src/core/control/Control.cpp index 8d547f6cc733..b51eb8f8f0e5 100644 --- a/src/core/control/Control.cpp +++ b/src/core/control/Control.cpp @@ -1709,8 +1709,6 @@ auto Control::showSaveDialog() -> bool { gtk_file_chooser_dialog_new(_("Save File"), getGtkWindow(), GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Save"), GTK_RESPONSE_OK, nullptr); - gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), true); - GtkFileFilter* filterXoj = gtk_file_filter_new(); gtk_file_filter_set_name(filterXoj, _("Xournal++ files")); gtk_file_filter_add_mime_type(filterXoj, "application/x-xopp"); @@ -1721,14 +1719,10 @@ auto Control::showSaveDialog() -> bool { auto suggested_name = this->doc->createSaveFilename(Document::XOPP, this->settings->getDefaultSaveName()); this->doc->unlock(); - gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), Util::toGFilename(suggested_folder).c_str()); + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), Util::toGFile(suggested_folder), nullptr); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), Util::toGFilename(suggested_name).c_str()); - gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog), - Util::toGFilename(this->settings->getLastOpenPath()).c_str(), nullptr); - - gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), false); // handled below - - gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(this->getWindow()->getWindow())); + gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog), Util::toGFile(this->settings->getLastOpenPath()), + nullptr); while (true) { if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK) { @@ -1736,7 +1730,9 @@ auto Control::showSaveDialog() -> bool { return false; } - auto fileTmp = Util::fromGFilename(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog))); + auto fileTmp = Util::fromGFile( + xoj::util::GObjectSPtr(gtk_file_chooser_get_file(GTK_FILE_CHOOSER(dialog)), xoj::util::adopt) + .get()); Util::clearExtensions(fileTmp); fileTmp += ".xopp"; // Since we add the extension after the OK button, we have to check manually on existing files @@ -1745,13 +1741,13 @@ auto Control::showSaveDialog() -> bool { } } - auto filename = Util::fromGFilename(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog))); - settings->setLastSavePath(filename.parent_path()); + auto file = Util::fromGFile( + xoj::util::GObjectSPtr(gtk_file_chooser_get_file(GTK_FILE_CHOOSER(dialog)), xoj::util::adopt).get()); + settings->setLastSavePath(file.parent_path()); gtk_widget_destroy(dialog); this->doc->lock(); - - this->doc->setFilepath(filename); + this->doc->setFilepath(file); this->doc->unlock(); return true; diff --git a/src/core/control/jobs/BaseExportJob.cpp b/src/core/control/jobs/BaseExportJob.cpp index f95928079330..5235294fee06 100644 --- a/src/core/control/jobs/BaseExportJob.cpp +++ b/src/core/control/jobs/BaseExportJob.cpp @@ -12,17 +12,13 @@ #include "util/PathUtil.h" // for toGFilename, clearExtensions #include "util/PopupWindowWrapper.h" // for PopupWindowWrapper #include "util/XojMsgBox.h" // for XojMsgBox +#include "util/glib_casts.h" // for wrap_for_g_callback_v #include "util/i18n.h" // for _, FS, _F BaseExportJob::BaseExportJob(Control* control, const std::string& name): BlockingJob(control, name) {} BaseExportJob::~BaseExportJob() = default; -static GtkWidget* initDialog(GtkWindow* win) { - return gtk_file_chooser_dialog_new(_("Export PDF"), win, GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"), - GTK_RESPONSE_CANCEL, _("_Export"), GTK_RESPONSE_OK, nullptr); -} - void BaseExportJob::addFileFilterToDialog(GtkFileChooser* dialog, const std::string& name, const std::string& mime) { GtkFileFilter* filter = gtk_file_filter_new(); gtk_file_filter_set_name(filter, name.c_str()); @@ -54,7 +50,9 @@ auto BaseExportJob::checkOverwriteBackgroundPDF(fs::path const& file) const -> b } void BaseExportJob::showFileChooser(std::function onFileSelected, std::function onCancel) { - auto* dialog = initDialog(control->getGtkWindow()); + auto* dialog = + gtk_file_chooser_dialog_new(_("Export"), control->getGtkWindow(), GTK_FILE_CHOOSER_ACTION_SAVE, + _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Export"), GTK_RESPONSE_OK, nullptr); addFilterToDialog(GTK_FILE_CHOOSER(dialog)); Settings* settings = control->getSettings(); @@ -76,8 +74,8 @@ void BaseExportJob::showFileChooser(std::function onFileSelected, std::f onFileSelected(std::move(onFileSelected)), onCancel(std::move(onCancel)) { this->signalId = g_signal_connect( - dialog, "response", G_CALLBACK(+[](GtkDialog* dialog, int response, gpointer data) { - auto* self = static_cast(data); + dialog, "response", G_CALLBACK((+[](GtkDialog* dialog, int response, gpointer data) { + FileDlg* self = static_cast(data); auto* job = self->job; if (response == GTK_RESPONSE_OK) { auto file = Util::fromGFile( @@ -90,13 +88,21 @@ void BaseExportJob::showFileChooser(std::function onFileSelected, std::f const char* filterName = gtk_file_filter_get_name(gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog))); ; - if (job->testAndSetFilepath(std::move(file), filterName) && - job->control->askToReplace(job->filepath)) { - job->control->getSettings()->setLastSavePath(job->filepath.parent_path()); - self->onFileSelected(); - // Closing the window causes another "response" signal, which we want to ignore - g_signal_handler_disconnect(dialog, self->signalId); - gtk_window_close(GTK_WINDOW(dialog)); // Deletes self, don't do anything after this + if (job->testAndSetFilepath(std::move(file), filterName)) { + auto doExport = [self, dialog]() { + // Closing the window causes another "response" signal, which we want to ignore + g_signal_handler_disconnect(dialog, self->signalId); + gtk_window_close(GTK_WINDOW(dialog)); + self->job->control->getSettings()->setLastSavePath( + self->job->filepath.parent_path()); + self->onFileSelected(); + }; + if (!fs::exists(job->filepath)) { + doExport(); + } else { + XojMsgBox::replaceFileQuestion(GTK_WINDOW(dialog), job->filepath, + std::move(doExport), []() {}); + } } // else the dialog stays on until a suitable destination is found or cancel is hit. } else { self->onCancel(); @@ -104,7 +110,7 @@ void BaseExportJob::showFileChooser(std::function onFileSelected, std::f g_signal_handler_disconnect(dialog, self->signalId); gtk_window_close(GTK_WINDOW(dialog)); // Deletes self, don't do anything after this } - }), + })), this); } ~FileDlg() = default; diff --git a/src/core/control/jobs/CustomExportJob.cpp b/src/core/control/jobs/CustomExportJob.cpp index 9fab08c5a974..be40e0a97894 100644 --- a/src/core/control/jobs/CustomExportJob.cpp +++ b/src/core/control/jobs/CustomExportJob.cpp @@ -75,7 +75,9 @@ void CustomExportJob::showDialogAndRun() { } Util::execInUiThread([job]() { - // We wait for the first dialog to be closed before showing the second one + // We must wait for the FileChooser dialog to be closed before showing the next one, otherwise the + // FileChooser dialog stays on + // Todo(post-gtk4): try out without the execInUiThread xoj::popup::PopupWindowWrapper popup( job->control->getGladeSearchPath(), job->format, job->control->getCurrentPageNo() + 1, job->control->getDocument()->getPageCount(), [job](const xoj::popup::ExportDialog& dialog) { diff --git a/src/util/XojMsgBox.cpp b/src/util/XojMsgBox.cpp index 1f7b08658319..f36b3e3b8370 100644 --- a/src/util/XojMsgBox.cpp +++ b/src/util/XojMsgBox.cpp @@ -6,6 +6,7 @@ #include // for g_free, g_markup_escape_text, g_error_free #include "util/PopupWindowWrapper.h" +#include "util/Util.h" #include "util/gtk4_helper.h" #include "util/i18n.h" // for _, FS, _F #include "util/raii/CStringWrapper.h" @@ -177,6 +178,32 @@ auto XojMsgBox::replaceFileQuestion(GtkWindow* win, const std::string& msg) -> i return res; } +void XojMsgBox::replaceFileQuestion(GtkWindow* win, const fs::path& file, std::function overwrite, + std::function pickOtherPath) { + GtkWidget* dialog = gtk_message_dialog_new( + win, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", + FS(FORMAT_STR("The file {1} already exists! Do you want to replace it?") % file.filename().u8string()) + .c_str()); + if (win != nullptr) { + gtk_window_set_transient_for(GTK_WINDOW(dialog), win); + } + gtk_dialog_add_button(GTK_DIALOG(dialog), _("Select another name"), GTK_RESPONSE_CANCEL); + gtk_dialog_add_button(GTK_DIALOG(dialog), _("Replace"), GTK_RESPONSE_OK); + + xoj::popup::PopupWindowWrapper popup( + GTK_DIALOG(dialog), + [overwrite = std::move(overwrite), pickOtherPath = std::move(pickOtherPath)](int response) { + if (response == GTK_RESPONSE_OK) { + // Wait for the message dialog to close before executing the response + Util::execInUiThread(std::move(overwrite)); + } else { + // Wait for the message dialog to close before executing the response + Util::execInUiThread(std::move(pickOtherPath)); + } + }); + popup.show(win); +} + constexpr auto* XOJ_HELP = "https://xournalpp.github.io/community/help/"; void XojMsgBox::showHelp(GtkWindow* win) { diff --git a/src/util/gtk4_helper.cpp b/src/util/gtk4_helper.cpp index 7412ce355b9d..29a18ef4a6b4 100644 --- a/src/util/gtk4_helper.cpp +++ b/src/util/gtk4_helper.cpp @@ -3,6 +3,7 @@ #include #include "util/Assert.h" +#include "util/raii/CStringWrapper.h" namespace { void set_child(GtkContainer* c, GtkWidget* child) { @@ -122,6 +123,10 @@ void gtk_im_context_set_client_widget(GtkIMContext* context, GtkWidget* widget) } /**** GtkFileChooserDialog ****/ +gboolean gtk_file_chooser_add_shortcut_folder(GtkFileChooser* chooser, GFile* file, GError** error) { + auto uri = xoj::util::OwnedCString::assumeOwnership(g_file_get_uri(file)); + return gtk_file_chooser_add_shortcut_folder(chooser, uri.get(), error); +} gboolean gtk_file_chooser_set_current_folder(GtkFileChooser* chooser, GFile* file, GError** error) { return gtk_file_chooser_set_current_folder_file(chooser, file, error); } diff --git a/src/util/include/util/XojMsgBox.h b/src/util/include/util/XojMsgBox.h index 6610666c7708..fdb8f4ba7ffc 100644 --- a/src/util/include/util/XojMsgBox.h +++ b/src/util/include/util/XojMsgBox.h @@ -19,6 +19,8 @@ #include "util/raii/GtkWindowUPtr.h" +#include "filesystem.h" + class XojMsgBox final { public: XojMsgBox( @@ -69,4 +71,7 @@ class XojMsgBox final { bool error = false); static int replaceFileQuestion(GtkWindow* win, const std::string& msg); static void showHelp(GtkWindow* win); + + static void replaceFileQuestion(GtkWindow* win, const fs::path& file, std::function overwrite, + std::function pickOtherPath); }; diff --git a/src/util/include/util/gtk4_helper.h b/src/util/include/util/gtk4_helper.h index 2abce97f9ea8..961a1e7e5f19 100644 --- a/src/util/include/util/gtk4_helper.h +++ b/src/util/include/util/gtk4_helper.h @@ -72,4 +72,5 @@ void gtk_label_set_wrap_mode(GtkLabel* label, PangoWrapMode wrap_mode); void gtk_im_context_set_client_widget(GtkIMContext* context, GtkWidget* widget); /**** GtkFileChooserDialog ****/ +gboolean gtk_file_chooser_add_shortcut_folder(GtkFileChooser* chooser, GFile* file, GError** error); gboolean gtk_file_chooser_set_current_folder(GtkFileChooser* chooser, GFile* file, GError** error);