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).
This commit is contained in:
Lawrence Lee 2023-04-08 18:16:23 -07:00
parent dc1c667790
commit 19f12f3182
No known key found for this signature in database
GPG Key ID: 048FF2B76A63895F
10 changed files with 97 additions and 32 deletions

View File

@ -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

View File

@ -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<int> *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:

View File

@ -253,6 +253,7 @@ private:
Gtk::Button* navNext;
Gtk::Button* navPrev;
Glib::RefPtr<Gio::AppInfo> external_editor_info;
bool external_editor_native_command;
std::unique_ptr<RTAppChooserDialog> app_chooser_dialog;
ExternalEditorChangedSignal *externalEditorChangedSignal;
sigc::connection externalEditorChangedSignalConnection;

View File

@ -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<Gio::Icon>(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<Gio::AppInfo> 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);
}

View File

@ -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<Glib::ustring> name;
Gtk::TreeModelColumn<Glib::RefPtr<Gio::Icon>> icon;
Gtk::TreeModelColumn<Glib::ustring> command;
Gtk::TreeModelColumn<bool> native_command;
Gtk::TreeModelColumn<void *> 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.

View File

@ -344,8 +344,20 @@ bool ExtProgStore::openInCustomEditor (const Glib::ustring& fileName, const Glib
}
bool ExtProgStore::openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr<Gio::AppInfo> &editorInfo)
bool ExtProgStore::openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr<Gio::AppInfo> &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 {

View File

@ -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<Gio::AppInfo> &editorInfo);
static bool openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr<Gio::AppInfo> &editorInfo, bool nativeCommand);
};
#define extProgStore ExtProgStore::getInstance()

View File

@ -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<Glib::ustring>() :
static_cast<std::vector<Glib::ustring>>(
keyFile.get_string_list("External Editor", "Commands"));
const auto & native_commands =
!keyFile.has_key("External Editor", "NativeCommands") ?
std::vector<bool>() :
static_cast<std::vector<bool>>(
keyFile.get_boolean_list("External Editor", "NativeCommands"));
const auto & icons_serialized =
!keyFile.has_key("External Editor", "IconsSerialized") ?
std::vector<Glib::ustring>() :
@ -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<Glib::ustring> names;
std::vector<Glib::ustring> commands;
std::vector<bool> native_commands;
std::vector<Glib::ustring> 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

View File

@ -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;

View File

@ -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<ExternalEditorPreferences::EditorInfo> 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<int>(moptions.externalEditors.size());
parent->updateExternalEditorWidget(selected_index, moptions.externalEditors);
}
if (moptions.cloneFavoriteTools != options.cloneFavoriteTools ||