Merge pull request #6299 from Beep6581/multi-external-editor

Multiple External Editors
This commit is contained in:
Lawrence37 2023-01-02 15:07:25 -08:00 committed by GitHub
commit f449a726e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1387 additions and 178 deletions

View File

@ -210,6 +210,7 @@ FILEBROWSER_ZOOMOUTHINT;Decrease thumbnail size.\n\nShortcuts:\n<b>-</b> - 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
@ -237,6 +238,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
@ -1621,7 +1623,7 @@ MAIN_BUTTON_PREFERENCES;Preferences
MAIN_BUTTON_PUTTOQUEUE_TOOLTIP;Put current image to processing queue.\nShortcut: <b>Ctrl+b</b>
MAIN_BUTTON_SAVE_TOOLTIP;Save current image.\nShortcut: <b>Ctrl+s</b>\nSave current profile (.pp3).\nShortcut: <b>Ctrl+Shift+s</b>
MAIN_BUTTON_SENDTOEDITOR;Edit image in external editor
MAIN_BUTTON_SENDTOEDITOR_TOOLTIP;Edit current image in external editor.\nShortcut: <b>Ctrl+e</b>
MAIN_BUTTON_SENDTOEDITOR_TOOLTIP;Edit current image in external editor.\nShortcut: <b>Ctrl+e</b>\nCurrent editor:
MAIN_BUTTON_SHOWHIDESIDEPANELS_TOOLTIP;Show/hide all side panels.\nShortcut: <b>m</b>
MAIN_BUTTON_UNFULLSCREEN;Exit fullscreen
MAIN_FRAME_EDITOR;Editor
@ -1872,6 +1874,10 @@ PREFERENCES_EXTEDITOR_DIR_CUSTOM;Custom
PREFERENCES_EXTEDITOR_DIR_TEMP;OS temp dir
PREFERENCES_EXTEDITOR_FLOAT32;32-bit float TIFF output
PREFERENCES_EXTERNALEDITOR;External Editor
PREFERENCES_EXTERNALEDITOR_CHANGE;Change Application
PREFERENCES_EXTERNALEDITOR_CHANGE_FILE;Change Executable
PREFERENCES_EXTERNALEDITOR_COLUMN_NAME;Name
PREFERENCES_EXTERNALEDITOR_COLUMN_COMMAND;Command
PREFERENCES_FBROWSEROPTS;File Browser / Thumbnail Options
PREFERENCES_FILEBROWSERTOOLBARSINGLEROW;Compact toolbars in File Browser
PREFERENCES_FLATFIELDFOUND;Found

View File

@ -62,6 +62,7 @@ set(NONCLISOURCEFILES
exiffiltersettings.cc
exifpanel.cc
exportpanel.cc
externaleditorpreferences.cc
extprog.cc
fattaltonemap.cc
filebrowser.cc
@ -134,6 +135,7 @@ set(NONCLISOURCEFILES
retinex.cc
rgbcurves.cc
rotate.cc
rtappchooserdialog.cc
rtimage.cc
rtscalable.cc
rtsurface.cc

View File

@ -39,6 +39,7 @@
#include "procparamchangers.h"
#include "placesbrowser.h"
#include "pathutils.h"
#include "rtappchooserdialog.h"
#include "thumbnail.h"
#include "toolpanelcoord.h"
@ -701,7 +702,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),
@ -899,12 +902,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::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"));
updateExternalEditorWidget(
options.externalEditorIndex >= 0 ? options.externalEditorIndex : options.externalEditors.size(),
options.externalEditors
);
send_to_external->show();
// Status box
progressLabel = Gtk::manage (new MyProgressBar (300));
@ -969,7 +975,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) {
@ -1073,7 +1079,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) {
@ -1906,7 +1913,7 @@ bool EditorPanel::handleShortcutKey (GdkEventKey* event)
case GDK_KEY_e:
if (!gimpPlugin) {
sendToGimpPressed();
sendToExternalPressed();
}
return true;
@ -2024,7 +2031,7 @@ bool EditorPanel::idle_saveImage (ProgressConnector<rtengine::IImagefloat*> *pc,
msgd.run ();
saveimgas->set_sensitive (true);
sendtogimp->set_sensitive (true);
send_to_external->set_sensitive(true);
isProcessing = false;
}
@ -2052,7 +2059,7 @@ bool EditorPanel::idle_imageSaved (ProgressConnector<int> *pc, rtengine::IImagef
}
saveimgas->set_sensitive (true);
sendtogimp->set_sensitive (true);
send_to_external->set_sensitive(true);
parent->setProgressStr ("");
parent->setProgress (0.);
@ -2163,7 +2170,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 ();
@ -2194,7 +2201,7 @@ void EditorPanel::queueImgPressed ()
parent->addBatchQueueJob (createBatchQueueEntry ());
}
void EditorPanel::sendToGimpPressed ()
void EditorPanel::sendToExternal()
{
if (!ipc || !openThm) {
return;
@ -2206,12 +2213,46 @@ void EditorPanel::sendToGimpPressed ()
if (options.editor_bypass_output_profile) {
pparams.icm.outputProfile = rtengine::procparams::ColorManagementParams::NoProfileString;
}
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);
ProgressConnector<rtengine::IImagefloat*> *ld = new ProgressConnector<rtengine::IImagefloat*>();
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<unsigned>(index) == options.externalEditors.size()) {
index = -1;
}
options.externalEditorIndex = index;
if (externalEditorChangedSignal) {
externalEditorChangedSignal->emit();
}
}
void EditorPanel::sendToExternalPressed()
{
if (options.externalEditorIndex == -1) {
// "Other" external editor. Show app chooser dialog to let user pick.
RTAppChooserDialog *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();
}
}
@ -2265,6 +2306,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();
@ -2330,7 +2388,7 @@ bool EditorPanel::idle_sendToGimp ( ProgressConnector<rtengine::IImagefloat*> *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;
@ -2338,27 +2396,27 @@ bool EditorPanel::idle_sendToGimp ( ProgressConnector<rtengine::IImagefloat*> *p
bool EditorPanel::idle_sentToGimp (ProgressConnector<int> *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);
sendtogimp->set_sensitive (true);
send_to_external->set_sensitive(true);
parent->setProgressStr ("");
parent->setProgress (0.);
bool success = false;
setUserOnlyPermission(Gio::File::create_for_path(filename), 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);
@ -2371,6 +2429,48 @@ bool EditorPanel::idle_sentToGimp (ProgressConnector<int> *pc, rtengine::IImagef
return false;
}
RTAppChooserDialog *EditorPanel::getAppChooserDialog()
{
if (!app_chooser_dialog.get()) {
app_chooser_dialog.reset(new RTAppChooserDialog("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::updateExternalEditorSelection()
{
int index = send_to_external->getSelected();
if (index >= 0 && static_cast<unsigned>(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)
{
@ -2646,6 +2746,40 @@ void EditorPanel::tbShowHideSidePanels_managestate()
ShowHideSidePanelsconn.block (false);
}
void EditorPanel::updateExternalEditorWidget(int selectedIndex, const std::vector<ExternalEditor> &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_serialized.empty()) {
Glib::RefPtr<Gio::Icon> 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<Gio::Icon>();
} 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);
}
}
send_to_external->setSelected(selectedIndex);
send_to_external->show();
}
void EditorPanel::updateProfiles (const Glib::ustring &printerProfile, rtengine::RenderingIntent printerIntent, bool printerBPC)
{
}

View File

@ -38,11 +38,15 @@ template<typename T>
class array2D;
}
using ExternalEditorChangedSignal = sigc::signal<void>;
class BatchQueueEntry;
class EditorPanel;
class FilePanel;
class MyProgressBar;
class Navigator;
class PopUpButton;
class RTAppChooserDialog;
class Thumbnail;
class ToolPanelCoordinator;
@ -65,6 +69,7 @@ class EditorPanel final :
public rtengine::NonCopyable
{
public:
explicit EditorPanel (FilePanel* filePanel = nullptr);
~EditorPanel () override;
@ -162,11 +167,17 @@ public:
void tbBeforeLock_toggled();
void saveAsPressed ();
void queueImgPressed ();
void sendToGimpPressed ();
void sendToExternal();
void sendToExternalChanged(int);
void sendToExternalPressed();
void openNextEditorImage ();
void openPreviousEditorImage ();
void syncFileBrowser ();
// Signals.
ExternalEditorChangedSignal * getExternalEditorChangedSignal();
void setExternalEditorChangedSignal(ExternalEditorChangedSignal *signal);
void tbTopPanel_1_visible (bool visible);
bool CheckSidePanelsVisibility();
void tbShowHideSidePanels_managestate();
@ -182,6 +193,7 @@ public:
{
return isProcessing;
}
void updateExternalEditorWidget(int selectedIndex, const std::vector<ExternalEditor> &editors);
void updateProfiles (const Glib::ustring &printerProfile, rtengine::RenderingIntent printerIntent, bool printerBPC);
void updateTPVScrollbar (bool hide);
void updateHistogramPosition (int oldPosition, int newPosition);
@ -201,6 +213,9 @@ private:
bool idle_sendToGimp ( ProgressConnector<rtengine::IImagefloat*> *pc, Glib::ustring fname);
bool idle_sentToGimp (ProgressConnector<int> *pc, rtengine::IImagefloat* img, Glib::ustring filename);
void histogramProfile_toggled ();
RTAppChooserDialog *getAppChooserDialog();
void onAppChooserDialogResponse(int resposneId);
void updateExternalEditorSelection();
Glib::ustring lastSaveAsFileName;
@ -230,10 +245,18 @@ 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<Gio::AppInfo> external_editor_info;
std::unique_ptr<RTAppChooserDialog> app_chooser_dialog;
ExternalEditorChangedSignal *externalEditorChangedSignal;
sigc::connection externalEditorChangedSignalConnection;
rtengine::InitialImage *cached_exported_image;
rtengine::procparams::ProcParams cached_exported_pparams;
Glib::ustring cached_exported_filename;
class ColorManagementToolbar;
std::unique_ptr<ColorManagementToolbar> colorMgmtToolBar;

View File

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

View File

@ -39,6 +39,8 @@ private:
std::set<Glib::ustring> filesEdited;
std::map<Glib::ustring, EditorPanel*> epanels;
sigc::signal<void> externalEditorChangedSignal;
bool isFullscreen;
bool isClosed;
bool isMinimized;

View File

@ -0,0 +1,354 @@
/*
* This file is part of RawTherapee.
*
* Copyright (c) 2021 Lawrence Lee <billee@ucdavis.edu>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <giomm/contenttype.h>
#include <glibmm/shell.h>
#include <gtkmm/filechooserdialog.h>
#include <gtkmm/stock.h>
#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::manage(new Gtk::TreeView());
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::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::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(
*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));
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_file_chooser);
toolbar.add(*button_add);
toolbar.add(*button_remove);
// This widget's children.
add(list_scroll_area);
add(toolbar);
show_all();
}
std::vector<ExternalEditorPreferences::EditorInfo>
ExternalEditorPreferences::getEditors() const
{
std::vector<ExternalEditorPreferences::EditorInfo> 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_serialized = icon == nullptr ? "" : icon->serialize().print();
editors.push_back(ExternalEditorPreferences::EditorInfo(
rowIter->get_value(model_columns.name),
rowIter->get_value(model_columns.command),
icon_serialized,
rowIter->get_value(model_columns.other_data)
));
}
return editors;
}
void ExternalEditorPreferences::setEditors(
const std::vector<ExternalEditorPreferences::EditorInfo> &editors)
{
list_model->clear();
for (const ExternalEditorPreferences::EditorInfo & editor : editors) {
auto row = *list_model->append();
Glib::RefPtr<Gio::Icon> icon;
// Get icon.
if (editor.icon_serialized.empty()) {
icon = Glib::RefPtr<Gio::Icon>();
} 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<Gio::Icon>();
} else {
icon = Gio::Icon::deserialize(Glib::VariantBase(icon_variant));
}
}
row[model_columns.name] = editor.name;
row[model_columns.icon] = icon;
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::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();
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::manage(new Gtk::CellRendererText());
auto col = Gtk::manage(new Gtk::TreeViewColumn());
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, RTAppChooserDialog *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::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<Gio::Icon>(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()) {
app_chooser_dialog->refresh();
app_chooser_dialog->show();
return;
}
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()
));
app_chooser_dialog->set_modal();
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();
for (const auto &selected : selection) {
list_model->erase(list_model->get_iter(selected));
}
}
void ExternalEditorPreferences::setApp(const Glib::RefPtr<Gio::AppInfo> 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<Gio::Icon>(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_file_chooser->set_sensitive(selected);
button_remove->set_sensitive(selected);
}
ExternalEditorPreferences::EditorInfo::EditorInfo(
Glib::ustring name, Glib::ustring command, Glib::ustring icon_serialized, void *other_data
) : name(name), icon_serialized(icon_serialized), command(command), other_data(other_data)
{
}
ExternalEditorPreferences::ModelColumns::ModelColumns()
{
add(name);
add(icon);
add(command);
add(other_data);
}

View File

@ -0,0 +1,167 @@
/*
* This file is part of RawTherapee.
*
* Copyright (c) 2021 Lawrence Lee <billee@ucdavis.edu>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <gtkmm/button.h>
#include <gtkmm/box.h>
#include <gtkmm/liststore.h>
#include <gtkmm/scrolledwindow.h>
#include <gtkmm/treemodelcolumn.h>
#include <gtkmm/treeview.h>
#include "rtappchooserdialog.h"
namespace Gtk
{
class FileChooserDialog;
}
/**
* 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_serialized = Glib::ustring(),
void *other_data = nullptr
);
/**
* Name of the external editor.
*/
Glib::ustring name;
/**
* The string representation of the icon. See Gio::Icon::serialize().
*/
Glib::ustring icon_serialized;
/**
* 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<EditorInfo> getEditors() const;
/**
* Populates this widget with the external editors described in the
* argument.
*/
void setEditors(const std::vector<EditorInfo> &editors);
private:
/**
* Model representing the data fields each external editor entry has.
*/
class ModelColumns : public Gtk::TreeModelColumnRecord
{
public:
ModelColumns();
Gtk::TreeModelColumn<Glib::ustring> name;
Gtk::TreeModelColumn<Glib::RefPtr<Gio::Icon>> icon;
Gtk::TreeModelColumn<Glib::ustring> command;
Gtk::TreeModelColumn<void *> other_data;
};
ModelColumns model_columns;
Glib::RefPtr<Gtk::ListStore> 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_file_chooser;
Gtk::Button *button_remove;
std::unique_ptr<RTAppChooserDialog> app_chooser_dialog;
std::unique_ptr<Gtk::FileChooserDialog> file_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, 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.
*/
void removeSelectedEditors();
/**
* Sets the selected entries with the provided information.
*/
void setApp(const Glib::RefPtr<Gio::AppInfo> 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();
};

View File

@ -339,3 +339,16 @@ bool ExtProgStore::openInCustomEditor (const Glib::ustring& fileName)
#endif
}
bool ExtProgStore::openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr<Gio::AppInfo> &editorInfo)
{
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;
}
}

View File

@ -20,10 +20,16 @@
#include <vector>
#include <glibmm/refptr.h>
#include <glibmm/ustring.h>
#include "threadutils.h"
namespace Gio
{
class AppInfo;
}
struct ExtProgAction
{
Glib::ustring filePathEXE;
@ -64,6 +70,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<Gio::AppInfo> &editorInfo);
};
#define extProgStore ExtProgStore::getInstance()

View File

@ -1507,13 +1507,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;

View File

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

View File

@ -412,6 +412,8 @@ void Options::setDefaults()
gimpDir = "";
psDir = "";
customEditorProg = "";
externalEditors.clear();
externalEditorIndex = -1;
CPBKeys = CPBKT_TID;
editorToSendTo = 1;
editor_out_dir = EDITOR_OUT_DIR_TEMP;
@ -821,6 +823,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");
@ -861,6 +864,156 @@ 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", "IconsSerialized")) {
// Multiple external editors.
const auto & names =
!keyFile.has_key("External Editor", "Names") ?
std::vector<Glib::ustring>() :
static_cast<std::vector<Glib::ustring>>(
keyFile.get_string_list("External Editor", "Names"));
const auto & commands =
!keyFile.has_key("External Editor", "Commands") ?
std::vector<Glib::ustring>() :
static_cast<std::vector<Glib::ustring>>(
keyFile.get_string_list("External Editor", "Commands"));
const auto & icons_serialized =
!keyFile.has_key("External Editor", "IconsSerialized") ?
std::vector<Glib::ustring>() :
static_cast<std::vector<Glib::ustring>>(
keyFile.get_string_list("External Editor", "IconsSerialized"));
externalEditors = std::vector<ExternalEditor>(std::max(std::max(
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 < icons_serialized.size(); i++) {
externalEditors[i].icon_serialized = icons_serialized[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<int>(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
auto getIconSerialized = [](const Glib::ustring &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")) {
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 + "\"", 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));
if (Glib::file_test(executable, Glib::FILE_TEST_IS_EXECUTABLE)) {
if (editorToSendTo == 1) {
externalEditorIndex = externalEditors.size();
}
externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", getIconSerialized(executable)));
break;
}
}
}
Glib::ustring psDir = "";
if (keyFile.has_key("External Editor", "PhotoshopDir")) {
psDir = keyFile.get_string("External Editor", "PhotoshopDir");
}
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 + "\"", getIconSerialized(executable)));
}
if (keyFile.has_key("External Editor", "CustomEditor")) {
executable = keyFile.get_string("External Editor", "CustomEditor");
if (!executable.empty()) {
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 (!executable.empty()) {
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 (!executable.empty()) {
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");
@ -2175,6 +2328,7 @@ 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);
@ -2184,6 +2338,24 @@ void Options::saveToFile(Glib::ustring fname)
keyFile.set_boolean("External Editor", "Float32", editor_float32);
keyFile.set_boolean("External Editor", "BypassOutputProfile", editor_bypass_output_profile);
{
std::vector<Glib::ustring> names;
std::vector<Glib::ustring> commands;
std::vector<Glib::ustring> icons_serialized;
for (const auto & editor : externalEditors) {
names.push_back(editor.name);
commands.push_back(editor.command);
icons_serialized.push_back(editor.icon_serialized);
}
keyFile.set_string_list("External Editor", "Names", names);
keyFile.set_string_list("External Editor", "Commands", commands);
keyFile.set_string_list("External Editor", "IconsSerialized", icons_serialized);
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);
@ -2853,3 +3025,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_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_serialized == other.icon_serialized;
}
bool ExternalEditor::operator!=(const ExternalEditor &other) const
{
return !(*this == other);
}

View File

@ -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_serialized);
Glib::ustring name;
Glib::ustring command;
Glib::ustring icon_serialized;
bool operator==(const ExternalEditor & other) const;
bool operator!=(const ExternalEditor & other) const;
};
struct SaveFormat {
SaveFormat(
const Glib::ustring& _format,
@ -276,6 +287,8 @@ public:
Glib::ustring gimpDir;
Glib::ustring psDir;
Glib::ustring customEditorProg;
std::vector<ExternalEditor> externalEditors;
int externalEditorIndex;
Glib::ustring CPBPath; // Custom Profile Builder's path
CPBKeyType CPBKeys; // Custom Profile Builder's key type
int editorToSendTo;

View File

@ -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::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::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);
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::manage(new RTImage(fileName));
}
bool success = insertEntryImpl(position, fileName, Glib::RefPtr<const Gio::Icon>(), image, label);
if (!success && image) {
delete image;
}
return success;
}
bool PopUpCommon::insertEntry(int position, const Glib::RefPtr<const Gio::Icon>& gIcon, const Glib::ustring& label)
{
RTImage* image = Gtk::manage(new RTImage(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<const Gio::Icon>& 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::manage(new MyImageMenuItem(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<MyImageMenuItem *>(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<const Gio::Icon>& gIcon)
{
if (!fileName.empty()) {
buttonImage->changeImage(fileName);
} else {
buttonImage->changeImage(gIcon, static_cast<Gtk::IconSize>(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;
@ -162,7 +251,7 @@ void PopUpCommon::setButtonHint()
auto item = dynamic_cast<MyImageMenuItem*>(widget);
if (item) {
hint += item->getLabel ()->get_text ();
hint += escapeHtmlChars(item->getLabel()->get_text());
}
}

View File

@ -20,12 +20,19 @@
*/
#pragma once
#include "glibmm/refptr.h"
#include <memory>
#include <vector>
#include <glibmm/ustring.h>
#include <sigc++/signal.h>
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<const Gio::Icon>& 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<Glib::RefPtr<const Gio::Icon>> imageIcons;
std::vector<Glib::ustring> imageFilenames;
std::vector<const RTImage*> images;
Glib::ustring buttonHint;
RTImage* buttonImage;
Gtk::Grid* imageContainer;
Gtk::Menu* menu;
std::unique_ptr<Gtk::Menu> menu;
Gtk::Button* button;
Gtk::Button* arrowButton;
int selected;
bool hasMenu;
void changeImage(int position);
void changeImage(const Glib::ustring& fileName, const Glib::RefPtr<const Gio::Icon>& gIcon);
void entrySelected(Gtk::Widget* menuItem);
bool insertEntryImpl(int position, const Glib::ustring& fileName, const Glib::RefPtr<const Gio::Icon>& gIcon, RTImage* image, const Glib::ustring& label);
void showMenu(GdkEventButton* event);
protected:

View File

@ -17,6 +17,7 @@
* along with RawTherapee. If not, see <https://www.gnu.org/licenses/>.
*/
#include <sigc++/slot.h>
#include "externaleditorpreferences.h"
#include "preferences.h"
#include "multilangmgr.h"
#include "splash.h"
@ -1202,68 +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);
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
externalEditors = Gtk::manage(new ExternalEditorPreferences());
externalEditors->set_size_request(-1, 200);
// 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));
Gtk::RadioButton::Group ge;
ge = editor_dir_temp->get_group();
editor_dir_current->set_group(ge);
editor_dir_custom->set_group(ge);
@ -1272,7 +1221,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);
@ -1283,7 +1232,7 @@ Gtk::Widget* Preferences::getGeneralPanel()
f->add(*vb);
hb = Gtk::manage(new Gtk::Box());
hb->pack_start(*externaleditorGrid);
hb->pack_start(*externalEditors);
hb->pack_start(*f, Gtk::PACK_EXPAND_WIDGET, 4);
vb = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
@ -1755,31 +1704,17 @@ void Preferences::storePreferences()
moptions.pseudoHiDPISupport = pseudoHiDPI->get_active();
#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;
const std::vector<ExternalEditorPreferences::EditorInfo> &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_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.
moptions.externalEditorIndex = i;
}
}
if (editor_dir_temp->get_active()) {
@ -2050,34 +1985,16 @@ void Preferences::fillPreferences()
hlThresh->set_value(moptions.highlightThreshold);
shThresh->set_value(moptions.shadowThreshold);
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());
std::vector<ExternalEditorPreferences::EditorInfo> editorInfos;
for (const auto &editor : moptions.externalEditors) {
editorInfos.push_back(ExternalEditorPreferences::EditorInfo(
editor.name, editor.command, editor.icon_serialized));
}
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());
if (moptions.externalEditorIndex >= 0) {
// Mark the current editor so we can track it.
editorInfos[moptions.externalEditorIndex].other_data = (void *)1;
}
#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);
externalEditors->setEditors(editorInfos);
editor_dir_temp->set_active(moptions.editor_out_dir == Options::EDITOR_OUT_DIR_TEMP);
editor_dir_current->set_active(moptions.editor_out_dir == Options::EDITOR_OUT_DIR_CURRENT);
@ -2554,6 +2471,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()

View File

@ -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;
Gtk::RadioButton *editor_dir_temp;
Gtk::RadioButton *editor_dir_current;

View File

@ -0,0 +1,77 @@
/*
* This file is part of RawTherapee.
*
* Copyright (c) 2021 Lawrence Lee <billee@ucdavis.edu>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#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<Gio::AppInfo> RTAppChooserDialog::get_app_info()
{
return Gtk::AppChooserDialog::get_app_info();
}
Glib::RefPtr<const Gio::AppInfo> 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<Gio::AppInfo> 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<const Gio::AppInfo> RTAppChooserDialog::get_app_info() const
{
GAppInfo *gAppInfo = gtk_app_chooser_get_app_info(GTK_APP_CHOOSER(
const_cast<GtkAppChooserDialog *>(gobj())));
return Glib::wrap(gAppInfo, true);
}
#endif

View File

@ -0,0 +1,39 @@
/*
* This file is part of RawTherapee.
*
* Copyright (c) 2021 Lawrence Lee <billee@ucdavis.edu>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <giomm/appinfo.h>
#include <glibmm/refptr.h>
#include <glibmm/ustring.h>
#include <gtkmm/appchooserdialog.h>
/**
* 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<Gio::AppInfo> get_app_info();
Glib::RefPtr<const Gio::AppInfo> get_app_info() const;
};

View File

@ -22,12 +22,40 @@
#include <cassert>
#include <iostream>
#include <gtkmm/icontheme.h>
#include <unordered_map>
#include "../rtengine/settings.h"
namespace
{
struct GIconKey {
Glib::RefPtr<const Gio::Icon> icon;
/**
* Icon size in pixels.
*/
int icon_size;
GIconKey() {}
GIconKey(const Glib::RefPtr<const Gio::Icon> &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<Gio::Icon>::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<int>()(key.icon_size);
}
};
std::unordered_map<GIconKey, Glib::RefPtr<Gdk::Pixbuf>, GIconKeyHash> gIconPixbufCache;
std::map<std::string, Glib::RefPtr<Gdk::Pixbuf> > pixbufCache;
std::map<std::string, Cairo::RefPtr<Cairo::ImageSurface> > 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<RTImage> &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<const Gio::Icon> &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<const Gio::Icon> &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<const Gio::Icon> &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<Gdk::Pixbuf> RTImage::createPixbufFromFile (const Glib::ustring& fileName)
@ -197,6 +291,17 @@ Glib::RefPtr<Gdk::Pixbuf> RTImage::createPixbufFromFile (const Glib::ustring& fi
return Gdk::Pixbuf::create(imgSurf, 0, 0, imgSurf->get_width(), imgSurf->get_height());
}
Glib::RefPtr<Gdk::Pixbuf> RTImage::createPixbufFromGIcon(const Glib::RefPtr<const Gio::Icon> &icon, int size)
{
// TODO: Listen for theme changes and update icon, remove from cache.
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<Gdk::Pixbuf>();
}
}
Cairo::RefPtr<Cairo::ImageSurface> RTImage::createImgSurfFromFile (const Glib::ustring& fileName)
{
Cairo::RefPtr<Cairo::ImageSurface> surf;

View File

@ -34,6 +34,11 @@ class RTImage final : public Gtk::Image, public RTScalable
protected:
Cairo::RefPtr<Cairo::ImageSurface> surface;
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
Glib::RefPtr<const Gio::Icon> gIcon;
int gIconSize;
void changeImage(const Glib::RefPtr<const Gio::Icon> &gIcon, int size);
int iconSizeToPixels(Gtk::IconSize size) const;
public:
RTImage ();
@ -42,9 +47,11 @@ public:
explicit RTImage (Cairo::RefPtr<Cairo::ImageSurface> &surf);
explicit RTImage(Cairo::RefPtr<Cairo::ImageSurface> other);
explicit RTImage (Glib::RefPtr<RTImage> &other);
explicit RTImage(const Glib::RefPtr<const Gio::Icon> &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<const Gio::Icon> &gIcon, Gtk::IconSize size);
void changeImage (const Glib::ustring& imageName);
Cairo::RefPtr<Cairo::ImageSurface> get_surface();
int get_width();
@ -58,6 +65,7 @@ public:
static void setScale (const int newScale);
static Glib::RefPtr<Gdk::Pixbuf> createPixbufFromFile (const Glib::ustring& fileName);
static Glib::RefPtr<Gdk::Pixbuf> createPixbufFromGIcon(const Glib::RefPtr<const Gio::Icon> &icon, int size);
static Cairo::RefPtr<Cairo::ImageSurface> createImgSurfFromFile (const Glib::ustring& fileName);
};

View File

@ -1054,6 +1054,13 @@ void RTWindow::MoveFileBrowserToEditor()
}
}
void RTWindow::updateExternalEditorWidget(int selectedIndex, const std::vector<ExternalEditor> & editors)
{
if (epanel) {
epanel->updateExternalEditorWidget(selectedIndex, editors);
}
}
void RTWindow::updateProfiles (const Glib::ustring &printerProfile, rtengine::RenderingIntent printerIntent, bool printerBPC)
{
if (epanel) {

View File

@ -33,6 +33,7 @@
class BatchQueueEntry;
class BatchQueuePanel;
class EditorPanel;
class ExternalEditor;
class FilePanel;
class PLDBridge;
class RTWindow final :
@ -118,6 +119,7 @@ public:
void MoveFileBrowserToEditor();
void MoveFileBrowserToMain();
void updateExternalEditorWidget(int selectedIndex, const std::vector<ExternalEditor> &editors);
void updateProfiles (const Glib::ustring &printerProfile, rtengine::RenderingIntent printerIntent, bool printerBPC);
void updateTPVScrollbar (bool hide);
void updateHistogramPosition (int oldPosition, int newPosition);