From 19f12f3182d3c2e4a648c607da4a0ac7fb9346ec Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 8 Apr 2023 18:16:23 -0700 Subject: [PATCH] Allow native commands as external editors If an editor is marked as a native command, it is launched using the older method (native for Windows or Glib otherwise). Non-native commands are launched with Gio. When reading preferences containing the old style external editor settings, all commands are marked as native to avoid breaking them. Fix bug where the send to editor button shows the wrong editor. The bug happens when the other editor option is selected while the user edits the external editors in preferences. When saved, the button shows the first option is selected instead of the other editor option (the last entry). --- rtdata/languages/default | 3 ++- rtgui/editorpanel.cc | 4 ++- rtgui/editorpanel.h | 1 + rtgui/externaleditorpreferences.cc | 39 +++++++++++++++++++++------ rtgui/externaleditorpreferences.h | 10 +++++++ rtgui/extprog.cc | 14 +++++++++- rtgui/extprog.h | 2 +- rtgui/options.cc | 43 +++++++++++++++++++----------- rtgui/options.h | 3 ++- rtgui/preferences.cc | 10 ++++--- 10 files changed, 97 insertions(+), 32 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index 8f3a59097..45471bb5c 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1883,8 +1883,9 @@ PREFERENCES_EXTEDITOR_FLOAT32;32-bit float TIFF output PREFERENCES_EXTERNALEDITOR;External Editor PREFERENCES_EXTERNALEDITOR_CHANGE;Change Application PREFERENCES_EXTERNALEDITOR_CHANGE_FILE;Change Executable -PREFERENCES_EXTERNALEDITOR_COLUMN_NAME;Name PREFERENCES_EXTERNALEDITOR_COLUMN_COMMAND;Command +PREFERENCES_EXTERNALEDITOR_COLUMN_NAME;Name +PREFERENCES_EXTERNALEDITOR_COLUMN_NATIVE_COMMAND;Native command PREFERENCES_FBROWSEROPTS;File Browser / Thumbnail Options PREFERENCES_FILEBROWSERTOOLBARSINGLEROW;Compact toolbars in File Browser PREFERENCES_FLATFIELDFOUND;Found diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index ffe13fea3..420717b7e 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -2250,6 +2250,7 @@ void EditorPanel::sendToExternalPressed() } else { struct ExternalEditor editor = options.externalEditors.at(options.externalEditorIndex); external_editor_info = Gio::AppInfo::create_from_commandline(editor.command, editor.name, Gio::APP_INFO_CREATE_NONE); + external_editor_native_command = editor.native_command; sendToExternal(); } } @@ -2415,7 +2416,7 @@ bool EditorPanel::idle_sentToGimp (ProgressConnector *pc, rtengine::IImagef setUserOnlyPermission(Gio::File::create_for_path(filename), false); - success = ExtProgStore::openInExternalEditor(filename, external_editor_info); + success = ExtProgStore::openInExternalEditor(filename, external_editor_info, external_editor_native_command); if (!success) { Gtk::MessageDialog msgd (*parent, M ("MAIN_MSG_CANNOTSTARTEDITOR"), false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); @@ -2447,6 +2448,7 @@ void EditorPanel::onAppChooserDialogResponse(int responseId) case Gtk::RESPONSE_OK: getAppChooserDialog()->close(); external_editor_info = getAppChooserDialog()->get_app_info(); + external_editor_native_command = false; sendToExternal(); break; case Gtk::RESPONSE_CANCEL: diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index 98a475a7c..309f14e8c 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -253,6 +253,7 @@ private: Gtk::Button* navNext; Gtk::Button* navPrev; Glib::RefPtr external_editor_info; + bool external_editor_native_command; std::unique_ptr app_chooser_dialog; ExternalEditorChangedSignal *externalEditorChangedSignal; sigc::connection externalEditorChangedSignalConnection; diff --git a/rtgui/externaleditorpreferences.cc b/rtgui/externaleditorpreferences.cc index 79dac52d2..55c9f7956 100644 --- a/rtgui/externaleditorpreferences.cc +++ b/rtgui/externaleditorpreferences.cc @@ -37,6 +37,7 @@ ExternalEditorPreferences::ExternalEditorPreferences(): list_view = Gtk::manage(new Gtk::TreeView()); list_view->set_model(list_model); list_view->append_column(*Gtk::manage(makeAppColumn())); + list_view->append_column(*Gtk::manage(makeNativeCommandColumn())); list_view->append_column(*Gtk::manage(makeCommandColumn())); for (auto &&column : list_view->get_columns()) { @@ -97,12 +98,12 @@ ExternalEditorPreferences::getEditors() const for (auto rowIter = children.begin(); rowIter != children.end(); rowIter++) { const Gio::Icon *const icon = rowIter->get_value(model_columns.icon).get(); const auto &icon_serialized = icon == nullptr ? "" : icon->serialize().print(); - editors.push_back(ExternalEditorPreferences::EditorInfo( - rowIter->get_value(model_columns.name), - rowIter->get_value(model_columns.command), - icon_serialized, - rowIter->get_value(model_columns.other_data) - )); + editors.emplace_back( + rowIter->get_value(model_columns.name), + rowIter->get_value(model_columns.command), + icon_serialized, + rowIter->get_value(model_columns.native_command), + rowIter->get_value(model_columns.other_data)); } return editors; @@ -138,6 +139,7 @@ void ExternalEditorPreferences::setEditors( row[model_columns.name] = editor.name; row[model_columns.icon] = icon; row[model_columns.command] = editor.command; + row[model_columns.native_command] = editor.native_command; row[model_columns.other_data] = editor.other_data; } } @@ -194,6 +196,24 @@ Gtk::TreeViewColumn *ExternalEditorPreferences::makeCommandColumn() return col; } +Gtk::TreeViewColumn *ExternalEditorPreferences::makeNativeCommandColumn() +{ + auto toggle_renderer = Gtk::manage(new Gtk::CellRendererToggle()); + auto col = Gtk::manage(new Gtk::TreeViewColumn()); + + col->set_title(M("PREFERENCES_EXTERNALEDITOR_COLUMN_NATIVE_COMMAND")); + col->pack_start(*toggle_renderer); + col->add_attribute(toggle_renderer->property_active(), model_columns.native_command); + + toggle_renderer->signal_toggled().connect([this](const Glib::ustring &path) { + const auto row_iter = list_model->get_iter(path); + bool new_value = !row_iter->get_value(model_columns.native_command); + row_iter->set_value(model_columns.native_command, new_value); + }); + + return col; +} + void ExternalEditorPreferences::onAppChooserDialogResponse( int response_id, RTAppChooserDialog *dialog) { @@ -224,6 +244,7 @@ void ExternalEditorPreferences::onFileChooserDialogResponse( for (const auto &selected : selection) { auto row = *list_model->get_iter(selected); row[model_columns.icon] = Glib::RefPtr(nullptr); + row[model_columns.native_command] = false; row[model_columns.command] = #ifdef WIN32 '"' + dialog->get_filename() + '"'; @@ -313,6 +334,7 @@ void ExternalEditorPreferences::setApp(const Glib::RefPtr app_info row[model_columns.icon] = app_info->get_icon(); row[model_columns.name] = app_info->get_name(); row[model_columns.command] = app_info->get_commandline(); + row[model_columns.native_command] = false; } } @@ -344,8 +366,8 @@ void ExternalEditorPreferences::updateToolbarSensitivity() } ExternalEditorPreferences::EditorInfo::EditorInfo( - Glib::ustring name, Glib::ustring command, Glib::ustring icon_serialized, void *other_data -) : name(name), icon_serialized(icon_serialized), command(command), other_data(other_data) + Glib::ustring name, Glib::ustring command, Glib::ustring icon_serialized, bool native_command, void *other_data +) : name(name), icon_serialized(icon_serialized), command(command), native_command(native_command), other_data(other_data) { } @@ -354,5 +376,6 @@ ExternalEditorPreferences::ModelColumns::ModelColumns() add(name); add(icon); add(command); + add(native_command); add(other_data); } diff --git a/rtgui/externaleditorpreferences.h b/rtgui/externaleditorpreferences.h index 34658d942..26903371c 100644 --- a/rtgui/externaleditorpreferences.h +++ b/rtgui/externaleditorpreferences.h @@ -50,6 +50,7 @@ public: Glib::ustring name = Glib::ustring(), Glib::ustring command = Glib::ustring(), Glib::ustring icon_serialized = Glib::ustring(), + bool native_command = false, void *other_data = nullptr ); /** @@ -65,6 +66,10 @@ public: * Gio::AppInfo::get_commandline() */ Glib::ustring command; + /** + * Use the OS native launcher instead of Gio. + */ + bool native_command; /** * Holds any other data associated with the editor. For example, it can * be used as a tag to uniquely identify the editor. @@ -96,6 +101,7 @@ private: Gtk::TreeModelColumn name; Gtk::TreeModelColumn> icon; Gtk::TreeModelColumn command; + Gtk::TreeModelColumn native_command; Gtk::TreeModelColumn other_data; }; @@ -124,6 +130,10 @@ private: * Constructs the column for displaying an editable commandline. */ Gtk::TreeViewColumn *makeCommandColumn(); + /** + * Constructs the column for displaying the native command toggle. + */ + Gtk::TreeViewColumn *makeNativeCommandColumn(); /** * Called when the user is done interacting with the app chooser dialog. * Closes the dialog and updates the selected entry if an app was chosen. diff --git a/rtgui/extprog.cc b/rtgui/extprog.cc index 9ec87c548..ea1800638 100644 --- a/rtgui/extprog.cc +++ b/rtgui/extprog.cc @@ -344,8 +344,20 @@ bool ExtProgStore::openInCustomEditor (const Glib::ustring& fileName, const Glib } -bool ExtProgStore::openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr &editorInfo) +bool ExtProgStore::openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr &editorInfo, bool nativeCommand) { + if (nativeCommand) { + if (rtengine::settings->verbose) { + std::cout << "Launching external editor as native command." << std::endl; + } + const Glib::ustring command = editorInfo->get_commandline(); + return openInCustomEditor(fileName, &command); + } + + if (rtengine::settings->verbose) { + std::cout << "Launching external editor with Gio." << std::endl; + } + bool success = false; try { diff --git a/rtgui/extprog.h b/rtgui/extprog.h index 6547896ef..5336c4703 100644 --- a/rtgui/extprog.h +++ b/rtgui/extprog.h @@ -70,7 +70,7 @@ public: static bool openInGimp (const Glib::ustring& fileName); static bool openInPhotoshop (const Glib::ustring& fileName); static bool openInCustomEditor (const Glib::ustring& fileName, const Glib::ustring* command = nullptr); - static bool openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr &editorInfo); + static bool openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr &editorInfo, bool nativeCommand); }; #define extProgStore ExtProgStore::getInstance() diff --git a/rtgui/options.cc b/rtgui/options.cc index b59e7c203..d6c3f7d25 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -873,6 +873,7 @@ void Options::readFromFile(Glib::ustring fname) if (keyFile.has_group("External Editor")) { if (keyFile.has_key("External Editor", "Names") || keyFile.has_key("External Editor", "Commands") + || keyFile.has_key("External Editor", "NativeCommands") || keyFile.has_key("External Editor", "IconsSerialized")) { // Multiple external editors. @@ -886,6 +887,11 @@ void Options::readFromFile(Glib::ustring fname) std::vector() : static_cast>( keyFile.get_string_list("External Editor", "Commands")); + const auto & native_commands = + !keyFile.has_key("External Editor", "NativeCommands") ? + std::vector() : + static_cast>( + keyFile.get_boolean_list("External Editor", "NativeCommands")); const auto & icons_serialized = !keyFile.has_key("External Editor", "IconsSerialized") ? std::vector() : @@ -899,6 +905,9 @@ void Options::readFromFile(Glib::ustring fname) for (unsigned i = 0; i < commands.size(); i++) { externalEditors[i].command = commands[i]; } + for (unsigned i = 0; i < native_commands.size(); i++) { + externalEditors[i].native_command = native_commands[i]; + } for (unsigned i = 0; i < icons_serialized.size(); i++) { externalEditors[i].icon_serialized = icons_serialized[i]; } @@ -938,7 +947,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", getIconSerialized(executable))); + externalEditors.emplace_back("GIMP", executable, true, getIconSerialized(executable)); } else { for (auto ver = 12; ver >= 0; --ver) { executable = Glib::build_filename(gimpDir, "bin", Glib::ustring::compose(Glib::ustring("gimp-2.%1.exe"), ver)); @@ -946,7 +955,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", getIconSerialized(executable))); + externalEditors.emplace_back("GIMP", executable, true, getIconSerialized(executable)); break; } } @@ -961,7 +970,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 2) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("Photoshop", "\"" + executable + "\"", getIconSerialized(executable))); + externalEditors.emplace_back("Photoshop", executable, true, getIconSerialized(executable)); } if (keyFile.has_key("External Editor", "CustomEditor")) { @@ -970,20 +979,20 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 3) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("-", "\"" + executable + "\"", "")); + externalEditors.emplace_back("-", executable, true, ""); } } #elif defined __APPLE__ if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "open -a GIMP", "gimp")); - externalEditors.push_back(ExternalEditor("GIMP-dev", "open -a GIMP-dev", "gimp")); + externalEditors.emplace_back("GIMP", "open -a GIMP", true, ""); + externalEditors.emplace_back("GIMP-dev", "open -a GIMP-dev", true, ""); if (editorToSendTo == 2) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("Photoshop", "open -a Photoshop", "")); + externalEditors.emplace_back("Photoshop", "open -a Photoshop", true, ""); if (keyFile.has_key("External Editor", "CustomEditor")) { auto executable = keyFile.get_string("External Editor", "CustomEditor"); @@ -991,20 +1000,21 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 3) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("-", executable, "")); + externalEditors.emplace_back("-", executable, true, ""); } } #else + const Glib::ustring gimp_icon_serialized = "('themed', <['gimp', 'gimp-symbolic']>)"; if (Glib::find_program_in_path("gimp").compare("")) { if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "gimp", "gimp")); + externalEditors.emplace_back("GIMP", "gimp", true, gimp_icon_serialized); } else if (Glib::find_program_in_path("gimp-remote").compare("")) { if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "gimp-remote", "gimp")); + externalEditors.emplace_back("GIMP", "gimp-remote", true, gimp_icon_serialized); } if (keyFile.has_key("External Editor", "CustomEditor")) { @@ -1013,7 +1023,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 3) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("-", executable, "")); + externalEditors.emplace_back("-", executable, true, ""); } } #endif @@ -2328,16 +2338,19 @@ void Options::saveToFile(Glib::ustring fname) { std::vector names; std::vector commands; + std::vector native_commands; std::vector icons_serialized; for (const auto & editor : externalEditors) { names.push_back(editor.name); commands.push_back(editor.command); + native_commands.push_back(editor.native_command); icons_serialized.push_back(editor.icon_serialized); } keyFile.set_string_list("External Editor", "Names", names); keyFile.set_string_list("External Editor", "Commands", commands); + keyFile.set_boolean_list("External Editor", "NativeCommands", native_commands); keyFile.set_string_list("External Editor", "IconsSerialized", icons_serialized); keyFile.set_integer("External Editor", "EditorIndex", externalEditorIndex); @@ -3009,15 +3022,15 @@ Glib::ustring Options::getICCProfileCopyright() return Glib::ustring::compose("Copyright RawTherapee %1, CC0", now.get_year()); } -ExternalEditor::ExternalEditor() {} +ExternalEditor::ExternalEditor() = default; ExternalEditor::ExternalEditor( - const Glib::ustring &name, const Glib::ustring &command, const Glib::ustring &icon_serialized -): name(name), command(command), icon_serialized(icon_serialized) {} + const Glib::ustring &name, const Glib::ustring &command, bool native_command, const Glib::ustring &icon_serialized +): name(name), command(command), native_command(native_command), icon_serialized(icon_serialized) {} bool ExternalEditor::operator==(const ExternalEditor &other) const { - return this->name == other.name && this->command == other.command && this->icon_serialized == other.icon_serialized; + return this->name == other.name && this->command == other.command && this->native_command == other.native_command && this->icon_serialized == other.icon_serialized; } bool ExternalEditor::operator!=(const ExternalEditor &other) const diff --git a/rtgui/options.h b/rtgui/options.h index 2c34260aa..78059357e 100644 --- a/rtgui/options.h +++ b/rtgui/options.h @@ -54,9 +54,10 @@ struct ExternalEditor { ExternalEditor(); - ExternalEditor(const Glib::ustring &name, const Glib::ustring &command, const Glib::ustring &icon_serialized); + ExternalEditor(const Glib::ustring &name, const Glib::ustring &command, bool native_command, const Glib::ustring &icon_serialized); Glib::ustring name; Glib::ustring command; + bool native_command; Glib::ustring icon_serialized; bool operator==(const ExternalEditor & other) const; diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 7d95ef716..a97cb003d 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -1752,7 +1752,7 @@ void Preferences::storePreferences() moptions.externalEditorIndex = -1; for (unsigned i = 0; i < editors.size(); i++) { moptions.externalEditors[i] = (ExternalEditor( - editors[i].name, editors[i].command, editors[i].icon_serialized)); + editors[i].name, editors[i].command, editors[i].native_command, editors[i].icon_serialized)); if (editors[i].other_data) { // The current editor was marked before the list was edited. We // found the mark, so this is the editor that was active. @@ -2034,8 +2034,7 @@ void Preferences::fillPreferences() std::vector editorInfos; for (const auto &editor : moptions.externalEditors) { - editorInfos.push_back(ExternalEditorPreferences::EditorInfo( - editor.name, editor.command, editor.icon_serialized)); + editorInfos.emplace_back(editor.name, editor.command, editor.icon_serialized, editor.native_command); } if (moptions.externalEditorIndex >= 0) { // Mark the current editor so we can track it. @@ -2536,7 +2535,10 @@ void Preferences::workflowUpdate() } if (changed) { // Update the send to external editor widget. - parent->updateExternalEditorWidget(moptions.externalEditorIndex, moptions.externalEditors); + int selected_index = moptions.externalEditorIndex >= 0 + ? moptions.externalEditorIndex + : static_cast(moptions.externalEditors.size()); + parent->updateExternalEditorWidget(selected_index, moptions.externalEditors); } if (moptions.cloneFavoriteTools != options.cloneFavoriteTools ||