From 453fb961f203af59e0cd3790bb16fab15f41bd32 Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Sun, 28 Mar 2021 18:12:47 -0700 Subject: [PATCH 01/25] Create a preferences widget for external editors The widget is intended to be embedded in the preferences dialog. It displays a list of external editors using an icon, name, and command for each editor. The name and command can be edited. Editors can be added, removed, and rearranged. A button allows one to set an editor's information by selecting from a list of installed applications. --- rtgui/externaleditorpreferences.cc | 261 +++++++++++++++++++++++++++++ rtgui/externaleditorpreferences.h | 147 ++++++++++++++++ 2 files changed, 408 insertions(+) create mode 100644 rtgui/externaleditorpreferences.cc create mode 100644 rtgui/externaleditorpreferences.h diff --git a/rtgui/externaleditorpreferences.cc b/rtgui/externaleditorpreferences.cc new file mode 100644 index 000000000..df6b6efc2 --- /dev/null +++ b/rtgui/externaleditorpreferences.cc @@ -0,0 +1,261 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2021 Lawrence Lee + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ +#include "externaleditorpreferences.h" +#include "multilangmgr.h" +#include "rtimage.h" + + +ExternalEditorPreferences::ExternalEditorPreferences(): + Box(Gtk::Orientation::ORIENTATION_VERTICAL), + list_model(Gtk::ListStore::create(model_columns)), + toolbar(Gtk::Orientation::ORIENTATION_HORIZONTAL) +{ + // List view. + list_view = Gtk::make_managed(); + list_view->set_model(list_model); + list_view->append_column(*Gtk::manage(makeAppColumn())); + list_view->append_column(*Gtk::manage(makeCommandColumn())); + + for (auto &&column : list_view->get_columns()) { + column->set_sizing(Gtk::TreeViewColumnSizing::TREE_VIEW_COLUMN_FIXED); + } + + list_view->set_grid_lines(Gtk::TREE_VIEW_GRID_LINES_VERTICAL); + list_view->set_reorderable(); + + // List scroll area. + list_scroll_area.set_hexpand(); + list_scroll_area.set_vexpand(); + list_scroll_area.add(*list_view); + + // Toolbar buttons. + auto add_image = Gtk::make_managed("add-small.png"); + auto remove_image = Gtk::make_managed("remove-small.png"); + button_add = Gtk::make_managed(); + button_remove = Gtk::make_managed(); + button_add->set_image(*add_image); + button_remove->set_image(*remove_image); + button_app_chooser = Gtk::make_managed(M("PREFERENCES_EXTERNALEDITOR_CHANGE")); + + button_app_chooser->signal_pressed().connect(sigc::mem_fun( + *this, &ExternalEditorPreferences::openAppChooserDialog)); + button_add->signal_pressed().connect(sigc::mem_fun( + *this, &ExternalEditorPreferences::addEditor)); + button_remove->signal_pressed().connect(sigc::mem_fun( + *this, &ExternalEditorPreferences::removeSelectedEditors)); + + list_view->get_selection()->signal_changed().connect(sigc::mem_fun( + *this, &ExternalEditorPreferences::updateToolbarSensitivity)); + updateToolbarSensitivity(); + + // Toolbar. + toolbar.set_halign(Gtk::Align::ALIGN_END); + toolbar.add(*button_app_chooser); + toolbar.add(*button_add); + toolbar.add(*button_remove); + + // This widget's children. + add(list_scroll_area); + add(toolbar); + show_all(); +} + +std::vector +ExternalEditorPreferences::getEditors() const +{ + std::vector editors; + + auto children = list_model->children(); + + for (auto rowIter = children.begin(); rowIter != children.end(); rowIter++) { + const Gio::Icon *const icon = rowIter->get_value(model_columns.icon).get(); + const auto &icon_name = icon == nullptr ? "" : icon->to_string(); + editors.push_back(ExternalEditorPreferences::EditorInfo( + rowIter->get_value(model_columns.name), + rowIter->get_value(model_columns.command), + icon_name, + rowIter->get_value(model_columns.other_data) + )); + } + + return editors; +} + +void ExternalEditorPreferences::setEditors( + const std::vector &editors) +{ + list_model->clear(); + + for (const ExternalEditorPreferences::EditorInfo & editor : editors) { + auto row = *list_model->append(); + row[model_columns.name] = editor.name; + row[model_columns.icon] = editor.icon_name.empty() ? Glib::RefPtr() : Gio::Icon::create(editor.icon_name); + row[model_columns.command] = editor.command; + row[model_columns.other_data] = editor.other_data; + } +} + +void ExternalEditorPreferences::addEditor() +{ + Gtk::TreeModel::Row row; + auto selected = list_view->get_selection()->get_selected_rows(); + + if (selected.size()) { + row = *list_model->insert_after(list_model->get_iter(selected.back())); + } else { + row = *list_model->append(); + } + + row[model_columns.name] = "-"; + list_view->get_selection()->select(row); +} + +Gtk::TreeViewColumn *ExternalEditorPreferences::makeAppColumn() +{ + auto name_renderer = Gtk::make_managed(); + auto icon_renderer = Gtk::make_managed(); + auto col = Gtk::make_managed(); + + col->set_title(M("PREFERENCES_EXTERNALEDITOR_COLUMN_NAME")); + col->set_resizable(); + col->pack_start(*icon_renderer, false); + col->pack_start(*name_renderer); + col->add_attribute(*icon_renderer, "gicon", model_columns.icon); + col->add_attribute(*name_renderer, "text", model_columns.name); + col->set_min_width(20); + + name_renderer->property_editable() = true; + name_renderer->signal_edited().connect( + sigc::mem_fun(*this, &ExternalEditorPreferences::setAppName)); + + return col; +} + +Gtk::TreeViewColumn *ExternalEditorPreferences::makeCommandColumn() +{ + auto command_renderer = Gtk::make_managed(); + auto col = Gtk::make_managed(); + + col->set_title(M("PREFERENCES_EXTERNALEDITOR_COLUMN_COMMAND")); + col->pack_start(*command_renderer); + col->add_attribute(*command_renderer, "text", model_columns.command); + + command_renderer->property_editable() = true; + command_renderer->signal_edited().connect( + sigc::mem_fun(*this, &ExternalEditorPreferences::setAppCommand)); + + return col; +} + +void ExternalEditorPreferences::onAppChooserDialogResponse( + int response_id, Gtk::AppChooserDialog *dialog) +{ + switch (response_id) { + case Gtk::RESPONSE_OK: + dialog->close(); + setApp(dialog->get_app_info()); + break; + + case Gtk::RESPONSE_CANCEL: + case Gtk::RESPONSE_CLOSE: + dialog->close(); + break; + + default: + break; + } +} + +void ExternalEditorPreferences::openAppChooserDialog() +{ + if (app_chooser_dialog.get()) { + app_chooser_dialog->refresh(); + app_chooser_dialog->show(); + return; + } + + app_chooser_dialog.reset(new Gtk::AppChooserDialog("image/tiff")); + app_chooser_dialog->signal_response().connect(sigc::bind( + sigc::mem_fun(*this, &ExternalEditorPreferences::onAppChooserDialogResponse), + app_chooser_dialog.get() + )); + app_chooser_dialog->set_modal(); + app_chooser_dialog->show(); +} + +void ExternalEditorPreferences::removeSelectedEditors() +{ + auto selection = list_view->get_selection()->get_selected_rows(); + + for (const auto &selected : selection) { + list_model->erase(list_model->get_iter(selected)); + } +} + +void ExternalEditorPreferences::setApp(const Glib::RefPtr app_info) +{ + auto selection = list_view->get_selection()->get_selected_rows(); + + for (const auto &selected : selection) { + auto row = *list_model->get_iter(selected); + row[model_columns.icon] = app_info->get_icon(); + row[model_columns.name] = app_info->get_name(); + row[model_columns.command] = app_info->get_commandline(); + } +} + +void ExternalEditorPreferences::setAppCommand( + const Glib::ustring & path, const Glib::ustring & new_text) +{ + auto row_iter = list_model->get_iter(path); + + if (!row_iter->get_value(model_columns.command).compare(new_text)) { + return; + } + + row_iter->set_value(model_columns.command, new_text); + row_iter->set_value(model_columns.icon, Glib::RefPtr(nullptr)); +} + +void ExternalEditorPreferences::setAppName( + const Glib::ustring & path, const Glib::ustring & new_text) +{ + list_model->get_iter(path)->set_value(model_columns.name, new_text); +} + +void ExternalEditorPreferences::updateToolbarSensitivity() +{ + bool selected = list_view->get_selection()->count_selected_rows(); + button_app_chooser->set_sensitive(selected); + button_remove->set_sensitive(selected); +} + +ExternalEditorPreferences::EditorInfo::EditorInfo( + Glib::ustring name, Glib::ustring command, Glib::ustring icon_name, void *other_data +) : name(name), icon_name(icon_name), command(command), other_data(other_data) +{ +} + +ExternalEditorPreferences::ModelColumns::ModelColumns() +{ + add(name); + add(icon); + add(command); + add(other_data); +} diff --git a/rtgui/externaleditorpreferences.h b/rtgui/externaleditorpreferences.h new file mode 100644 index 000000000..be988e901 --- /dev/null +++ b/rtgui/externaleditorpreferences.h @@ -0,0 +1,147 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2021 Lawrence Lee + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +/** + * Widget for editing the external editors options. + */ +class ExternalEditorPreferences : public Gtk::Box +{ +public: + /** + * Data struct containing information about an external editor. + */ + struct EditorInfo { + explicit EditorInfo( + Glib::ustring name = Glib::ustring(), + Glib::ustring command = Glib::ustring(), + Glib::ustring icon_name = Glib::ustring(), + void *other_data = nullptr + ); + /** + * Name of the external editor. + */ + Glib::ustring name; + /** + * The string representation of the icon. See Gio::Icon::to_string(). + */ + Glib::ustring icon_name; + /** + * The commandline for running the program. See + * Gio::AppInfo::get_commandline() + */ + Glib::ustring command; + /** + * Holds any other data associated with the editor. For example, it can + * be used as a tag to uniquely identify the editor. + */ + void *other_data; + }; + + ExternalEditorPreferences(); + + /** + * Creates and returns a vector representing the external editors shown in + * this widget. + */ + std::vector getEditors() const; + /** + * Populates this widget with the external editors described in the + * argument. + */ + void setEditors(const std::vector &editors); + +private: + /** + * Model representing the data fields each external editor entry has. + */ + class ModelColumns : public Gtk::TreeModelColumnRecord + { + public: + ModelColumns(); + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn> icon; + Gtk::TreeModelColumn command; + Gtk::TreeModelColumn other_data; + }; + + ModelColumns model_columns; + Glib::RefPtr list_model; // The list of editors. + Gtk::ScrolledWindow list_scroll_area; // Allows the list to be scrolled. + Gtk::TreeView *list_view; // Widget for displaying the list. + Gtk::Box toolbar; // Contains buttons for editing the list. + Gtk::Button *button_app_chooser; + Gtk::Button *button_add; + Gtk::Button *button_remove; + std::unique_ptr app_chooser_dialog; + + /** + * Inserts a new editor entry after the current selection, or at the end if + * no editor is selected. + */ + void addEditor(); + /** + * Constructs the column for displaying the external editor name (and icon). + */ + Gtk::TreeViewColumn *makeAppColumn(); + /** + * Constructs the column for displaying an editable commandline. + */ + Gtk::TreeViewColumn *makeCommandColumn(); + /** + * 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. + */ + void onAppChooserDialogResponse(int responseId, Gtk::AppChooserDialog *dialog); + /** + * Shows the app chooser dialog. + */ + void openAppChooserDialog(); + /** + * Removes all selected editors. + */ + void removeSelectedEditors(); + /** + * Sets the selected entries with the provided information. + */ + void setApp(const Glib::RefPtr app_info); + /** + * Updates the application command and removes the icon for the given row. + */ + void setAppCommand(const Glib::ustring & path, const Glib::ustring & new_text); + /** + * Updates the application name for the given row. + */ + void setAppName(const Glib::ustring & path, const Glib::ustring & new_text); + /** + * Sets the sensitivity of the widgets in the toolbar to reflect the current + * state of the list. For example, makes the remove button insensitive if no + * entries are selected. + */ + void updateToolbarSensitivity(); +}; From d2a280fbf361b804bbe70c4da46d21cf7827064a Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Tue, 30 Mar 2021 21:45:20 -0700 Subject: [PATCH 02/25] Update language file and cmake list Update for the external editor preferences widget. --- rtdata/languages/default | 3 +++ rtgui/CMakeLists.txt | 1 + 2 files changed, 4 insertions(+) diff --git a/rtdata/languages/default b/rtdata/languages/default index 15aceb51b..2ff654ede 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1755,6 +1755,9 @@ PREFERENCES_DIRSOFTWARE;Installation directory PREFERENCES_EDITORCMDLINE;Custom command line PREFERENCES_EDITORLAYOUT;Editor layout PREFERENCES_EXTERNALEDITOR;External Editor +PREFERENCES_EXTERNALEDITOR_CHANGE;Change Application +PREFERENCES_EXTERNALEDITOR_COLUMN_NAME;Name +PREFERENCES_EXTERNALEDITOR_COLUMN_COMMAND;Command PREFERENCES_FBROWSEROPTS;File Browser / Thumbnail Options PREFERENCES_FILEBROWSERTOOLBARSINGLEROW;Compact toolbars in File Browser PREFERENCES_FLATFIELDFOUND;Found diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt index 5f8baed47..1d4b57553 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -62,6 +62,7 @@ set(NONCLISOURCEFILES exiffiltersettings.cc exifpanel.cc exportpanel.cc + externaleditorpreferences.cc extprog.cc fattaltonemap.cc filebrowser.cc From afe20fee0e0046083cd61abe28e8f44591baf212 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sun, 4 Apr 2021 22:16:16 -0700 Subject: [PATCH 03/25] Add Gio::Icon based RTImages --- rtgui/rtimage.cc | 102 ++++++++++++++++++++++++++++++++++++++++++++++- rtgui/rtimage.h | 8 ++++ 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/rtgui/rtimage.cc b/rtgui/rtimage.cc index 44078ed3b..9a38c1885 100644 --- a/rtgui/rtimage.cc +++ b/rtgui/rtimage.cc @@ -22,12 +22,40 @@ #include #include +#include +#include #include "../rtengine/settings.h" namespace { +struct GIconKey { + Glib::RefPtr icon; + /** + * Icon size in pixels. + */ + int icon_size; + + GIconKey() {} + GIconKey(const Glib::RefPtr &icon, int icon_size): icon(icon), icon_size(icon_size) {} + + bool operator==(const GIconKey &other) const + { + bool icons_match = (icon.get() == nullptr && other.icon.get() == nullptr) || (icon.get() != nullptr && icon->equal(Glib::RefPtr::cast_const(other.icon))); + return icons_match && icon_size == other.icon_size; + } +}; + +struct GIconKeyHash { + size_t operator()(const GIconKey &key) const + { + const size_t icon_hash = key.icon ? key.icon->hash() : 0; + return icon_hash ^ std::hash()(key.icon_size); + } +}; + +std::unordered_map, GIconKeyHash> gIconPixbufCache; std::map > pixbufCache; std::map > surfaceCache; @@ -44,6 +72,8 @@ RTImage::RTImage (RTImage &other) : surface(other.surface), pixbuf(other.pixbuf) set(pixbuf); } else if (surface) { set(surface); + } else if (other.gIcon) { + changeImage(other.gIcon, other.gIconSize); } } @@ -80,13 +110,27 @@ RTImage::RTImage (Glib::RefPtr &other) if (other->get_surface()) { surface = other->get_surface(); set(surface); - } else { + } else if (other->pixbuf) { pixbuf = other->get_pixbuf(); set(pixbuf); + } else if (other->gIcon) { + changeImage(other->gIcon, other->gIconSize); } } } +RTImage::RTImage(const Glib::RefPtr &gIcon, Gtk::IconSize size) +{ + changeImage(gIcon, size); +} + +int RTImage::iconSizeToPixels(Gtk::IconSize size) const +{ + int width, height; + Gtk::IconSize::lookup(size, width, height); + return std::round(getTweakedDPI() / baseDPI * std::max(width, height)); +} + void RTImage::setImage (const Glib::ustring& fileName, const Glib::ustring& rtlFileName) { Glib::ustring imageName; @@ -113,10 +157,41 @@ void RTImage::setDPInScale (const double newDPI, const int newScale) } } +void RTImage::changeImage(const Glib::RefPtr &gIcon, int size) +{ + clear(); + + pixbuf.reset(); + surface.clear(); + this->gIcon = gIcon; + + if (!gIcon) { + return; + } + + gIconSize = size; + GIconKey key(gIcon, gIconSize); + auto iterator = gIconPixbufCache.find(key); + + if (iterator == gIconPixbufCache.end()) { + auto icon_pixbuf = createPixbufFromGIcon(gIcon, gIconSize); + iterator = gIconPixbufCache.emplace(key, icon_pixbuf).first; + } + + set(iterator->second); +} + +void RTImage::changeImage(const Glib::RefPtr &gIcon, Gtk::IconSize size) +{ + changeImage(gIcon, iconSizeToPixels(size)); +} + void RTImage::changeImage (const Glib::ustring& imageName) { clear (); + gIcon.reset(); + if (imageName.empty()) { return; } @@ -150,6 +225,11 @@ int RTImage::get_width() if (pixbuf) { return pixbuf->get_width(); } + + if (gIcon) { + return this->get_pixbuf()->get_width(); + } + return -1; } @@ -161,6 +241,11 @@ int RTImage::get_height() if (pixbuf) { return pixbuf->get_height(); } + + if (gIcon) { + return this->get_pixbuf()->get_height(); + } + return -1; } @@ -178,6 +263,11 @@ void RTImage::cleanup(bool all) for (auto& entry : surfaceCache) { entry.second.clear(); } + + for (auto& entry : gIconPixbufCache) { + entry.second.reset(); + } + RTScalable::cleanup(all); } @@ -189,6 +279,10 @@ void RTImage::updateImages() for (auto& entry : surfaceCache) { entry.second = createImgSurfFromFile(entry.first); } + + for (auto& entry : gIconPixbufCache) { + entry.second = createPixbufFromGIcon(entry.first.icon, entry.first.icon_size); + } } Glib::RefPtr RTImage::createPixbufFromFile (const Glib::ustring& fileName) @@ -197,6 +291,12 @@ Glib::RefPtr RTImage::createPixbufFromFile (const Glib::ustring& fi return Gdk::Pixbuf::create(imgSurf, 0, 0, imgSurf->get_width(), imgSurf->get_height()); } +Glib::RefPtr RTImage::createPixbufFromGIcon(const Glib::RefPtr &icon, int size) +{ + // TODO: Listen for theme changes and update icon, remove from cache. + return Gtk::IconTheme::get_default()->lookup_icon(icon, size, Gtk::ICON_LOOKUP_FORCE_SIZE).load_icon(); +} + Cairo::RefPtr RTImage::createImgSurfFromFile (const Glib::ustring& fileName) { Cairo::RefPtr surf; diff --git a/rtgui/rtimage.h b/rtgui/rtimage.h index eb1930d28..183a83a94 100644 --- a/rtgui/rtimage.h +++ b/rtgui/rtimage.h @@ -34,6 +34,11 @@ class RTImage final : public Gtk::Image, public RTScalable protected: Cairo::RefPtr surface; Glib::RefPtr pixbuf; + Glib::RefPtr gIcon; + int gIconSize; + + void changeImage(const Glib::RefPtr &gIcon, int size); + int iconSizeToPixels(Gtk::IconSize size) const; public: RTImage (); @@ -42,9 +47,11 @@ public: explicit RTImage (Cairo::RefPtr &surf); explicit RTImage(Cairo::RefPtr other); explicit RTImage (Glib::RefPtr &other); + explicit RTImage(const Glib::RefPtr &gIcon, Gtk::IconSize size); explicit RTImage (const Glib::ustring& fileName, const Glib::ustring& rtlFileName = Glib::ustring()); void setImage (const Glib::ustring& fileName, const Glib::ustring& rtlFileName = Glib::ustring()); + void changeImage(const Glib::RefPtr &gIcon, Gtk::IconSize size); void changeImage (const Glib::ustring& imageName); Cairo::RefPtr get_surface(); int get_width(); @@ -58,6 +65,7 @@ public: static void setScale (const int newScale); static Glib::RefPtr createPixbufFromFile (const Glib::ustring& fileName); + static Glib::RefPtr createPixbufFromGIcon(const Glib::RefPtr &icon, int size); static Cairo::RefPtr createImgSurfFromFile (const Glib::ustring& fileName); }; From f30f094a6c014976e6930ee6f4e8aa6d007e9c06 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 10 Apr 2021 17:41:33 -0700 Subject: [PATCH 04/25] Improve pop-up common with gicons and item ops Add ability to use gicon images, insert pop-up entries in any location, and remove entries. --- rtgui/popupcommon.cc | 157 +++++++++++++++++++++++++++++++++---------- rtgui/popupcommon.h | 19 +++++- 2 files changed, 141 insertions(+), 35 deletions(-) diff --git a/rtgui/popupcommon.cc b/rtgui/popupcommon.cc index 8c4c9dda1..fabc4d572 100644 --- a/rtgui/popupcommon.cc +++ b/rtgui/popupcommon.cc @@ -27,7 +27,7 @@ PopUpCommon::PopUpCommon (Gtk::Button* thisButton, const Glib::ustring& label) : buttonImage (nullptr) - , menu (nullptr) + , menu(new Gtk::Menu()) , selected (-1) // -1 means that the button is invalid { button = thisButton; @@ -48,59 +48,148 @@ PopUpCommon::PopUpCommon (Gtk::Button* thisButton, const Glib::ustring& label) setExpandAlignProperties(buttonGroup, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); buttonGroup->attach(*button, 0, 0, 1, 1); buttonGroup->get_style_context()->add_class("image-combo"); + + // Create the image for the button + buttonImage = Gtk::make_managed(); + setExpandAlignProperties(buttonImage, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); + imageContainer->attach_next_to(*buttonImage, Gtk::POS_RIGHT, 1, 1); + buttonImage->set_no_show_all(); + + // Create the button for showing the pop-up. + arrowButton = Gtk::make_managed(); + Gtk::Image *arrowImage = Gtk::make_managed(); + arrowImage->set_from_icon_name("pan-down-symbolic", Gtk::ICON_SIZE_BUTTON); + setExpandAlignProperties(arrowButton, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_FILL); + arrowButton->add(*arrowImage); //menuSymbol); + arrowImage->show(); + buttonGroup->attach_next_to(*arrowButton, *button, Gtk::POS_RIGHT, 1, 1); + arrowButton->signal_button_release_event().connect_notify(sigc::mem_fun(*this, &PopUpCommon::showMenu)); + arrowButton->get_style_context()->add_class("Right"); + arrowButton->get_style_context()->add_class("popupbutton-arrow"); + arrowButton->set_no_show_all(); } PopUpCommon::~PopUpCommon () { - delete menu; - delete buttonImage; } bool PopUpCommon::addEntry (const Glib::ustring& fileName, const Glib::ustring& label) { - if (label.empty ()) - return false; + return insertEntry(getEntryCount(), fileName, label); +} + +bool PopUpCommon::insertEntry(int position, const Glib::ustring& fileName, const Glib::ustring& label) +{ + RTImage* image = nullptr; + if (!fileName.empty()) { + image = Gtk::make_managed(fileName); + } + bool success = insertEntryImpl(position, fileName, Glib::RefPtr(), image, label); + if (!success && image) { + delete image; + } + return success; +} + +bool PopUpCommon::insertEntry(int position, const Glib::RefPtr& gIcon, const Glib::ustring& label) +{ + RTImage* image = Gtk::make_managed(gIcon, Gtk::ICON_SIZE_BUTTON); + bool success = insertEntryImpl(position, "", gIcon, image, label); + if (!success) { + delete image; + } + return success; +} + +bool PopUpCommon::insertEntryImpl(int position, const Glib::ustring& fileName, const Glib::RefPtr& gIcon, RTImage* image, const Glib::ustring& label) +{ + if (label.empty() || position < 0 || position > getEntryCount()) + return false; // Create the menu item and image - MyImageMenuItem* newItem = Gtk::manage (new MyImageMenuItem (label, fileName)); - imageFilenames.push_back (fileName); - images.push_back (newItem->getImage ()); - - if (selected == -1) { - // Create the menu on the first item - menu = new Gtk::Menu (); - // Create the image for the button - buttonImage = new RTImage(fileName); - setExpandAlignProperties(buttonImage, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); - // Use the first image by default - imageContainer->attach_next_to(*buttonImage, Gtk::POS_RIGHT, 1, 1); - selected = 0; - } + MyImageMenuItem *newItem = Gtk::make_managed(label, image); + imageIcons.insert(imageIcons.begin() + position, gIcon); + imageFilenames.insert(imageFilenames.begin() + position, fileName); + images.insert(images.begin() + position, newItem->getImage()); // When there is at least 1 choice, we add the arrow button if (images.size() == 1) { - Gtk::Button* arrowButton = Gtk::manage( new Gtk::Button() ); - Gtk::Image *arrowImage = Gtk::manage(new Gtk::Image()); - arrowImage->set_from_icon_name("pan-down-symbolic", Gtk::ICON_SIZE_BUTTON); - setExpandAlignProperties(arrowButton, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_FILL); - arrowButton->add(*arrowImage); //menuSymbol); - buttonGroup->attach_next_to(*arrowButton, *button, Gtk::POS_RIGHT, 1, 1); - arrowButton->signal_button_release_event().connect_notify( sigc::mem_fun(*this, &PopUpCommon::showMenu) ); + changeImage(fileName, gIcon); + buttonImage->show(); + selected = 0; button->get_style_context()->add_class("Left"); - arrowButton->get_style_context()->add_class("Right"); - arrowButton->get_style_context()->add_class("popupbutton-arrow"); + arrowButton->show(); hasMenu = true; + } else if (position <= selected) { + selected++; } - newItem->signal_activate ().connect (sigc::bind (sigc::mem_fun (*this, &PopUpCommon::entrySelected), images.size () - 1)); - menu->append (*newItem); - + void (PopUpCommon::*entrySelectedFunc)(Gtk::Widget *) = &PopUpCommon::entrySelected; + newItem->signal_activate ().connect (sigc::bind (sigc::mem_fun (*this, entrySelectedFunc), newItem)); + menu->insert(*newItem, position); return true; } -// TODO: 'PopUpCommon::removeEntry' method to be created... +void PopUpCommon::removeEntry(int position) +{ + if (position < 0 || position >= getEntryCount()) { + return; + } -void PopUpCommon::entrySelected (int i) + if (getEntryCount() == 1) { // Last of the entries. + // Hide the arrow button. + button->get_style_context()->remove_class("Left"); + arrowButton->hide(); + hasMenu = false; + // Remove the button image. + buttonImage->hide(); + selected = -1; + } + else if (position < selected) { + selected--; + } + else if (position == selected) { // Select a different entry before removing. + int nextSelection = position + (position == getEntryCount() - 1 ? -1 : 1); + changeImage(nextSelection); + setButtonHint(); + } + + MyImageMenuItem *menuItem = dynamic_cast(menu->get_children()[position]); + menu->remove(*menuItem); + delete menuItem; + imageIcons.erase(imageIcons.begin() + position); + imageFilenames.erase(imageFilenames.begin() + position); + images.erase(images.begin() + position); +} + +void PopUpCommon::changeImage(int position) +{ + changeImage(imageFilenames.at(position), imageIcons.at(position)); +} + +void PopUpCommon::changeImage(const Glib::ustring& fileName, const Glib::RefPtr& gIcon) +{ + if (!fileName.empty()) { + buttonImage->changeImage(fileName); + } else { + buttonImage->changeImage(gIcon, static_cast(Gtk::ICON_SIZE_BUTTON)); + } +} + +void PopUpCommon::entrySelected(Gtk::Widget* widget) +{ + int i = 0; + for (const auto & child : menu->get_children()) { + if (widget == child) { + break; + } + i++; + } + + entrySelected(i); +} + +void PopUpCommon::entrySelected(int i) { // Emit a signal if the selected item has changed if (setSelected (posToIndex(i))) @@ -130,7 +219,7 @@ bool PopUpCommon::setSelected (int entryNum) return false; } else { // Maybe we could do something better than loading the image file each time the selection is changed !? - buttonImage->changeImage(imageFilenames.at(entryNum)); + changeImage(entryNum); selected = entryNum; setButtonHint(); return true; diff --git a/rtgui/popupcommon.h b/rtgui/popupcommon.h index b4cf4d7e0..59a5b8d0e 100644 --- a/rtgui/popupcommon.h +++ b/rtgui/popupcommon.h @@ -20,12 +20,19 @@ */ #pragma once +#include "glibmm/refptr.h" +#include #include #include #include +namespace Gio +{ +class Icon; +} + namespace Gtk { @@ -33,6 +40,7 @@ class Grid; class Menu; class Button; class ImageMenuItem; +class Widget; } @@ -53,9 +61,12 @@ public: explicit PopUpCommon (Gtk::Button* button, const Glib::ustring& label = ""); virtual ~PopUpCommon (); bool addEntry (const Glib::ustring& fileName, const Glib::ustring& label); + bool insertEntry(int position, const Glib::ustring& fileName, const Glib::ustring& label); + bool insertEntry(int position, const Glib::RefPtr& gIcon, const Glib::ustring& label); int getEntryCount () const; bool setSelected (int entryNum); int getSelected () const; + void removeEntry(int position); void setButtonHint(); void show (); void set_tooltip_text (const Glib::ustring &text); @@ -65,16 +76,22 @@ private: type_signal_changed messageChanged; type_signal_item_selected messageItemSelected; + std::vector> imageIcons; std::vector imageFilenames; std::vector images; Glib::ustring buttonHint; RTImage* buttonImage; Gtk::Grid* imageContainer; - Gtk::Menu* menu; + std::unique_ptr menu; Gtk::Button* button; + Gtk::Button* arrowButton; int selected; bool hasMenu; + void changeImage(int position); + void changeImage(const Glib::ustring& fileName, const Glib::RefPtr& gIcon); + void entrySelected(Gtk::Widget* menuItem); + bool insertEntryImpl(int position, const Glib::ustring& fileName, const Glib::RefPtr& gIcon, RTImage* image, const Glib::ustring& label); void showMenu(GdkEventButton* event); protected: From be7aecac40e5e91ae4047dd84bcc7219b0d4615a Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sun, 11 Apr 2021 18:24:39 -0700 Subject: [PATCH 05/25] Add multiple external editors to options --- rtgui/options.cc | 170 +++++++++++++++++++++++++++++++++++++++++++++++ rtgui/options.h | 13 ++++ 2 files changed, 183 insertions(+) diff --git a/rtgui/options.cc b/rtgui/options.cc index ce03db434..2a00af525 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -413,6 +413,8 @@ void Options::setDefaults() gimpDir = ""; psDir = ""; customEditorProg = ""; + externalEditors.clear(); + externalEditorIndex = -1; CPBKeys = CPBKT_TID; editorToSendTo = 1; favoriteDirs.clear(); @@ -808,6 +810,7 @@ void Options::readFromFile(Glib::ustring fname) } } + // TODO: Remove. if (keyFile.has_group("External Editor")) { if (keyFile.has_key("External Editor", "EditorKind")) { editorToSendTo = keyFile.get_integer("External Editor", "EditorKind"); @@ -826,6 +829,138 @@ 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", "IconNames")) { + // Multiple external editors. + + const auto & names = + !keyFile.has_key("External Editor", "Names") ? + std::vector() : + static_cast>( + keyFile.get_string_list("External Editor", "Names")); + const auto & commands = + !keyFile.has_key("External Editor", "Commands") ? + std::vector() : + static_cast>( + keyFile.get_string_list("External Editor", "Commands")); + const auto & icon_names = + !keyFile.has_key("External Editor", "IconNames") ? + std::vector() : + static_cast>( + keyFile.get_string_list("External Editor", "IconNames")); + externalEditors = std::vector(std::max(std::max( + names.size(), commands.size()), icon_names.size())); + for (unsigned i = 0; i < names.size(); i++) { + externalEditors[i].name = names[i]; + } + for (unsigned i = 0; i < commands.size(); i++) { + externalEditors[i].command = commands[i]; + } + for (unsigned i = 0; i < icon_names.size(); i++) { + externalEditors[i].icon_name = icon_names[i]; + } + + if (keyFile.has_key("External Editor", "EditorIndex")) { + int index = keyFile.get_integer("External Editor", "EditorIndex"); + externalEditorIndex = std::min( + std::max(-1, index), + static_cast(externalEditors.size()) + ); + } + } else if (keyFile.has_key("External Editor", "EditorKind")) { + // Legacy fixed external editors. Convert to flexible. + + // GIMP == 1, Photoshop == 2, Custom == 3. + editorToSendTo = keyFile.get_integer("External Editor", "EditorKind"); + + #ifdef WIN32 + Glib::ustring gimpDir = ""; + if (keyFile.has_key("External Editor", "GimpDir")) { + gimpDir = keyFile.get_string("External Editor", "GimpDir"); + } + auto executable = Glib::build_filename(options.gimpDir, "bin", "gimp-win-remote"); + if (Glib::file_test(executable, Glib::FILE_TEST_IS_EXECUTABLE)) { + if (editorToSendTo == 1) { + externalEditorIndex = externalEditors.size(); + } + externalEditors.push_back(ExternalEditor("GIMP", executable, "gimp")); + } else { + for (auto ver = 12; ver >= 0; --ver) { + executable = Glib::build_filename(gimpDir, "bin", Glib::ustring::compose(Glib::ustring("gimp-2.%1.exe"), ver)); + if (Glib::file_test(executable, Glib::FILE_TEST_IS_EXECUTABLE)) { + if (editorToSendTo == 1) { + externalEditorIndex = externalEditors.size(); + } + externalEditors.push_back(ExternalEditor("GIMP", executable, "gimp")); + break; + } + } + } + + Glib::ustring psDir = ""; + if (keyFile.has_key("External Editor", "PhotoshopDir")) { + psDir = keyFile.get_string("External Editor", "PhotoshopDir"); + } + auto executable = Glib::build_filename(psDir, "Photoshop.exe"); + if (Glib::file_test(executable, Glib::FILE_TEST_IS_EXECUTABLE)) { + if (editorToSendTo == 2) { + externalEditorIndex = externalEditors.size(); + } + externalEditors.push_back(ExternalEditor("Photoshop", executable, "")); + } + + if (keyFile.has_key("External Editor", "CustomEditor")) { + executable = keyFile.get_string("External Editor", "CustomEditor"); + if (editorToSendTo == 3) { + externalEditorIndex = externalEditors.size(); + } + externalEditors.push_back(ExternalEditor("-", executable, ""); + } + #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")); + + if (editorToSendTo == 2) { + externalEditorIndex = externalEditors.size(); + } + externalEditors.push_back(ExternalEditor("Photoshop", "open -a Photoshop", "")); + + if (keyFile.has_key("External Editor", "CustomEditor")) { + auto executable = keyFile.get_string("External Editor", "CustomEditor"); + if (editorToSendTo == 3) { + externalEditorIndex = externalEditors.size(); + } + externalEditors.push_back(ExternalEditor("-", executable, "")); + } + #else + if (Glib::find_program_in_path("gimp").compare("")) { + if (editorToSendTo == 1) { + externalEditorIndex = externalEditors.size(); + } + externalEditors.push_back(ExternalEditor("GIMP", "gimp", "gimp")); + } else if (Glib::find_program_in_path("gimp-remote").compare("")) { + if (editorToSendTo == 1) { + externalEditorIndex = externalEditors.size(); + } + externalEditors.push_back(ExternalEditor("GIMP", "gimp-remote", "gimp")); + } + + if (keyFile.has_key("External Editor", "CustomEditor")) { + auto executable = keyFile.get_string("External Editor", "CustomEditor"); + if (editorToSendTo == 3) { + externalEditorIndex = externalEditors.size(); + } + externalEditors.push_back(ExternalEditor("-", executable, "")); + } + #endif + } + } + if (keyFile.has_group("Output")) { if (keyFile.has_key("Output", "Format")) { saveFormat.format = keyFile.get_string("Output", "Format"); @@ -2111,11 +2246,30 @@ void Options::saveToFile(Glib::ustring fname) keyFile.set_boolean("General", "Detectshape", rtSettings.detectshape); keyFile.set_boolean("General", "Fftwsigma", rtSettings.fftwsigma); + // TODO: Remove. keyFile.set_integer("External Editor", "EditorKind", editorToSendTo); keyFile.set_string("External Editor", "GimpDir", gimpDir); keyFile.set_string("External Editor", "PhotoshopDir", psDir); keyFile.set_string("External Editor", "CustomEditor", customEditorProg); + { + std::vector names; + std::vector commands; + std::vector icon_names; + + for (const auto & editor : externalEditors) { + names.push_back(editor.name); + commands.push_back(editor.command); + icon_names.push_back(editor.icon_name); + } + + keyFile.set_string_list("External Editor", "Names", names); + keyFile.set_string_list("External Editor", "Commands", commands); + keyFile.set_string_list("External Editor", "IconNames", icon_names); + + keyFile.set_integer("External Editor", "EditorIndex", externalEditorIndex); + } + keyFile.set_boolean("File Browser", "BrowseOnlyRaw", fbOnlyRaw); keyFile.set_boolean("File Browser", "BrowserShowsDate", fbShowDateTime); keyFile.set_boolean("File Browser", "BrowserShowsExif", fbShowBasicExif); @@ -2778,3 +2932,19 @@ Glib::ustring Options::getICCProfileCopyright() now.set_time_current(); return Glib::ustring::compose("Copyright RawTherapee %1, CC0", now.get_year()); } + +ExternalEditor::ExternalEditor() {} + +ExternalEditor::ExternalEditor( + const Glib::ustring &name, const Glib::ustring &command, const Glib::ustring &icon_name +): name(name), command(command), icon_name(icon_name) {} + +bool ExternalEditor::operator==(const ExternalEditor &other) const +{ + return this->name == other.name && this->command == other.command && this->icon_name == other.icon_name; +} + +bool ExternalEditor::operator!=(const ExternalEditor &other) const +{ + return !(*this == other); +} diff --git a/rtgui/options.h b/rtgui/options.h index 03b551efe..be2ba70ab 100644 --- a/rtgui/options.h +++ b/rtgui/options.h @@ -52,6 +52,17 @@ // Special name for the Dynamic profile #define DEFPROFILE_DYNAMIC "Dynamic" +struct ExternalEditor { + ExternalEditor(); + ExternalEditor(const Glib::ustring &name, const Glib::ustring &command, const Glib::ustring &icon_name); + Glib::ustring name; + Glib::ustring command; + Glib::ustring icon_name; + + bool operator==(const ExternalEditor & other) const; + bool operator!=(const ExternalEditor & other) const; +}; + struct SaveFormat { SaveFormat( const Glib::ustring& _format, @@ -277,6 +288,8 @@ public: Glib::ustring gimpDir; Glib::ustring psDir; Glib::ustring customEditorProg; + std::vector externalEditors; + int externalEditorIndex; Glib::ustring CPBPath; // Custom Profile Builder's path CPBKeyType CPBKeys; // Custom Profile Builder's key type int editorToSendTo; From 3efbb99ba9eedd4942a5ba34f8d8ce54fbdc07d6 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Mon, 12 Apr 2021 21:48:54 -0700 Subject: [PATCH 06/25] Make MyImageMenuItem constructable from an RTImage --- rtgui/guiutils.cc | 19 +++++++++++++++++-- rtgui/guiutils.h | 3 +++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/rtgui/guiutils.cc b/rtgui/guiutils.cc index f415d770f..52ad2d6d4 100644 --- a/rtgui/guiutils.cc +++ b/rtgui/guiutils.cc @@ -1466,13 +1466,28 @@ TextOrIcon::TextOrIcon (const Glib::ustring &fname, const Glib::ustring &labelTx } MyImageMenuItem::MyImageMenuItem(Glib::ustring label, Glib::ustring imageFileName) +{ + RTImage* itemImage = nullptr; + + if (!imageFileName.empty()) { + itemImage = Gtk::manage(new RTImage(imageFileName)); + } + + construct(label, itemImage); +} + +MyImageMenuItem::MyImageMenuItem(Glib::ustring label, RTImage* itemImage) { + construct(label, itemImage); +} + +void MyImageMenuItem::construct(Glib::ustring label, RTImage* itemImage) { box = Gtk::manage (new Gtk::Grid()); this->label = Gtk::manage( new Gtk::Label(label)); box->set_orientation(Gtk::ORIENTATION_HORIZONTAL); - if (!imageFileName.empty()) { - image = Gtk::manage( new RTImage(imageFileName) ); + if (itemImage) { + image = itemImage; box->attach_next_to(*image, Gtk::POS_LEFT, 1, 1); } else { image = nullptr; diff --git a/rtgui/guiutils.h b/rtgui/guiutils.h index d90d45370..2077d505d 100644 --- a/rtgui/guiutils.h +++ b/rtgui/guiutils.h @@ -489,8 +489,11 @@ private: RTImage *image; Gtk::Label *label; + void construct(Glib::ustring label, RTImage* image); + public: MyImageMenuItem (Glib::ustring label, Glib::ustring imageFileName); + MyImageMenuItem (Glib::ustring label, RTImage* image); const RTImage *getImage () const; const Gtk::Label* getLabel () const; }; From 349ceb933627955a8ea7238e2b0c1ecb904e6103 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Mon, 12 Apr 2021 21:53:04 -0700 Subject: [PATCH 07/25] Add function for opening images with Gio::AppInfo --- rtgui/extprog.cc | 5 +++++ rtgui/extprog.h | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/rtgui/extprog.cc b/rtgui/extprog.cc index 57d57ecd8..939ead608 100644 --- a/rtgui/extprog.cc +++ b/rtgui/extprog.cc @@ -339,3 +339,8 @@ bool ExtProgStore::openInCustomEditor (const Glib::ustring& fileName) #endif } + +bool ExtProgStore::openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr &editorInfo) +{ + return editorInfo->launch(Gio::File::create_for_path(fileName)); +} diff --git a/rtgui/extprog.h b/rtgui/extprog.h index c5e00bb1b..86dbc1674 100644 --- a/rtgui/extprog.h +++ b/rtgui/extprog.h @@ -24,6 +24,11 @@ #include "threadutils.h" +namespace Gio +{ + class AppInfo; +} + struct ExtProgAction { Glib::ustring filePathEXE; @@ -64,6 +69,7 @@ public: static bool openInGimp (const Glib::ustring& fileName); static bool openInPhotoshop (const Glib::ustring& fileName); static bool openInCustomEditor (const Glib::ustring& fileName); + static bool openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr &editorInfo); }; #define extProgStore ExtProgStore::getInstance() From 927e9500ff98ba642a9e31d4b1fcf6348bfdf752 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 17 Apr 2021 12:55:17 -0700 Subject: [PATCH 08/25] Change GUI to support multiple external editors Replace radio selector in external editor section of preferences with external editor preferences widget. Replace send-to-GIMP button with pop-up button for exporting to a selectable application. --- rtdata/languages/default | 3 +- rtgui/editorpanel.cc | 116 +++++++++++++++++++++++++++++++-------- rtgui/editorpanel.h | 12 +++- rtgui/popupcommon.cc | 2 +- rtgui/preferences.cc | 58 ++++++++++++++++++++ rtgui/preferences.h | 2 + rtgui/rtwindow.cc | 7 +++ rtgui/rtwindow.h | 2 + 8 files changed, 175 insertions(+), 27 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index 2ff654ede..605f0d95d 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -235,6 +235,7 @@ GENERAL_NO;No GENERAL_NONE;None GENERAL_OK;OK GENERAL_OPEN;Open +GENERAL_OTHER;Other GENERAL_PORTRAIT;Portrait GENERAL_RESET;Reset GENERAL_SAVE;Save @@ -1513,7 +1514,7 @@ MAIN_BUTTON_PREFERENCES;Preferences MAIN_BUTTON_PUTTOQUEUE_TOOLTIP;Put current image to processing queue.\nShortcut: Ctrl+b MAIN_BUTTON_SAVE_TOOLTIP;Save current image.\nShortcut: Ctrl+s\nSave current profile (.pp3).\nShortcut: Ctrl+Shift+s MAIN_BUTTON_SENDTOEDITOR;Edit image in external editor -MAIN_BUTTON_SENDTOEDITOR_TOOLTIP;Edit current image in external editor.\nShortcut: Ctrl+e +MAIN_BUTTON_SENDTOEDITOR_TOOLTIP;Edit current image in external editor.\nShortcut: Ctrl+e\nCurrent editor: MAIN_BUTTON_SHOWHIDESIDEPANELS_TOOLTIP;Show/hide all side panels.\nShortcut: m MAIN_BUTTON_UNFULLSCREEN;Exit fullscreen MAIN_FRAME_EDITOR;Editor diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index 7553a7353..e5543105c 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -666,12 +666,15 @@ EditorPanel::EditorPanel (FilePanel* filePanel) queueimg->set_tooltip_markup (M ("MAIN_BUTTON_PUTTOQUEUE_TOOLTIP")); setExpandAlignProperties (queueimg, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_FILL); - Gtk::Image *sendToEditorButtonImage = Gtk::manage (new RTImage ("palette-brush.png")); - sendtogimp = Gtk::manage (new Gtk::Button ()); - sendtogimp->set_relief(Gtk::RELIEF_NONE); - sendtogimp->add (*sendToEditorButtonImage); - sendtogimp->set_tooltip_markup (M ("MAIN_BUTTON_SENDTOEDITOR_TOOLTIP")); - setExpandAlignProperties (sendtogimp, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_FILL); + send_to_external = Gtk::make_managed("", false); + send_to_external->set_tooltip_text(M("MAIN_BUTTON_SENDTOEDITOR_TOOLTIP")); + setExpandAlignProperties(send_to_external->buttonGroup, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_FILL); + send_to_external->addEntry("palette-brush.png", M("GENERAL_OTHER")); + updateExternalEditorWidget( + options.externalEditorIndex >= 0 ? options.externalEditorIndex : options.externalEditors.size(), + options.externalEditors + ); + send_to_external->show(); // Status box progressLabel = Gtk::manage (new MyProgressBar (300)); @@ -736,7 +739,7 @@ EditorPanel::EditorPanel (FilePanel* filePanel) iops->attach_next_to (*vsep1, Gtk::POS_LEFT, 1, 1); if (!gimpPlugin) { - iops->attach_next_to (*sendtogimp, Gtk::POS_LEFT, 1, 1); + iops->attach_next_to(*send_to_external->buttonGroup, Gtk::POS_LEFT, 1, 1); } if (!gimpPlugin && !simpleEditor) { @@ -840,7 +843,8 @@ EditorPanel::EditorPanel (FilePanel* filePanel) tbRightPanel_1->signal_toggled().connect ( sigc::mem_fun (*this, &EditorPanel::tbRightPanel_1_toggled) ); saveimgas->signal_pressed().connect ( sigc::mem_fun (*this, &EditorPanel::saveAsPressed) ); queueimg->signal_pressed().connect ( sigc::mem_fun (*this, &EditorPanel::queueImgPressed) ); - sendtogimp->signal_pressed().connect ( sigc::mem_fun (*this, &EditorPanel::sendToGimpPressed) ); + send_to_external->signal_changed().connect(sigc::mem_fun(*this, &EditorPanel::sendToExternalChanged)); + send_to_external->signal_pressed().connect(sigc::mem_fun(*this, &EditorPanel::sendToExternalPressed)); toggleHistogramProfile->signal_toggled().connect( sigc::mem_fun (*this, &EditorPanel::histogramProfile_toggled) ); if (navPrev) { @@ -1673,7 +1677,7 @@ bool EditorPanel::handleShortcutKey (GdkEventKey* event) case GDK_KEY_e: if (!gimpPlugin) { - sendToGimpPressed(); + sendToExternalPressed(); } return true; @@ -1791,7 +1795,7 @@ bool EditorPanel::idle_saveImage (ProgressConnector *pc, msgd.run (); saveimgas->set_sensitive (true); - sendtogimp->set_sensitive (true); + send_to_external->set_sensitive(true); isProcessing = false; } @@ -1819,7 +1823,7 @@ bool EditorPanel::idle_imageSaved (ProgressConnector *pc, rtengine::IImagef } saveimgas->set_sensitive (true); - sendtogimp->set_sensitive (true); + send_to_external->set_sensitive(true); parent->setProgressStr (""); parent->setProgress (0.); @@ -1930,7 +1934,7 @@ void EditorPanel::saveAsPressed () ld->startFunc (sigc::bind (sigc::ptr_fun (&rtengine::processImage), job, err, parent->getProgressListener(), false ), sigc::bind (sigc::mem_fun ( *this, &EditorPanel::idle_saveImage ), ld, fnameOut, sf, pparams)); saveimgas->set_sensitive (false); - sendtogimp->set_sensitive (false); + send_to_external->set_sensitive(false); } } else { BatchQueueEntry* bqe = createBatchQueueEntry (); @@ -1961,7 +1965,7 @@ void EditorPanel::queueImgPressed () parent->addBatchQueueJob (createBatchQueueEntry ()); } -void EditorPanel::sendToGimpPressed () +void EditorPanel::sendToExternal() { if (!ipc || !openThm) { return; @@ -1975,7 +1979,29 @@ void EditorPanel::sendToGimpPressed () ld->startFunc (sigc::bind (sigc::ptr_fun (&rtengine::processImage), job, err, parent->getProgressListener(), false ), sigc::bind (sigc::mem_fun ( *this, &EditorPanel::idle_sendToGimp ), ld, openThm->getFileName() )); saveimgas->set_sensitive (false); - sendtogimp->set_sensitive (false); + send_to_external->set_sensitive(false); +} + +void EditorPanel::sendToExternalChanged(int) +{ + int index = send_to_external->getSelected(); + if (index >= 0 && static_cast(index) == options.externalEditors.size()) { + index = -1; + } + options.externalEditorIndex = index; +} + +void EditorPanel::sendToExternalPressed() +{ + if (options.externalEditorIndex == -1) { + // "Other" external editor. Show app chooser dialog to let user pick. + Gtk::AppChooserDialog *dialog = getAppChooserDialog(); + dialog->show(); + } 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); + sendToExternal(); + } } @@ -2078,7 +2104,7 @@ bool EditorPanel::idle_sendToGimp ( ProgressConnector *p Gtk::MessageDialog msgd (*parent, msg_, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); msgd.run (); saveimgas->set_sensitive (true); - sendtogimp->set_sensitive (true); + send_to_external->set_sensitive(true); } return false; @@ -2093,18 +2119,12 @@ bool EditorPanel::idle_sentToGimp (ProgressConnector *pc, rtengine::IImagef if (!errore) { saveimgas->set_sensitive (true); - sendtogimp->set_sensitive (true); + send_to_external->set_sensitive(true); parent->setProgressStr (""); parent->setProgress (0.); bool success = false; - if (options.editorToSendTo == 1) { - success = ExtProgStore::openInGimp (filename); - } else if (options.editorToSendTo == 2) { - success = ExtProgStore::openInPhotoshop (filename); - } else if (options.editorToSendTo == 3) { - success = ExtProgStore::openInCustomEditor (filename); - } + success = ExtProgStore::openInExternalEditor(filename, external_editor_info); if (!success) { Gtk::MessageDialog msgd (*parent, M ("MAIN_MSG_CANNOTSTARTEDITOR"), false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); @@ -2117,6 +2137,36 @@ bool EditorPanel::idle_sentToGimp (ProgressConnector *pc, rtengine::IImagef return false; } +Gtk::AppChooserDialog *EditorPanel::getAppChooserDialog() +{ + if (!app_chooser_dialog.get()) { + app_chooser_dialog.reset(new Gtk::AppChooserDialog("image/tiff")); + app_chooser_dialog->signal_response().connect( + sigc::mem_fun(*this, &EditorPanel::onAppChooserDialogResponse) + ); + app_chooser_dialog->set_modal(); + } + + return app_chooser_dialog.get(); +} + +void EditorPanel::onAppChooserDialogResponse(int responseId) +{ + switch (responseId) { + case Gtk::RESPONSE_OK: + getAppChooserDialog()->close(); + external_editor_info = getAppChooserDialog()->get_app_info(); + sendToExternal(); + break; + case Gtk::RESPONSE_CANCEL: + case Gtk::RESPONSE_CLOSE: + getAppChooserDialog()->close(); + break; + default: + break; + } +} + void EditorPanel::historyBeforeLineChanged (const rtengine::procparams::ProcParams& params) { @@ -2392,6 +2442,26 @@ void EditorPanel::tbShowHideSidePanels_managestate() ShowHideSidePanelsconn.block (false); } +void EditorPanel::updateExternalEditorWidget(int selectedIndex, const std::vector &editors) +{ + // Remove the editors and leave the "Other" entry. + while (send_to_external->getEntryCount() > 1) { + send_to_external->removeEntry(0); + } + // Add the editors. + for (unsigned i = 0; i < editors.size(); i++) { + const auto & name = editors[i].name.empty() ? Glib::ustring(" ") : editors[i].name; + if (!editors[i].icon_name.empty()) { + Glib::RefPtr gioIcon = Gio::Icon::create(editors[i].icon_name); + send_to_external->insertEntry(i, gioIcon, name); + } else { + send_to_external->insertEntry(i, "palette-brush.png", name); + } + } + send_to_external->setSelected(selectedIndex); + send_to_external->show(); +} + void EditorPanel::updateProfiles (const Glib::ustring &printerProfile, rtengine::RenderingIntent printerIntent, bool printerBPC) { } diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index 7675face5..0008786fa 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -43,6 +43,7 @@ class EditorPanel; class FilePanel; class MyProgressBar; class Navigator; +class PopUpButton; class Thumbnail; class ToolPanelCoordinator; @@ -162,7 +163,9 @@ public: void tbBeforeLock_toggled(); void saveAsPressed (); void queueImgPressed (); - void sendToGimpPressed (); + void sendToExternal(); + void sendToExternalChanged(int); + void sendToExternalPressed(); void openNextEditorImage (); void openPreviousEditorImage (); void syncFileBrowser (); @@ -182,6 +185,7 @@ public: { return isProcessing; } + void updateExternalEditorWidget(int selectedIndex, const std::vector &editors); void updateProfiles (const Glib::ustring &printerProfile, rtengine::RenderingIntent printerIntent, bool printerBPC); void updateTPVScrollbar (bool hide); void updateHistogramPosition (int oldPosition, int newPosition); @@ -201,6 +205,8 @@ private: bool idle_sendToGimp ( ProgressConnector *pc, Glib::ustring fname); bool idle_sentToGimp (ProgressConnector *pc, rtengine::IImagefloat* img, Glib::ustring filename); void histogramProfile_toggled (); + Gtk::AppChooserDialog *getAppChooserDialog(); + void onAppChooserDialogResponse(int resposneId); Glib::ustring lastSaveAsFileName; @@ -230,10 +236,12 @@ private: Gtk::Button* queueimg; Gtk::Button* saveimgas; - Gtk::Button* sendtogimp; + PopUpButton* send_to_external; Gtk::Button* navSync; Gtk::Button* navNext; Gtk::Button* navPrev; + Glib::RefPtr external_editor_info; + std::unique_ptr app_chooser_dialog; class ColorManagementToolbar; std::unique_ptr colorMgmtToolBar; diff --git a/rtgui/popupcommon.cc b/rtgui/popupcommon.cc index fabc4d572..c33ac068e 100644 --- a/rtgui/popupcommon.cc +++ b/rtgui/popupcommon.cc @@ -251,7 +251,7 @@ void PopUpCommon::setButtonHint() auto item = dynamic_cast(widget); if (item) { - hint += item->getLabel ()->get_text (); + hint += escapeHtmlChars(item->getLabel()->get_text()); } } diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 9d9603297..d4f550dd3 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -17,6 +17,7 @@ * along with RawTherapee. If not, see . */ #include +#include "externaleditorpreferences.h" #include "preferences.h" #include "multilangmgr.h" #include "splash.h" @@ -33,6 +34,8 @@ #include #endif +//#define EXT_EDITORS_RADIOS // TODO: Remove the corresponding code after testing. + namespace { void placeSpinBox(Gtk::Container* where, Gtk::SpinButton* &spin, const std::string &labelText, int digits, int inc0, int inc1, int maxLength, int range0, int range1, const std::string &toolTip = "") { Gtk::Box* HB = Gtk::manage ( new Gtk::Box () ); @@ -1188,6 +1191,7 @@ Gtk::Widget* Preferences::getGeneralPanel() Gtk::Frame* fdg = Gtk::manage(new Gtk::Frame(M("PREFERENCES_EXTERNALEDITOR"))); setExpandAlignProperties(fdg, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); +#ifdef EXT_EDITORS_RADIOS Gtk::Grid* externaleditorGrid = Gtk::manage(new Gtk::Grid()); externaleditorGrid->set_column_spacing(4); externaleditorGrid->set_row_spacing(4); @@ -1243,8 +1247,17 @@ Gtk::Widget* Preferences::getGeneralPanel() externaleditorGrid->attach_next_to(*edOther, *edGimp, Gtk::POS_BOTTOM, 1, 1); externaleditorGrid->attach_next_to(*editorToSendTo, *edOther, Gtk::POS_RIGHT, 1, 1); #endif +#endif + + externalEditors = Gtk::make_managed(); + externalEditors->set_size_request(-1, 200); +#ifdef EXT_EDITORS_RADIOS + externaleditorGrid->attach_next_to(*externalEditors, *edOther, Gtk::POS_BOTTOM, 2, 1); fdg->add(*externaleditorGrid); +#else + fdg->add(*externalEditors); +#endif vbGeneral->attach_next_to (*fdg, *fclip, Gtk::POS_BOTTOM, 2, 1); langAutoDetectConn = ckbLangAutoDetect->signal_toggled().connect(sigc::mem_fun(*this, &Preferences::langAutoDetectToggled)); tconn = themeCBT->signal_changed().connect ( sigc::mem_fun (*this, &Preferences::themeChanged) ); @@ -1700,6 +1713,7 @@ void Preferences::storePreferences() moptions.pseudoHiDPISupport = pseudoHiDPI->get_active(); +#ifdef EXT_EDITORS_RADIOS #ifdef WIN32 moptions.gimpDir = gimpDir->get_filename(); moptions.psDir = psDir->get_filename(); @@ -1726,6 +1740,20 @@ void Preferences::storePreferences() else if (edOther->get_active()) { moptions.editorToSendTo = 3; } +#endif + + const std::vector &editors = externalEditors->getEditors(); + moptions.externalEditors.resize(editors.size()); + moptions.externalEditorIndex = -1; + for (unsigned i = 0; i < editors.size(); i++) { + moptions.externalEditors[i] = (ExternalEditor( + editors[i].name, editors[i].command, editors[i].icon_name)); + 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. + moptions.externalEditorIndex = i; + } + } moptions.CPBPath = txtCustProfBuilderPath->get_text(); moptions.CPBKeys = CPBKeyType(custProfBuilderLabelType->get_active_row_number()); @@ -1981,6 +2009,7 @@ void Preferences::fillPreferences() hlThresh->set_value(moptions.highlightThreshold); shThresh->set_value(moptions.shadowThreshold); +#ifdef EXT_EDITORS_RADIOS edGimp->set_active(moptions.editorToSendTo == 1); edOther->set_active(moptions.editorToSendTo == 3); #ifdef WIN32 @@ -2009,6 +2038,18 @@ void Preferences::fillPreferences() #endif editorToSendTo->set_text(moptions.customEditorProg); +#endif + + std::vector editorInfos; + for (const auto &editor : moptions.externalEditors) { + editorInfos.push_back(ExternalEditorPreferences::EditorInfo( + editor.name, editor.command, editor.icon_name)); + } + if (moptions.externalEditorIndex >= 0) { + // Mark the current editor so we can track it. + editorInfos[moptions.externalEditorIndex].other_data = (void *)1; + } + externalEditors->setEditors(editorInfos); txtCustProfBuilderPath->set_text(moptions.CPBPath); custProfBuilderLabelType->set_active(moptions.CPBKeys); @@ -2474,6 +2515,23 @@ void Preferences::workflowUpdate() parent->updateProfiles (moptions.rtSettings.printerProfile, rtengine::RenderingIntent(moptions.rtSettings.printerIntent), moptions.rtSettings.printerBPC); } + bool changed = moptions.externalEditorIndex != options.externalEditorIndex + || moptions.externalEditors.size() != options.externalEditors.size(); + if (!changed) { + auto &editors = options.externalEditors; + auto &meditors = moptions.externalEditors; + for (unsigned i = 0; i < editors.size(); i++) { + if (editors[i] != meditors[i]) { + changed = true; + break; + } + } + } + if (changed) { + // Update the send to external editor widget. + parent->updateExternalEditorWidget(moptions.externalEditorIndex, moptions.externalEditors); + } + } void Preferences::addExtPressed() diff --git a/rtgui/preferences.h b/rtgui/preferences.h index df4e3327a..9e0730c04 100644 --- a/rtgui/preferences.h +++ b/rtgui/preferences.h @@ -26,6 +26,7 @@ #include "options.h" #include "../rtengine/profilestore.h" +class ExternalEditorPreferences; class RTWindow; class Splash; @@ -101,6 +102,7 @@ class Preferences final : Gtk::RadioButton* edGimp; Gtk::RadioButton* edPS; Gtk::RadioButton* edOther; + ExternalEditorPreferences *externalEditors; MyFileChooserButton* darkFrameDir; MyFileChooserButton* flatFieldDir; MyFileChooserButton* clutsDir; diff --git a/rtgui/rtwindow.cc b/rtgui/rtwindow.cc index c0042f949..0822c0aad 100644 --- a/rtgui/rtwindow.cc +++ b/rtgui/rtwindow.cc @@ -1030,6 +1030,13 @@ void RTWindow::MoveFileBrowserToEditor() } } +void RTWindow::updateExternalEditorWidget(int selectedIndex, const std::vector & editors) +{ + if (epanel) { + epanel->updateExternalEditorWidget(selectedIndex, editors); + } +} + void RTWindow::updateProfiles (const Glib::ustring &printerProfile, rtengine::RenderingIntent printerIntent, bool printerBPC) { if (epanel) { diff --git a/rtgui/rtwindow.h b/rtgui/rtwindow.h index e5e180747..32d0756b5 100644 --- a/rtgui/rtwindow.h +++ b/rtgui/rtwindow.h @@ -33,6 +33,7 @@ class BatchQueueEntry; class BatchQueuePanel; class EditorPanel; +class ExternalEditor; class FilePanel; class PLDBridge; class RTWindow final : @@ -114,6 +115,7 @@ public: void MoveFileBrowserToEditor(); void MoveFileBrowserToMain(); + void updateExternalEditorWidget(int selectedIndex, const std::vector &editors); void updateProfiles (const Glib::ustring &printerProfile, rtengine::RenderingIntent printerIntent, bool printerBPC); void updateTPVScrollbar (bool hide); void updateHistogramPosition (int oldPosition, int newPosition); From 044451868ac623bda15a5b8d1a6d02f37b5b31b4 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 17 Apr 2021 22:11:17 -0700 Subject: [PATCH 09/25] Add missing parenthesis --- rtgui/options.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtgui/options.cc b/rtgui/options.cc index 2a00af525..a967ed873 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -916,7 +916,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 3) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("-", executable, ""); + externalEditors.push_back(ExternalEditor("-", executable, "")); } #elif defined __APPLE__ if (editorToSendTo == 1) { From e6b2c9e7b06bf67b511db5ae53a172d08c8ab6fb Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sun, 18 Apr 2021 12:33:32 -0700 Subject: [PATCH 10/25] Make options ignore empty custom editor command --- rtgui/options.cc | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/rtgui/options.cc b/rtgui/options.cc index a967ed873..cf577fe7d 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -875,7 +875,7 @@ void Options::readFromFile(Glib::ustring fname) // GIMP == 1, Photoshop == 2, Custom == 3. editorToSendTo = keyFile.get_integer("External Editor", "EditorKind"); - #ifdef WIN32 +#ifdef WIN32 Glib::ustring gimpDir = ""; if (keyFile.has_key("External Editor", "GimpDir")) { gimpDir = keyFile.get_string("External Editor", "GimpDir"); @@ -903,7 +903,7 @@ void Options::readFromFile(Glib::ustring fname) if (keyFile.has_key("External Editor", "PhotoshopDir")) { psDir = keyFile.get_string("External Editor", "PhotoshopDir"); } - auto executable = Glib::build_filename(psDir, "Photoshop.exe"); + executable = Glib::build_filename(psDir, "Photoshop.exe"); if (Glib::file_test(executable, Glib::FILE_TEST_IS_EXECUTABLE)) { if (editorToSendTo == 2) { externalEditorIndex = externalEditors.size(); @@ -913,12 +913,14 @@ void Options::readFromFile(Glib::ustring fname) if (keyFile.has_key("External Editor", "CustomEditor")) { executable = keyFile.get_string("External Editor", "CustomEditor"); - if (editorToSendTo == 3) { - externalEditorIndex = externalEditors.size(); + if (!executable.empty()) { + if (editorToSendTo == 3) { + externalEditorIndex = externalEditors.size(); + } + externalEditors.push_back(ExternalEditor("-", executable, "")); } - externalEditors.push_back(ExternalEditor("-", executable, "")); } - #elif defined __APPLE__ +#elif defined __APPLE__ if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } @@ -932,12 +934,14 @@ void Options::readFromFile(Glib::ustring fname) if (keyFile.has_key("External Editor", "CustomEditor")) { auto executable = keyFile.get_string("External Editor", "CustomEditor"); - if (editorToSendTo == 3) { - externalEditorIndex = externalEditors.size(); + if (!executable.empty()) { + if (editorToSendTo == 3) { + externalEditorIndex = externalEditors.size(); + } + externalEditors.push_back(ExternalEditor("-", executable, "")); } - externalEditors.push_back(ExternalEditor("-", executable, "")); } - #else +#else if (Glib::find_program_in_path("gimp").compare("")) { if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); @@ -952,12 +956,14 @@ void Options::readFromFile(Glib::ustring fname) if (keyFile.has_key("External Editor", "CustomEditor")) { auto executable = keyFile.get_string("External Editor", "CustomEditor"); - if (editorToSendTo == 3) { - externalEditorIndex = externalEditors.size(); + if (!executable.empty()) { + if (editorToSendTo == 3) { + externalEditorIndex = externalEditors.size(); + } + externalEditors.push_back(ExternalEditor("-", executable, "")); } - externalEditors.push_back(ExternalEditor("-", executable, "")); } - #endif +#endif } } From d9fe87569dabbbc5ee561c93e230b75e88ea3ee7 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sun, 18 Apr 2021 17:23:40 -0700 Subject: [PATCH 11/25] Cache most recent send-to-editor temp file Caches the name of the most recently generated temporary file used for exporting to external editors and uses that file if the processing parameters are identical and the file exists. This can dramatically improve speed when exporting to multiple different editors. --- rtgui/editorpanel.cc | 22 ++++++++++++++++++---- rtgui/editorpanel.h | 3 +++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index e5543105c..021333503 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -1974,6 +1974,14 @@ void EditorPanel::sendToExternal() // develop image rtengine::procparams::ProcParams pparams; ipc->getParams (&pparams); + + if (!cached_exported_filename.empty() && pparams == cached_exported_pparams && Glib::file_test(cached_exported_filename, Glib::FILE_TEST_IS_REGULAR)) { + idle_sentToGimp(nullptr, nullptr, cached_exported_filename); + return; + } + + cached_exported_pparams = pparams; + cached_exported_filename.clear(); rtengine::ProcessingJob* job = rtengine::ProcessingJob::create (ipc->getInitialImage(), pparams); ProgressConnector *ld = new ProgressConnector(); ld->startFunc (sigc::bind (sigc::ptr_fun (&rtengine::processImage), job, err, parent->getProgressListener(), false ), @@ -2112,12 +2120,18 @@ bool EditorPanel::idle_sendToGimp ( ProgressConnector *p bool EditorPanel::idle_sentToGimp (ProgressConnector *pc, rtengine::IImagefloat* img, Glib::ustring filename) { - delete img; - int errore = pc->returnValue(); + if (img) { + delete img; + cached_exported_filename = filename; + } + int errore = 0; setProgressState(false); - delete pc; + if (pc) { + errore = pc->returnValue(); + delete pc; + } - if (!errore) { + if ((!img && Glib::file_test(filename, Glib::FILE_TEST_IS_REGULAR)) || (img && !errore)) { saveimgas->set_sensitive (true); send_to_external->set_sensitive(true); parent->setProgressStr (""); diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index 0008786fa..1e6eaaa1f 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -243,6 +243,9 @@ private: Glib::RefPtr external_editor_info; std::unique_ptr app_chooser_dialog; + rtengine::procparams::ProcParams cached_exported_pparams; + Glib::ustring cached_exported_filename; + class ColorManagementToolbar; std::unique_ptr colorMgmtToolBar; From 4044f67c0ff73dd793bd62908a198754978ef6a6 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Mon, 5 Jul 2021 11:01:51 -0700 Subject: [PATCH 12/25] Align External Editor > Output directory to top Prevents the frame from expanding vertically. --- rtgui/preferences.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index d19481ea1..786262896 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -1271,7 +1271,7 @@ Gtk::Widget* Preferences::getGeneralPanel() editor_bypass_output_profile = Gtk::manage(new Gtk::CheckButton(M("PREFERENCES_EXTEDITOR_BYPASS_OUTPUT_PROFILE"))); { Gtk::Frame *f = Gtk::manage(new Gtk::Frame(M("PREFERENCES_EXTEDITOR_DIR"))); - setExpandAlignProperties(f, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); + setExpandAlignProperties(f, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_START); Gtk::Box *vb = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); vb->pack_start(*editor_dir_temp); vb->pack_start(*editor_dir_current); From c62597545374b3427ae137a103584aa3f81afc7a Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sun, 11 Jul 2021 21:38:52 -0700 Subject: [PATCH 13/25] Surround Windows commands in quotes When external editors are launched in Windows OS, the command is surrounded by quotes. This commit fixes the translation of commands for use by the multiple custom external editors feature so that the translated commands are also surrounded by quotes. --- rtgui/options.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rtgui/options.cc b/rtgui/options.cc index 88406452b..3bb3bb08f 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -912,7 +912,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", executable, "gimp")); + externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", "gimp")); } else { for (auto ver = 12; ver >= 0; --ver) { executable = Glib::build_filename(gimpDir, "bin", Glib::ustring::compose(Glib::ustring("gimp-2.%1.exe"), ver)); @@ -920,7 +920,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", executable, "gimp")); + externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", "gimp")); break; } } @@ -935,7 +935,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 2) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("Photoshop", executable, "")); + externalEditors.push_back(ExternalEditor("Photoshop", "\"" + executable + "\"", "")); } if (keyFile.has_key("External Editor", "CustomEditor")) { @@ -944,7 +944,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 3) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("-", executable, "")); + externalEditors.push_back(ExternalEditor("-", "\"" + executable + "\"", "")); } } #elif defined __APPLE__ From 02e3e0129453a37fa666ff85c793469a6b4fd6f8 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sun, 11 Jul 2021 21:45:39 -0700 Subject: [PATCH 14/25] Use correct GIMP and Photoshop icons for Windows Icon names in Windows are typically the executable path followed by the string ",0". --- rtgui/options.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rtgui/options.cc b/rtgui/options.cc index 3bb3bb08f..6a944faa6 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -912,7 +912,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", "gimp")); + externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", executable + ",0")); } else { for (auto ver = 12; ver >= 0; --ver) { executable = Glib::build_filename(gimpDir, "bin", Glib::ustring::compose(Glib::ustring("gimp-2.%1.exe"), ver)); @@ -920,7 +920,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", "gimp")); + externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", executable + ",0")); break; } } @@ -935,7 +935,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 2) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("Photoshop", "\"" + executable + "\"", "")); + externalEditors.push_back(ExternalEditor("Photoshop", "\"" + executable + "\"", executable + ",0")); } if (keyFile.has_key("External Editor", "CustomEditor")) { From c4a8554db3da65d50497703ccd4951dbb16a86b9 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sun, 11 Jul 2021 22:13:27 -0700 Subject: [PATCH 15/25] Catch exceptions when launching external editor Catch all Glib::Errors and print the error code and message to stderr. --- rtgui/extprog.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rtgui/extprog.cc b/rtgui/extprog.cc index 148eee944..e4cf63733 100644 --- a/rtgui/extprog.cc +++ b/rtgui/extprog.cc @@ -342,5 +342,13 @@ bool ExtProgStore::openInCustomEditor (const Glib::ustring& fileName) bool ExtProgStore::openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr &editorInfo) { - return editorInfo->launch(Gio::File::create_for_path(fileName)); + try { + return editorInfo->launch(Gio::File::create_for_path(fileName)); + } catch (const Glib::Error &e) { + std::cerr + << "Error launching external editor.\n" + << "Error code #" << e.code() << ": " << e.what() + << std::endl; + return false; + } } From a0711ebc8c528e2b79706f4421c6181f1c71cce8 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sun, 25 Jul 2021 16:03:13 -0700 Subject: [PATCH 16/25] Fix tmp image reuse when exporting different image If two images have identical processing parameters, then sending one to an external editor followed by sending the other one will cause the first temporary image to be reused. This commit associates the image in the editor with the "cached" temporary image so that the temporary image only gets used if the editor image matches. --- rtgui/editorpanel.cc | 3 ++- rtgui/editorpanel.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index 5c61e672e..e52dab5db 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -1980,11 +1980,12 @@ void EditorPanel::sendToExternal() pparams.icm.outputProfile = rtengine::procparams::ColorManagementParams::NoProfileString; } - if (!cached_exported_filename.empty() && pparams == cached_exported_pparams && Glib::file_test(cached_exported_filename, Glib::FILE_TEST_IS_REGULAR)) { + if (!cached_exported_filename.empty() && cached_exported_image == ipc->getInitialImage() && pparams == cached_exported_pparams && Glib::file_test(cached_exported_filename, Glib::FILE_TEST_IS_REGULAR)) { idle_sentToGimp(nullptr, nullptr, cached_exported_filename); return; } + cached_exported_image = ipc->getInitialImage(); cached_exported_pparams = pparams; cached_exported_filename.clear(); rtengine::ProcessingJob* job = rtengine::ProcessingJob::create (ipc->getInitialImage(), pparams); diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index 1e6eaaa1f..1372a2171 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -243,6 +243,7 @@ private: Glib::RefPtr external_editor_info; std::unique_ptr app_chooser_dialog; + rtengine::InitialImage *cached_exported_image; rtengine::procparams::ProcParams cached_exported_pparams; Glib::ustring cached_exported_filename; From db7d56c253433e94d892f5bbeaa5226008a410f5 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sun, 25 Jul 2021 17:45:20 -0700 Subject: [PATCH 17/25] Synchronize send to external editor buttons Keep all buttons updated when using a multiple editor tabs mode. --- rtgui/editorpanel.cc | 32 ++++++++++++++++++++++++++++++++ rtgui/editorpanel.h | 10 ++++++++++ rtgui/editwindow.cc | 2 ++ rtgui/editwindow.h | 2 ++ 4 files changed, 46 insertions(+) diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index e52dab5db..6649fc970 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -2003,6 +2003,9 @@ void EditorPanel::sendToExternalChanged(int) index = -1; } options.externalEditorIndex = index; + if (externalEditorChangedSignal) { + externalEditorChangedSignal->emit(); + } } void EditorPanel::sendToExternalPressed() @@ -2069,6 +2072,23 @@ void EditorPanel::syncFileBrowser() // synchronize filebrowser with image in E } } +ExternalEditorChangedSignal * EditorPanel::getExternalEditorChangedSignal() +{ + return externalEditorChangedSignal; +} + +void EditorPanel::setExternalEditorChangedSignal(ExternalEditorChangedSignal *signal) +{ + if (externalEditorChangedSignal) { + externalEditorChangedSignalConnection.disconnect(); + } + externalEditorChangedSignal = signal; + if (signal) { + externalEditorChangedSignalConnection = signal->connect( + sigc::mem_fun(*this, &EditorPanel::updateExternalEditorSelection)); + } +} + void EditorPanel::histogramProfile_toggled() { options.rtSettings.HistogramWorking = toggleHistogramProfile->get_active(); @@ -2203,6 +2223,18 @@ void EditorPanel::onAppChooserDialogResponse(int responseId) } } +void EditorPanel::updateExternalEditorSelection() +{ + int index = send_to_external->getSelected(); + if (index >= 0 && static_cast(index) == options.externalEditors.size()) { + index = -1; + } + if (options.externalEditorIndex != index) { + send_to_external->setSelected( + options.externalEditorIndex >= 0 ? options.externalEditorIndex : options.externalEditors.size()); + } +} + void EditorPanel::historyBeforeLineChanged (const rtengine::procparams::ProcParams& params) { diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index 1372a2171..1bbb95a6e 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -38,6 +38,8 @@ template class array2D; } +using ExternalEditorChangedSignal = sigc::signal; + class BatchQueueEntry; class EditorPanel; class FilePanel; @@ -66,6 +68,7 @@ class EditorPanel final : public rtengine::NonCopyable { public: + explicit EditorPanel (FilePanel* filePanel = nullptr); ~EditorPanel () override; @@ -170,6 +173,10 @@ public: void openPreviousEditorImage (); void syncFileBrowser (); + // Signals. + ExternalEditorChangedSignal * getExternalEditorChangedSignal(); + void setExternalEditorChangedSignal(ExternalEditorChangedSignal *signal); + void tbTopPanel_1_visible (bool visible); bool CheckSidePanelsVisibility(); void tbShowHideSidePanels_managestate(); @@ -207,6 +214,7 @@ private: void histogramProfile_toggled (); Gtk::AppChooserDialog *getAppChooserDialog(); void onAppChooserDialogResponse(int resposneId); + void updateExternalEditorSelection(); Glib::ustring lastSaveAsFileName; @@ -242,6 +250,8 @@ private: Gtk::Button* navPrev; Glib::RefPtr external_editor_info; std::unique_ptr app_chooser_dialog; + ExternalEditorChangedSignal *externalEditorChangedSignal; + sigc::connection externalEditorChangedSignalConnection; rtengine::InitialImage *cached_exported_image; rtengine::procparams::ProcParams cached_exported_pparams; diff --git a/rtgui/editwindow.cc b/rtgui/editwindow.cc index d0e53d730..0584edf77 100644 --- a/rtgui/editwindow.cc +++ b/rtgui/editwindow.cc @@ -250,6 +250,7 @@ void EditWindow::addEditorPanel (EditorPanel* ep, const std::string &name) { ep->setParent (parent); ep->setParentWindow(this); + ep->setExternalEditorChangedSignal(&externalEditorChangedSignal); // construct closeable tab for the image Gtk::Box* hb = Gtk::manage (new Gtk::Box ()); @@ -288,6 +289,7 @@ void EditWindow::remEditorPanel (EditorPanel* ep) return; // Will crash if destroyed while loading } + ep->setExternalEditorChangedSignal(nullptr); epanels.erase (ep->getFileName()); filesEdited.erase (ep->getFileName ()); parent->fpanel->refreshEditedState (filesEdited); diff --git a/rtgui/editwindow.h b/rtgui/editwindow.h index b8eeaee82..27d4598c1 100644 --- a/rtgui/editwindow.h +++ b/rtgui/editwindow.h @@ -39,6 +39,8 @@ private: std::set filesEdited; std::map epanels; + sigc::signal externalEditorChangedSignal; + bool isFullscreen; bool isClosed; bool isMinimized; From 9423ebc97c4839c40ca8e9a75b22f1f0f028e6c0 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 14 Aug 2021 11:32:25 -0700 Subject: [PATCH 18/25] Fix crash when changing external editors Initialize a pointer to nullptr. --- rtgui/editorpanel.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index 6649fc970..775e6a935 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -470,7 +470,9 @@ public: EditorPanel::EditorPanel (FilePanel* filePanel) : catalogPane (nullptr), realized (false), tbBeforeLock (nullptr), iHistoryShow (nullptr), iHistoryHide (nullptr), iTopPanel_1_Show (nullptr), iTopPanel_1_Hide (nullptr), iRightPanel_1_Show (nullptr), iRightPanel_1_Hide (nullptr), - iBeforeLockON (nullptr), iBeforeLockOFF (nullptr), previewHandler (nullptr), beforePreviewHandler (nullptr), + iBeforeLockON (nullptr), iBeforeLockOFF (nullptr), + externalEditorChangedSignal (nullptr), + previewHandler (nullptr), beforePreviewHandler (nullptr), beforeIarea (nullptr), beforeBox (nullptr), afterBox (nullptr), beforeLabel (nullptr), afterLabel (nullptr), beforeHeaderBox (nullptr), afterHeaderBox (nullptr), parent (nullptr), parentWindow (nullptr), openThm (nullptr), selectedFrame(0), isrc (nullptr), ipc (nullptr), beforeIpc (nullptr), err (0), isProcessing (false), From d3e524a491c900adac72ef10fde01c5c5c626127 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 14 Aug 2021 16:05:11 -0700 Subject: [PATCH 19/25] Fix crash when adding an external editor On some platforms, the app chooser dialog causes a crash after selecting an application. The application information returned by the dialog may also trigger crashes when accessed. See https://gitlab.gnome.org/GNOME/glib/-/issues/1104 and https://gitlab.gnome.org/GNOME/glibmm/-/issues/94. This commit overrides gtkmm's app chooser dialog to work around these bugs. --- rtgui/CMakeLists.txt | 1 + rtgui/editorpanel.cc | 7 +-- rtgui/editorpanel.h | 5 +- rtgui/externaleditorpreferences.cc | 4 +- rtgui/externaleditorpreferences.h | 7 +-- rtgui/rtappchooserdialog.cc | 77 ++++++++++++++++++++++++++++++ rtgui/rtappchooserdialog.h | 39 +++++++++++++++ 7 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 rtgui/rtappchooserdialog.cc create mode 100644 rtgui/rtappchooserdialog.h diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt index ed6a0b364..3ca04dc2b 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -135,6 +135,7 @@ set(NONCLISOURCEFILES retinex.cc rgbcurves.cc rotate.cc + rtappchooserdialog.cc rtimage.cc rtscalable.cc rtsurface.cc diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index 775e6a935..6cb90a444 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -39,6 +39,7 @@ #include "procparamchangers.h" #include "placesbrowser.h" #include "pathutils.h" +#include "rtappchooserdialog.h" #include "thumbnail.h" #include "toolpanelcoord.h" @@ -2014,7 +2015,7 @@ void EditorPanel::sendToExternalPressed() { if (options.externalEditorIndex == -1) { // "Other" external editor. Show app chooser dialog to let user pick. - Gtk::AppChooserDialog *dialog = getAppChooserDialog(); + RTAppChooserDialog *dialog = getAppChooserDialog(); dialog->show(); } else { struct ExternalEditor editor = options.externalEditors.at(options.externalEditorIndex); @@ -2195,10 +2196,10 @@ bool EditorPanel::idle_sentToGimp (ProgressConnector *pc, rtengine::IImagef return false; } -Gtk::AppChooserDialog *EditorPanel::getAppChooserDialog() +RTAppChooserDialog *EditorPanel::getAppChooserDialog() { if (!app_chooser_dialog.get()) { - app_chooser_dialog.reset(new Gtk::AppChooserDialog("image/tiff")); + app_chooser_dialog.reset(new RTAppChooserDialog("image/tiff")); app_chooser_dialog->signal_response().connect( sigc::mem_fun(*this, &EditorPanel::onAppChooserDialogResponse) ); diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index 1bbb95a6e..e822f1677 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -46,6 +46,7 @@ class FilePanel; class MyProgressBar; class Navigator; class PopUpButton; +class RTAppChooserDialog; class Thumbnail; class ToolPanelCoordinator; @@ -212,7 +213,7 @@ private: bool idle_sendToGimp ( ProgressConnector *pc, Glib::ustring fname); bool idle_sentToGimp (ProgressConnector *pc, rtengine::IImagefloat* img, Glib::ustring filename); void histogramProfile_toggled (); - Gtk::AppChooserDialog *getAppChooserDialog(); + RTAppChooserDialog *getAppChooserDialog(); void onAppChooserDialogResponse(int resposneId); void updateExternalEditorSelection(); @@ -249,7 +250,7 @@ private: Gtk::Button* navNext; Gtk::Button* navPrev; Glib::RefPtr external_editor_info; - std::unique_ptr app_chooser_dialog; + std::unique_ptr app_chooser_dialog; ExternalEditorChangedSignal *externalEditorChangedSignal; sigc::connection externalEditorChangedSignalConnection; diff --git a/rtgui/externaleditorpreferences.cc b/rtgui/externaleditorpreferences.cc index df6b6efc2..b64cdcf9f 100644 --- a/rtgui/externaleditorpreferences.cc +++ b/rtgui/externaleditorpreferences.cc @@ -164,7 +164,7 @@ Gtk::TreeViewColumn *ExternalEditorPreferences::makeCommandColumn() } void ExternalEditorPreferences::onAppChooserDialogResponse( - int response_id, Gtk::AppChooserDialog *dialog) + int response_id, RTAppChooserDialog *dialog) { switch (response_id) { case Gtk::RESPONSE_OK: @@ -190,7 +190,7 @@ void ExternalEditorPreferences::openAppChooserDialog() return; } - app_chooser_dialog.reset(new Gtk::AppChooserDialog("image/tiff")); + app_chooser_dialog.reset(new RTAppChooserDialog("image/tiff")); app_chooser_dialog->signal_response().connect(sigc::bind( sigc::mem_fun(*this, &ExternalEditorPreferences::onAppChooserDialogResponse), app_chooser_dialog.get() diff --git a/rtgui/externaleditorpreferences.h b/rtgui/externaleditorpreferences.h index be988e901..dbd0f1648 100644 --- a/rtgui/externaleditorpreferences.h +++ b/rtgui/externaleditorpreferences.h @@ -18,7 +18,6 @@ */ #pragma once -#include #include #include #include @@ -26,6 +25,8 @@ #include #include +#include "rtappchooserdialog.h" + /** * Widget for editing the external editors options. @@ -98,7 +99,7 @@ private: Gtk::Button *button_app_chooser; Gtk::Button *button_add; Gtk::Button *button_remove; - std::unique_ptr app_chooser_dialog; + std::unique_ptr app_chooser_dialog; /** * Inserts a new editor entry after the current selection, or at the end if @@ -117,7 +118,7 @@ private: * 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. */ - void onAppChooserDialogResponse(int responseId, Gtk::AppChooserDialog *dialog); + void onAppChooserDialogResponse(int responseId, RTAppChooserDialog *dialog); /** * Shows the app chooser dialog. */ diff --git a/rtgui/rtappchooserdialog.cc b/rtgui/rtappchooserdialog.cc new file mode 100644 index 000000000..50a71ee38 --- /dev/null +++ b/rtgui/rtappchooserdialog.cc @@ -0,0 +1,77 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2021 Lawrence Lee + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ +#include "rtappchooserdialog.h" + +#if !(defined WIN32 || defined __APPLE__) +#define GTKMM_APPCHOOSERDIALOG +#endif + +RTAppChooserDialog::~RTAppChooserDialog() {} + +#ifdef GTKMM_APPCHOOSERDIALOG // Use Gtk::AppChooserDialog directly. + +RTAppChooserDialog::RTAppChooserDialog(const Glib::ustring &content_type) : + Gtk::AppChooserDialog(content_type) +{ +} + +Glib::RefPtr RTAppChooserDialog::get_app_info() +{ + return Gtk::AppChooserDialog::get_app_info(); +} + +Glib::RefPtr RTAppChooserDialog::get_app_info() const +{ + return Gtk::AppChooserDialog::get_app_info(); +} + +#else // Work around bugs with GLib and glibmm. + +RTAppChooserDialog::RTAppChooserDialog(const Glib::ustring &content_type) : + Gtk::AppChooserDialog(content_type) +{ + // GTK calls a faulty GLib function to update the most recently selected + // application after an application is selected. This removes all signal + // handlers to prevent the function call. + auto signal_id = g_signal_lookup("response", GTK_TYPE_APP_CHOOSER_DIALOG); + while (true) { + auto handler_id = g_signal_handler_find(gobj(), G_SIGNAL_MATCH_ID, signal_id, GQuark(), nullptr, nullptr, nullptr); + if (!handler_id) { + break; + } + g_signal_handler_disconnect(gobj(), handler_id); + } +} + +Glib::RefPtr RTAppChooserDialog::get_app_info() +{ + // glibmm wrapping of GAppInfo does not work on some platforms. Manually + // wrap it here. + GAppInfo *gAppInfo = gtk_app_chooser_get_app_info(GTK_APP_CHOOSER(gobj())); + return Glib::wrap(gAppInfo, true); +} + +Glib::RefPtr RTAppChooserDialog::get_app_info() const +{ + GAppInfo *gAppInfo = gtk_app_chooser_get_app_info(GTK_APP_CHOOSER( + const_cast(gobj()))); + return Glib::wrap(gAppInfo, true); +} + +#endif diff --git a/rtgui/rtappchooserdialog.h b/rtgui/rtappchooserdialog.h new file mode 100644 index 000000000..9d0e3b041 --- /dev/null +++ b/rtgui/rtappchooserdialog.h @@ -0,0 +1,39 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2021 Lawrence Lee + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ +#pragma once + +#include +#include +#include +#include + +/** + * Custom version of gtkmm's Gtk::AppChooserDialog to work around crashes + * (https://gitlab.gnome.org/GNOME/glib/-/issues/1104 and + * https://gitlab.gnome.org/GNOME/glibmm/-/issues/94). + */ +class RTAppChooserDialog : public Gtk::AppChooserDialog +{ +public: + RTAppChooserDialog(const Glib::ustring &content_type); + ~RTAppChooserDialog(); + + Glib::RefPtr get_app_info(); + Glib::RefPtr get_app_info() const; +}; From 672d6302f37aefae0e8341761a54d31042035210 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 14 Aug 2021 17:11:07 -0700 Subject: [PATCH 20/25] Fix storage of external editor icons De-serialize and serialize icons instead of using their names. --- rtgui/editorpanel.cc | 18 +++++++++++++-- rtgui/externaleditorpreferences.cc | 32 ++++++++++++++++++++++----- rtgui/externaleditorpreferences.h | 6 ++--- rtgui/options.cc | 35 ++++++++++++++++-------------- rtgui/options.h | 4 ++-- rtgui/preferences.cc | 4 ++-- rtgui/rtimage.cc | 7 +++++- 7 files changed, 75 insertions(+), 31 deletions(-) diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index 6cb90a444..987852891 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -2522,8 +2522,22 @@ void EditorPanel::updateExternalEditorWidget(int selectedIndex, const std::vecto // Add the editors. for (unsigned i = 0; i < editors.size(); i++) { const auto & name = editors[i].name.empty() ? Glib::ustring(" ") : editors[i].name; - if (!editors[i].icon_name.empty()) { - Glib::RefPtr gioIcon = Gio::Icon::create(editors[i].icon_name); + if (!editors[i].icon_serialized.empty()) { + Glib::RefPtr gioIcon; + GError *e = nullptr; + GVariant *icon_variant = g_variant_parse( + nullptr, editors[i].icon_serialized.c_str(), nullptr, nullptr, &e); + + if (e) { + std::cerr + << "Error loading external editor icon from \"" + << editors[i].icon_serialized << "\": " << e->message + << std::endl; + gioIcon = Glib::RefPtr(); + } else { + gioIcon = Gio::Icon::deserialize(Glib::VariantBase(icon_variant)); + } + send_to_external->insertEntry(i, gioIcon, name); } else { send_to_external->insertEntry(i, "palette-brush.png", name); diff --git a/rtgui/externaleditorpreferences.cc b/rtgui/externaleditorpreferences.cc index b64cdcf9f..61bf8dc3a 100644 --- a/rtgui/externaleditorpreferences.cc +++ b/rtgui/externaleditorpreferences.cc @@ -16,6 +16,8 @@ * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ +#include + #include "externaleditorpreferences.h" #include "multilangmgr.h" #include "rtimage.h" @@ -85,11 +87,11 @@ 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_name = icon == nullptr ? "" : icon->to_string(); + 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_name, + icon_serialized, rowIter->get_value(model_columns.other_data) )); } @@ -104,8 +106,28 @@ void ExternalEditorPreferences::setEditors( for (const ExternalEditorPreferences::EditorInfo & editor : editors) { auto row = *list_model->append(); + Glib::RefPtr icon; + + // Get icon. + if (editor.icon_serialized.empty()) { + icon = Glib::RefPtr(); + } else { + GError *e = nullptr; + GVariant *icon_variant = g_variant_parse( + nullptr, editor.icon_serialized.c_str(), nullptr, nullptr, &e); + if (e) { + std::cerr + << "Error loading external editor icon from \"" + << editor.icon_serialized << "\": " << e->message + << std::endl; + icon = Glib::RefPtr(); + } else { + icon = Gio::Icon::deserialize(Glib::VariantBase(icon_variant)); + } + } + row[model_columns.name] = editor.name; - row[model_columns.icon] = editor.icon_name.empty() ? Glib::RefPtr() : Gio::Icon::create(editor.icon_name); + row[model_columns.icon] = icon; row[model_columns.command] = editor.command; row[model_columns.other_data] = editor.other_data; } @@ -247,8 +269,8 @@ void ExternalEditorPreferences::updateToolbarSensitivity() } ExternalEditorPreferences::EditorInfo::EditorInfo( - Glib::ustring name, Glib::ustring command, Glib::ustring icon_name, void *other_data -) : name(name), icon_name(icon_name), command(command), other_data(other_data) + 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) { } diff --git a/rtgui/externaleditorpreferences.h b/rtgui/externaleditorpreferences.h index dbd0f1648..5761d8b63 100644 --- a/rtgui/externaleditorpreferences.h +++ b/rtgui/externaleditorpreferences.h @@ -41,7 +41,7 @@ public: explicit EditorInfo( Glib::ustring name = Glib::ustring(), Glib::ustring command = Glib::ustring(), - Glib::ustring icon_name = Glib::ustring(), + Glib::ustring icon_serialized = Glib::ustring(), void *other_data = nullptr ); /** @@ -49,9 +49,9 @@ public: */ Glib::ustring name; /** - * The string representation of the icon. See Gio::Icon::to_string(). + * The string representation of the icon. See Gio::Icon::serialize(). */ - Glib::ustring icon_name; + Glib::ustring icon_serialized; /** * The commandline for running the program. See * Gio::AppInfo::get_commandline() diff --git a/rtgui/options.cc b/rtgui/options.cc index 6a944faa6..53cedea95 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -859,7 +859,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", "IconNames")) { + || keyFile.has_key("External Editor", "IconsSerialized")) { // Multiple external editors. const auto & names = @@ -872,21 +872,21 @@ void Options::readFromFile(Glib::ustring fname) std::vector() : static_cast>( keyFile.get_string_list("External Editor", "Commands")); - const auto & icon_names = - !keyFile.has_key("External Editor", "IconNames") ? + const auto & icons_serialized = + !keyFile.has_key("External Editor", "IconsSerialized") ? std::vector() : static_cast>( - keyFile.get_string_list("External Editor", "IconNames")); + keyFile.get_string_list("External Editor", "IconsSerialized")); externalEditors = std::vector(std::max(std::max( - names.size(), commands.size()), icon_names.size())); + names.size(), commands.size()), icons_serialized.size())); for (unsigned i = 0; i < names.size(); i++) { externalEditors[i].name = names[i]; } for (unsigned i = 0; i < commands.size(); i++) { externalEditors[i].command = commands[i]; } - for (unsigned i = 0; i < icon_names.size(); i++) { - externalEditors[i].icon_name = icon_names[i]; + for (unsigned i = 0; i < icons_serialized.size(); i++) { + externalEditors[i].icon_serialized = icons_serialized[i]; } if (keyFile.has_key("External Editor", "EditorIndex")) { @@ -903,6 +903,9 @@ void Options::readFromFile(Glib::ustring fname) editorToSendTo = keyFile.get_integer("External Editor", "EditorKind"); #ifdef WIN32 + auto getIconSerialized = [](const Glib::ustring &executable) { + return Glib::ustring::compose("('themed', <['%1,0', '%1,0-symbolic']>)", executable); + }; Glib::ustring gimpDir = ""; if (keyFile.has_key("External Editor", "GimpDir")) { gimpDir = keyFile.get_string("External Editor", "GimpDir"); @@ -912,7 +915,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", executable + ",0")); + externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", 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)); @@ -920,7 +923,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", executable + ",0")); + externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", getIconSerialized(executable))); break; } } @@ -935,7 +938,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 2) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("Photoshop", "\"" + executable + "\"", executable + ",0")); + externalEditors.push_back(ExternalEditor("Photoshop", "\"" + executable + "\"", getIconSerialized(executable))); } if (keyFile.has_key("External Editor", "CustomEditor")) { @@ -2296,17 +2299,17 @@ void Options::saveToFile(Glib::ustring fname) { std::vector names; std::vector commands; - std::vector icon_names; + std::vector icons_serialized; for (const auto & editor : externalEditors) { names.push_back(editor.name); commands.push_back(editor.command); - icon_names.push_back(editor.icon_name); + 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_string_list("External Editor", "IconNames", icon_names); + keyFile.set_string_list("External Editor", "IconsSerialized", icons_serialized); keyFile.set_integer("External Editor", "EditorIndex", externalEditorIndex); } @@ -2978,12 +2981,12 @@ Glib::ustring Options::getICCProfileCopyright() ExternalEditor::ExternalEditor() {} ExternalEditor::ExternalEditor( - const Glib::ustring &name, const Glib::ustring &command, const Glib::ustring &icon_name -): name(name), command(command), icon_name(icon_name) {} + const Glib::ustring &name, const Glib::ustring &command, const Glib::ustring &icon_serialized +): name(name), command(command), icon_serialized(icon_serialized) {} bool ExternalEditor::operator==(const ExternalEditor &other) const { - return this->name == other.name && this->command == other.command && this->icon_name == other.icon_name; + return this->name == other.name && this->command == other.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 38d3f3d76..d6b546d40 100644 --- a/rtgui/options.h +++ b/rtgui/options.h @@ -54,10 +54,10 @@ struct ExternalEditor { ExternalEditor(); - ExternalEditor(const Glib::ustring &name, const Glib::ustring &command, const Glib::ustring &icon_name); + ExternalEditor(const Glib::ustring &name, const Glib::ustring &command, const Glib::ustring &icon_serialized); Glib::ustring name; Glib::ustring command; - Glib::ustring icon_name; + Glib::ustring icon_serialized; bool operator==(const ExternalEditor & other) const; bool operator!=(const ExternalEditor & other) const; diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 786262896..a8f5c64a3 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -1793,7 +1793,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_name)); + editors[i].name, editors[i].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. @@ -2100,7 +2100,7 @@ void Preferences::fillPreferences() std::vector editorInfos; for (const auto &editor : moptions.externalEditors) { editorInfos.push_back(ExternalEditorPreferences::EditorInfo( - editor.name, editor.command, editor.icon_name)); + editor.name, editor.command, editor.icon_serialized)); } if (moptions.externalEditorIndex >= 0) { // Mark the current editor so we can track it. diff --git a/rtgui/rtimage.cc b/rtgui/rtimage.cc index 9a38c1885..98e61b897 100644 --- a/rtgui/rtimage.cc +++ b/rtgui/rtimage.cc @@ -294,7 +294,12 @@ Glib::RefPtr RTImage::createPixbufFromFile (const Glib::ustring& fi Glib::RefPtr RTImage::createPixbufFromGIcon(const Glib::RefPtr &icon, int size) { // TODO: Listen for theme changes and update icon, remove from cache. - return Gtk::IconTheme::get_default()->lookup_icon(icon, size, Gtk::ICON_LOOKUP_FORCE_SIZE).load_icon(); + Gtk::IconInfo iconInfo = Gtk::IconTheme::get_default()->lookup_icon(icon, size, Gtk::ICON_LOOKUP_FORCE_SIZE); + try { + return iconInfo.load_icon(); + } catch (Glib::Exception &e) { + return Glib::RefPtr(); + } } Cairo::RefPtr RTImage::createImgSurfFromFile (const Glib::ustring& fileName) From 732316dcafc1fdb16ea27c0c5af1707dffcd80b4 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Tue, 5 Oct 2021 21:49:42 -0700 Subject: [PATCH 21/25] Fix initial generation of external editor icons In Windows, escape backslashes and quotes in the serialized GVariants of the icons. --- rtgui/options.cc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/rtgui/options.cc b/rtgui/options.cc index 53cedea95..a6b693f53 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -904,7 +904,16 @@ void Options::readFromFile(Glib::ustring fname) #ifdef WIN32 auto getIconSerialized = [](const Glib::ustring &executable) { - return Glib::ustring::compose("('themed', <['%1,0', '%1,0-symbolic']>)", executable); + // Backslashes and quotes must be escaped in the text representation of GVariant strings. + // See https://www.freedesktop.org/software/gstreamer-sdk/data/docs/2012.5/glib/gvariant-text.html#gvariant-text-strings + Glib::ustring exec_escaped = ""; + for (const auto character : executable) { + if (character == '\\' || character == '\'') { + exec_escaped += '\\'; + } + exec_escaped += character; + } + return Glib::ustring::compose("('themed', <['%1,0', '%1,0-symbolic']>)", exec_escaped); }; Glib::ustring gimpDir = ""; if (keyFile.has_key("External Editor", "GimpDir")) { From 58f0783561d1244edfcd6413b4d8b9d8df98c95e Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 5 Mar 2022 18:26:52 -0800 Subject: [PATCH 22/25] Allow selecting file as external editor --- rtdata/languages/default | 2 + rtgui/externaleditorpreferences.cc | 71 ++++++++++++++++++++++++++++++ rtgui/externaleditorpreferences.h | 19 ++++++++ 3 files changed, 92 insertions(+) diff --git a/rtdata/languages/default b/rtdata/languages/default index 538c25b00..4d80f56a9 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -208,6 +208,7 @@ FILEBROWSER_ZOOMOUTHINT;Decrease thumbnail size.\n\nShortcuts:\n- - Multi FILECHOOSER_FILTER_ANY;All files FILECHOOSER_FILTER_COLPROF;Color profiles (*.icc) FILECHOOSER_FILTER_CURVE;Curve files +FILECHOOSER_FILTER_EXECUTABLE;Executable files FILECHOOSER_FILTER_LCP;Lens correction profiles FILECHOOSER_FILTER_PP;Processing profiles FILECHOOSER_FILTER_SAME;Same format as current photo @@ -1782,6 +1783,7 @@ PREFERENCES_EDITORCMDLINE;Custom command line PREFERENCES_EDITORLAYOUT;Editor layout 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_EXTEDITOR_DIR;Output directory diff --git a/rtgui/externaleditorpreferences.cc b/rtgui/externaleditorpreferences.cc index 61bf8dc3a..c6ee9b93e 100644 --- a/rtgui/externaleditorpreferences.cc +++ b/rtgui/externaleditorpreferences.cc @@ -18,6 +18,11 @@ */ #include +#include +#include +#include +#include + #include "externaleditorpreferences.h" #include "multilangmgr.h" #include "rtimage.h" @@ -54,11 +59,14 @@ ExternalEditorPreferences::ExternalEditorPreferences(): button_add->set_image(*add_image); button_remove->set_image(*remove_image); button_app_chooser = Gtk::make_managed(M("PREFERENCES_EXTERNALEDITOR_CHANGE")); + button_file_chooser = Gtk::manage(new Gtk::Button(M("PREFERENCES_EXTERNALEDITOR_CHANGE_FILE"))); button_app_chooser->signal_pressed().connect(sigc::mem_fun( *this, &ExternalEditorPreferences::openAppChooserDialog)); button_add->signal_pressed().connect(sigc::mem_fun( *this, &ExternalEditorPreferences::addEditor)); + button_file_chooser->signal_pressed().connect(sigc::mem_fun( + *this, &ExternalEditorPreferences::openFileChooserDialog)); button_remove->signal_pressed().connect(sigc::mem_fun( *this, &ExternalEditorPreferences::removeSelectedEditors)); @@ -69,6 +77,7 @@ ExternalEditorPreferences::ExternalEditorPreferences(): // Toolbar. toolbar.set_halign(Gtk::Align::ALIGN_END); toolbar.add(*button_app_chooser); + toolbar.add(*button_file_chooser); toolbar.add(*button_add); toolbar.add(*button_remove); @@ -204,6 +213,38 @@ void ExternalEditorPreferences::onAppChooserDialogResponse( } } +void ExternalEditorPreferences::onFileChooserDialogResponse( + int response_id, Gtk::FileChooserDialog *dialog) +{ + switch (response_id) { + case Gtk::RESPONSE_OK: { + dialog->close(); + + auto selection = list_view->get_selection()->get_selected_rows(); + for (const auto &selected : selection) { + auto row = *list_model->get_iter(selected); + row[model_columns.icon] = Glib::RefPtr(nullptr); + row[model_columns.command] = +#ifdef WIN32 + '"' + dialog->get_filename() + '"'; +#else + Glib::shell_quote(dialog->get_filename()); +#endif + } + + break; + } + + case Gtk::RESPONSE_CANCEL: + case Gtk::RESPONSE_CLOSE: + dialog->close(); + break; + + default: + break; + } +} + void ExternalEditorPreferences::openAppChooserDialog() { if (app_chooser_dialog.get()) { @@ -221,6 +262,35 @@ void ExternalEditorPreferences::openAppChooserDialog() app_chooser_dialog->show(); } +void ExternalEditorPreferences::openFileChooserDialog() +{ + if (file_chooser_dialog.get()) { + file_chooser_dialog->show(); + return; + } + + file_chooser_dialog.reset(new Gtk::FileChooserDialog(M("PREFERENCES_EXTERNALEDITOR_CHANGE_FILE"))); + + const auto exe_filter = Gtk::FileFilter::create(); + exe_filter->set_name(M("FILECHOOSER_FILTER_EXECUTABLE")); + exe_filter->add_custom(Gtk::FILE_FILTER_MIME_TYPE, [](const Gtk::FileFilter::Info &info) { + return Gio::content_type_can_be_executable(info.mime_type); + }); + const auto all_filter = Gtk::FileFilter::create(); + all_filter->set_name(M("FILECHOOSER_FILTER_ANY")); + all_filter->add_pattern("*"); + file_chooser_dialog->add_filter(exe_filter); + file_chooser_dialog->add_filter(all_filter); + + file_chooser_dialog->signal_response().connect(sigc::bind( + sigc::mem_fun(*this, &ExternalEditorPreferences::onFileChooserDialogResponse), + file_chooser_dialog.get())); + file_chooser_dialog->set_modal(); + file_chooser_dialog->add_button(M("GENERAL_CANCEL"), Gtk::RESPONSE_CANCEL); + file_chooser_dialog->add_button(M("GENERAL_OPEN"), Gtk::RESPONSE_OK); + file_chooser_dialog->show(); +} + void ExternalEditorPreferences::removeSelectedEditors() { auto selection = list_view->get_selection()->get_selected_rows(); @@ -265,6 +335,7 @@ void ExternalEditorPreferences::updateToolbarSensitivity() { bool selected = list_view->get_selection()->count_selected_rows(); button_app_chooser->set_sensitive(selected); + button_file_chooser->set_sensitive(selected); button_remove->set_sensitive(selected); } diff --git a/rtgui/externaleditorpreferences.h b/rtgui/externaleditorpreferences.h index 5761d8b63..34658d942 100644 --- a/rtgui/externaleditorpreferences.h +++ b/rtgui/externaleditorpreferences.h @@ -28,6 +28,14 @@ #include "rtappchooserdialog.h" +namespace Gtk +{ + +class FileChooserDialog; + +} + + /** * Widget for editing the external editors options. */ @@ -98,8 +106,10 @@ private: Gtk::Box toolbar; // Contains buttons for editing the list. Gtk::Button *button_app_chooser; Gtk::Button *button_add; + Gtk::Button *button_file_chooser; Gtk::Button *button_remove; std::unique_ptr app_chooser_dialog; + std::unique_ptr file_chooser_dialog; /** * Inserts a new editor entry after the current selection, or at the end if @@ -119,10 +129,19 @@ private: * Closes the dialog and updates the selected entry if an app was chosen. */ void onAppChooserDialogResponse(int responseId, RTAppChooserDialog *dialog); + /** + * Called when the user is done interacting with the file chooser dialog. + * Closes the dialog and updates the selected entry if a file was chosen. + */ + void onFileChooserDialogResponse(int responseId, Gtk::FileChooserDialog *dialog); /** * Shows the app chooser dialog. */ void openAppChooserDialog(); + /** + * Shows the file chooser dialog for picking an executable. + */ + void openFileChooserDialog(); /** * Removes all selected editors. */ From 329341f89f78cd995e6266e97e19e76f08a419b2 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 5 Mar 2022 18:36:28 -0800 Subject: [PATCH 23/25] Replace Gtk::make_managed() with Gtk::manage() Maintain compatibility with gtkmm 3.16. --- rtgui/editorpanel.cc | 2 +- rtgui/externaleditorpreferences.cc | 22 +++++++++++----------- rtgui/popupcommon.cc | 12 ++++++------ rtgui/preferences.cc | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index 987852891..a9797a216 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -671,7 +671,7 @@ EditorPanel::EditorPanel (FilePanel* filePanel) queueimg->set_tooltip_markup (M ("MAIN_BUTTON_PUTTOQUEUE_TOOLTIP")); setExpandAlignProperties (queueimg, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_FILL); - send_to_external = Gtk::make_managed("", false); + send_to_external = Gtk::manage(new PopUpButton("", false)); send_to_external->set_tooltip_text(M("MAIN_BUTTON_SENDTOEDITOR_TOOLTIP")); setExpandAlignProperties(send_to_external->buttonGroup, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_FILL); send_to_external->addEntry("palette-brush.png", M("GENERAL_OTHER")); diff --git a/rtgui/externaleditorpreferences.cc b/rtgui/externaleditorpreferences.cc index c6ee9b93e..58a562725 100644 --- a/rtgui/externaleditorpreferences.cc +++ b/rtgui/externaleditorpreferences.cc @@ -34,7 +34,7 @@ ExternalEditorPreferences::ExternalEditorPreferences(): toolbar(Gtk::Orientation::ORIENTATION_HORIZONTAL) { // List view. - list_view = Gtk::make_managed(); + 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(makeCommandColumn())); @@ -52,13 +52,13 @@ ExternalEditorPreferences::ExternalEditorPreferences(): list_scroll_area.add(*list_view); // Toolbar buttons. - auto add_image = Gtk::make_managed("add-small.png"); - auto remove_image = Gtk::make_managed("remove-small.png"); - button_add = Gtk::make_managed(); - button_remove = Gtk::make_managed(); + auto add_image = Gtk::manage(new RTImage("add-small.png")); + auto remove_image = Gtk::manage(new RTImage("remove-small.png")); + button_add = Gtk::manage(new Gtk::Button()); + button_remove = Gtk::manage(new Gtk::Button()); button_add->set_image(*add_image); button_remove->set_image(*remove_image); - button_app_chooser = Gtk::make_managed(M("PREFERENCES_EXTERNALEDITOR_CHANGE")); + button_app_chooser = Gtk::manage(new Gtk::Button(M("PREFERENCES_EXTERNALEDITOR_CHANGE"))); button_file_chooser = Gtk::manage(new Gtk::Button(M("PREFERENCES_EXTERNALEDITOR_CHANGE_FILE"))); button_app_chooser->signal_pressed().connect(sigc::mem_fun( @@ -159,9 +159,9 @@ void ExternalEditorPreferences::addEditor() Gtk::TreeViewColumn *ExternalEditorPreferences::makeAppColumn() { - auto name_renderer = Gtk::make_managed(); - auto icon_renderer = Gtk::make_managed(); - auto col = Gtk::make_managed(); + auto name_renderer = Gtk::manage(new Gtk::CellRendererText()); + auto icon_renderer = Gtk::manage(new Gtk::CellRendererPixbuf()); + auto col = Gtk::manage(new Gtk::TreeViewColumn()); col->set_title(M("PREFERENCES_EXTERNALEDITOR_COLUMN_NAME")); col->set_resizable(); @@ -180,8 +180,8 @@ Gtk::TreeViewColumn *ExternalEditorPreferences::makeAppColumn() Gtk::TreeViewColumn *ExternalEditorPreferences::makeCommandColumn() { - auto command_renderer = Gtk::make_managed(); - auto col = Gtk::make_managed(); + auto command_renderer = Gtk::manage(new Gtk::CellRendererText()); + auto col = Gtk::manage(new Gtk::TreeViewColumn()); col->set_title(M("PREFERENCES_EXTERNALEDITOR_COLUMN_COMMAND")); col->pack_start(*command_renderer); diff --git a/rtgui/popupcommon.cc b/rtgui/popupcommon.cc index c33ac068e..1dbde833e 100644 --- a/rtgui/popupcommon.cc +++ b/rtgui/popupcommon.cc @@ -50,14 +50,14 @@ PopUpCommon::PopUpCommon (Gtk::Button* thisButton, const Glib::ustring& label) buttonGroup->get_style_context()->add_class("image-combo"); // Create the image for the button - buttonImage = Gtk::make_managed(); + buttonImage = Gtk::manage(new RTImage()); setExpandAlignProperties(buttonImage, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); imageContainer->attach_next_to(*buttonImage, Gtk::POS_RIGHT, 1, 1); buttonImage->set_no_show_all(); // Create the button for showing the pop-up. - arrowButton = Gtk::make_managed(); - Gtk::Image *arrowImage = Gtk::make_managed(); + arrowButton = Gtk::manage(new Gtk::Button()); + Gtk::Image *arrowImage = Gtk::manage(new Gtk::Image()); arrowImage->set_from_icon_name("pan-down-symbolic", Gtk::ICON_SIZE_BUTTON); setExpandAlignProperties(arrowButton, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_FILL); arrowButton->add(*arrowImage); //menuSymbol); @@ -82,7 +82,7 @@ bool PopUpCommon::insertEntry(int position, const Glib::ustring& fileName, const { RTImage* image = nullptr; if (!fileName.empty()) { - image = Gtk::make_managed(fileName); + image = Gtk::manage(new RTImage(fileName)); } bool success = insertEntryImpl(position, fileName, Glib::RefPtr(), image, label); if (!success && image) { @@ -93,7 +93,7 @@ bool PopUpCommon::insertEntry(int position, const Glib::ustring& fileName, const bool PopUpCommon::insertEntry(int position, const Glib::RefPtr& gIcon, const Glib::ustring& label) { - RTImage* image = Gtk::make_managed(gIcon, Gtk::ICON_SIZE_BUTTON); + RTImage* image = Gtk::manage(new RTImage(gIcon, Gtk::ICON_SIZE_BUTTON)); bool success = insertEntryImpl(position, "", gIcon, image, label); if (!success) { delete image; @@ -107,7 +107,7 @@ bool PopUpCommon::insertEntryImpl(int position, const Glib::ustring& fileName, c return false; // Create the menu item and image - MyImageMenuItem *newItem = Gtk::make_managed(label, image); + MyImageMenuItem *newItem = Gtk::manage(new MyImageMenuItem(label, image)); imageIcons.insert(imageIcons.begin() + position, gIcon); imageFilenames.insert(imageFilenames.begin() + position, fileName); images.insert(images.begin() + position, newItem->getImage()); diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index a8f5c64a3..734e74e91 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -1249,7 +1249,7 @@ Gtk::Widget* Preferences::getGeneralPanel() #endif #endif - externalEditors = Gtk::make_managed(); + externalEditors = Gtk::manage(new ExternalEditorPreferences()); externalEditors->set_size_request(-1, 200); #ifdef EXT_EDITORS_RADIOS externaleditorGrid->attach_next_to(*externalEditors, *edOther, Gtk::POS_BOTTOM, 2, 1); From 793a77fc4484505645106547ee952dfd2e2d30a8 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sun, 6 Mar 2022 11:52:09 -0800 Subject: [PATCH 24/25] Add missing include --- rtgui/extprog.h | 1 + 1 file changed, 1 insertion(+) diff --git a/rtgui/extprog.h b/rtgui/extprog.h index 86dbc1674..cdcf14e48 100644 --- a/rtgui/extprog.h +++ b/rtgui/extprog.h @@ -20,6 +20,7 @@ #include +#include #include #include "threadutils.h" From 646065f64310aa4d1e83a5a2d2920ccb80849b96 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sun, 1 Jan 2023 21:22:47 -0800 Subject: [PATCH 25/25] Remove unused code Remove the preferences code for selecting the external editor since it is superseded by the multiple external editor preferences. --- rtgui/preferences.cc | 129 ------------------------------------------- 1 file changed, 129 deletions(-) diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 2c993d65e..85816e39f 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -34,8 +34,6 @@ #include #endif -//#define EXT_EDITORS_RADIOS // TODO: Remove the corresponding code after testing. - namespace { void placeSpinBox(Gtk::Container* where, Gtk::SpinButton* &spin, const std::string &labelText, int digits, int inc0, int inc1, int maxLength, int range0, int range1, const std::string &toolTip = "") { Gtk::Box* HB = Gtk::manage ( new Gtk::Box () ); @@ -1205,78 +1203,16 @@ Gtk::Widget* Preferences::getGeneralPanel() Gtk::Frame* fdg = Gtk::manage(new Gtk::Frame(M("PREFERENCES_EXTERNALEDITOR"))); setExpandAlignProperties(fdg, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); -#ifdef EXT_EDITORS_RADIOS - Gtk::Grid* externaleditorGrid = Gtk::manage(new Gtk::Grid()); - externaleditorGrid->set_column_spacing(4); - externaleditorGrid->set_row_spacing(4); - setExpandAlignProperties(externaleditorGrid, false, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); - - edOther = Gtk::manage(new Gtk::RadioButton(M("PREFERENCES_EDITORCMDLINE") + ":")); - setExpandAlignProperties(edOther, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - editorToSendTo = Gtk::manage(new Gtk::Entry()); - setExpandAlignProperties(editorToSendTo, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_BASELINE); - Gtk::RadioButton::Group ge = edOther->get_group(); - -#ifdef __APPLE__ - edGimp = Gtk::manage(new Gtk::RadioButton("GIMP")); - setExpandAlignProperties(edGimp, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - edGimp->set_group(ge); - externaleditorGrid->attach_next_to(*edGimp, Gtk::POS_TOP, 2, 1); - - edPS = Gtk::manage(new Gtk::RadioButton(M("PREFERENCES_PSPATH") + ":")); - setExpandAlignProperties(edPS, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - psDir = Gtk::manage(new MyFileChooserButton(M("PREFERENCES_PSPATH"), Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER)); - setExpandAlignProperties(psDir, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); - externaleditorGrid->attach_next_to(*edPS, *edGimp, Gtk::POS_BOTTOM, 1, 1); - externaleditorGrid->attach_next_to(*psDir, *edPS, Gtk::POS_RIGHT, 1, 1); - edPS->set_group(ge); - - externaleditorGrid->attach_next_to(*edOther, *edPS, Gtk::POS_BOTTOM, 1, 1); - externaleditorGrid->attach_next_to(*editorToSendTo, *edOther, Gtk::POS_RIGHT, 1, 1); -#elif defined WIN32 - edGimp = Gtk::manage(new Gtk::RadioButton(M("PREFERENCES_GIMPPATH") + ":")); - setExpandAlignProperties(edGimp, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - gimpDir = Gtk::manage(new MyFileChooserButton(M("PREFERENCES_GIMPPATH"), Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER)); - setExpandAlignProperties(gimpDir, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); - externaleditorGrid->attach_next_to(*edGimp, Gtk::POS_TOP, 1, 1); - externaleditorGrid->attach_next_to(*gimpDir, *edGimp, Gtk::POS_RIGHT, 1, 1); - edGimp->set_group(ge); - - edPS = Gtk::manage(new Gtk::RadioButton(M("PREFERENCES_PSPATH") + ":")); - setExpandAlignProperties(edPS, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - psDir = Gtk::manage(new MyFileChooserButton(M("PREFERENCES_PSPATH"), Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER)); - setExpandAlignProperties(psDir, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); - externaleditorGrid->attach_next_to(*edPS, *edGimp, Gtk::POS_BOTTOM, 1, 1); - externaleditorGrid->attach_next_to(*psDir, *edPS, Gtk::POS_RIGHT, 1, 1); - edPS->set_group(ge); - - externaleditorGrid->attach_next_to(*edOther, *edPS, Gtk::POS_BOTTOM, 1, 1); - externaleditorGrid->attach_next_to(*editorToSendTo, *edOther, Gtk::POS_RIGHT, 1, 1); -#else - edGimp = Gtk::manage(new Gtk::RadioButton("GIMP")); - setExpandAlignProperties(edGimp, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - externaleditorGrid->attach_next_to(*edGimp, Gtk::POS_TOP, 2, 1); - edGimp->set_group(ge); - - externaleditorGrid->attach_next_to(*edOther, *edGimp, Gtk::POS_BOTTOM, 1, 1); - externaleditorGrid->attach_next_to(*editorToSendTo, *edOther, Gtk::POS_RIGHT, 1, 1); -#endif -#endif externalEditors = Gtk::manage(new ExternalEditorPreferences()); externalEditors->set_size_request(-1, 200); -#ifdef EXT_EDITORS_RADIOS - externaleditorGrid->attach_next_to(*externalEditors, *edOther, Gtk::POS_BOTTOM, 2, 1); -#endif // fdg->add(*externaleditorGrid); editor_dir_temp = Gtk::manage(new Gtk::RadioButton(M("PREFERENCES_EXTEDITOR_DIR_TEMP"))); editor_dir_current = Gtk::manage(new Gtk::RadioButton(M("PREFERENCES_EXTEDITOR_DIR_CURRENT"))); editor_dir_custom = Gtk::manage(new Gtk::RadioButton(M("PREFERENCES_EXTEDITOR_DIR_CUSTOM") + ": ")); editor_dir_custom_path = Gtk::manage(new MyFileChooserButton("", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER)); -#ifndef EXT_EDITORS_RADIOS Gtk::RadioButton::Group ge; -#endif ge = editor_dir_temp->get_group(); editor_dir_current->set_group(ge); editor_dir_custom->set_group(ge); @@ -1296,12 +1232,7 @@ Gtk::Widget* Preferences::getGeneralPanel() f->add(*vb); hb = Gtk::manage(new Gtk::Box()); -#ifdef EXT_EDITORS_RADIOS - externaleditorGrid->attach_next_to(*externalEditors, *edOther, Gtk::POS_BOTTOM, 2, 1); - hb->pack_start(*externaleditorGrid); -#else hb->pack_start(*externalEditors); -#endif hb->pack_start(*f, Gtk::PACK_EXPAND_WIDGET, 4); vb = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); @@ -1773,35 +1704,6 @@ void Preferences::storePreferences() moptions.pseudoHiDPISupport = pseudoHiDPI->get_active(); -#ifdef EXT_EDITORS_RADIOS -#ifdef WIN32 - moptions.gimpDir = gimpDir->get_filename(); - moptions.psDir = psDir->get_filename(); -#elif defined __APPLE__ - moptions.psDir = psDir->get_filename(); -#endif - moptions.customEditorProg = editorToSendTo->get_text(); - - if (edGimp->get_active()) { - moptions.editorToSendTo = 1; - } - -#ifdef WIN32 - else if (edPS->get_active()) { - moptions.editorToSendTo = 2; - } - -#elif defined __APPLE__ - else if (edPS->get_active()) { - moptions.editorToSendTo = 2; - } - -#endif - else if (edOther->get_active()) { - moptions.editorToSendTo = 3; - } -#endif - const std::vector &editors = externalEditors->getEditors(); moptions.externalEditors.resize(editors.size()); moptions.externalEditorIndex = -1; @@ -2083,37 +1985,6 @@ void Preferences::fillPreferences() hlThresh->set_value(moptions.highlightThreshold); shThresh->set_value(moptions.shadowThreshold); -#ifdef EXT_EDITORS_RADIOS - edGimp->set_active(moptions.editorToSendTo == 1); - edOther->set_active(moptions.editorToSendTo == 3); -#ifdef WIN32 - edPS->set_active(moptions.editorToSendTo == 2); - - if (Glib::file_test(moptions.gimpDir, Glib::FILE_TEST_IS_DIR)) { - gimpDir->set_current_folder(moptions.gimpDir); - } else { - gimpDir->set_current_folder(Glib::get_home_dir()); - } - - if (Glib::file_test(moptions.psDir, Glib::FILE_TEST_IS_DIR)) { - psDir->set_current_folder(moptions.psDir); - } else { - psDir->set_current_folder(Glib::get_home_dir()); - } - -#elif defined __APPLE__ - edPS->set_active(moptions.editorToSendTo == 2); - - if (Glib::file_test(moptions.psDir, Glib::FILE_TEST_IS_DIR)) { - psDir->set_current_folder(moptions.psDir); - } else { - psDir->set_current_folder(Glib::get_home_dir()); - } - -#endif - editorToSendTo->set_text(moptions.customEditorProg); -#endif - std::vector editorInfos; for (const auto &editor : moptions.externalEditors) { editorInfos.push_back(ExternalEditorPreferences::EditorInfo(