diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 8fd7608f9..9ad9a6cbd 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -14,7 +14,7 @@ env: publish_pre_dev_labels: '[]' jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: diff --git a/AUTHORS.txt b/AUTHORS.txt index 4fb664fa5..374a7935b 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -5,6 +5,7 @@ Project initiator: Development contributors, in last name alphabetical order: + Harald Aust Roel Baars Richard E Barber Martin Burri diff --git a/rtdata/languages/default b/rtdata/languages/default index d1f8f2480..d45fa1e2c 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1825,6 +1825,7 @@ PREFERENCES_CACHECLEAR_SAFETY;Only files in the cache are cleared. Processing pr PREFERENCES_CACHEMAXENTRIES;Maximum number of cache entries PREFERENCES_CACHEOPTS;Cache Options PREFERENCES_CACHETHUMBHEIGHT;Maximum thumbnail height +PREFERENCES_CAMERAPROFILESDIR;Camera profiles directory PREFERENCES_CHUNKSIZES;Tiles per thread PREFERENCES_CHUNKSIZE_RAW_AMAZE;AMaZE demosaic PREFERENCES_CHUNKSIZE_RAW_CA;Raw CA correction @@ -1883,8 +1884,9 @@ PREFERENCES_EXTEDITOR_FLOAT32;32-bit float TIFF output PREFERENCES_EXTERNALEDITOR;External Editor PREFERENCES_EXTERNALEDITOR_CHANGE;Change Application PREFERENCES_EXTERNALEDITOR_CHANGE_FILE;Change Executable -PREFERENCES_EXTERNALEDITOR_COLUMN_NAME;Name PREFERENCES_EXTERNALEDITOR_COLUMN_COMMAND;Command +PREFERENCES_EXTERNALEDITOR_COLUMN_NAME;Name +PREFERENCES_EXTERNALEDITOR_COLUMN_NATIVE_COMMAND;Native command PREFERENCES_FBROWSEROPTS;File Browser / Thumbnail Options PREFERENCES_FILEBROWSERTOOLBARSINGLEROW;Compact toolbars in File Browser PREFERENCES_FLATFIELDFOUND;Found @@ -1912,6 +1914,7 @@ PREFERENCES_INTENT_SATURATION;Saturation PREFERENCES_INTERNALTHUMBIFUNTOUCHED;Show embedded JPEG thumbnail if raw is unedited PREFERENCES_LANG;Language PREFERENCES_LANGAUTODETECT;Use system language +PREFERENCES_LENSPROFILESDIR;Lens profiles directory PREFERENCES_MAXRECENTFOLDERS;Maximum number of recent folders PREFERENCES_MENUGROUPEXTPROGS;Group 'Open with' PREFERENCES_MENUGROUPFILEOPERATIONS;Group 'File operations' diff --git a/rtengine/camconst.cc b/rtengine/camconst.cc index 64fc4d4ba..5cb56b2ae 100644 --- a/rtengine/camconst.cc +++ b/rtengine/camconst.cc @@ -224,7 +224,7 @@ CameraConst* CameraConst::parseEntry(const void *cJSON_, const char *make_model) const auto get_masked_areas = [](int w, int h, const cJSON *ji, CameraConst *cc) -> bool { - std::array, 2> rm; + std::array, 2> rm = {}; if (ji->type != cJSON_Array) { //fprintf(stderr, "\"masked_areas\" must be an array\n"); @@ -505,7 +505,15 @@ bool CameraConst::has_rawMask(int raw_width, int raw_height, int idx) const return false; } - return raw_mask.find(std::make_pair(raw_width, raw_height)) != raw_mask.end() || raw_mask.find(std::make_pair(0, 0)) != raw_mask.end(); + auto it = raw_mask.find(std::make_pair(raw_width, raw_height)); + if (it == raw_mask.end()) { + it = raw_mask.find(std::make_pair(0, 0)); + } + if (it != raw_mask.end()) { + return (it->second[idx][0] | it->second[idx][1] | it->second[idx][2] | it->second[idx][3]) != 0; + } else { + return false; + } } diff --git a/rtengine/dynamicprofile.cc b/rtengine/dynamicprofile.cc index 6dbd2d0f0..32be2ceb2 100644 --- a/rtengine/dynamicprofile.cc +++ b/rtengine/dynamicprofile.cc @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -220,6 +221,22 @@ bool DynamicProfileRules::loadRules() try { rule.profilepath = kf.get_string (group, "profilepath"); + #if defined (WIN32) + // if this is Windows, replace any "/" in the path with "\\" + size_t pos = rule.profilepath.find("/"); + while (pos != Glib::ustring::npos) { + rule.profilepath.replace(pos, 1, "\\"); + pos = rule.profilepath.find("/", pos); + } + #endif + #if !defined (WIN32) + // if this is not Windows, replace any "\\" in the path with "/" + size_t pos = rule.profilepath.find("\\"); + while (pos != Glib::ustring::npos) { + rule.profilepath.replace(pos, 1, "/"); + pos = rule.profilepath.find("\\", pos); + } + #endif } catch (Glib::KeyFileError &) { dynamicRules.pop_back(); } @@ -254,7 +271,14 @@ bool DynamicProfileRules::storeRules() kf.set_string (group, "profilepath", rule.profilepath); } - return kf.save_to_file (Glib::build_filename (Options::rtdir, "dynamicprofile.cfg")); + std::string fn = Glib::build_filename (Options::rtdir, "dynamicprofile.cfg"); + if (Glib::file_test(fn, Glib::FILE_TEST_IS_SYMLINK)) { + // file is symlink; use target instead + // symlinks apparently are not recognízed on Windows + return kf.save_to_file (g_file_read_link (fn.c_str(), NULL)); + } else { + return kf.save_to_file (fn); + } } const std::vector &DynamicProfileRules::getRules() diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 75f51633f..3f435b126 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -65,6 +65,40 @@ Glib::ustring expandRelativePath(const Glib::ustring &procparams_fname, const Gl return absPath; } +Glib::ustring expandRelativePath2(const Glib::ustring &procparams_fname, const Glib::ustring &procparams_fname2, const Glib::ustring &prefix, Glib::ustring embedded_fname) +{ + #if defined (WIN32) + // if this is Windows, replace any "/" in the filename with "\\" + size_t pos = embedded_fname.find("/"); + while (pos != string::npos) { + embedded_fname.replace(pos, 1, "\\"); + pos = embedded_fname.find("/", pos); + } + #endif + #if !defined (WIN32) + // if this is not Windows, replace any "\\" in the filename with "/" + size_t pos = embedded_fname.find("\\"); + while (pos != string::npos) { + embedded_fname.replace(pos, 1, "/"); + pos = embedded_fname.find("\\", pos); + } + #endif + + // if embedded_fname is not already an absolute path, + // try to convert it using procparams_fname (the directory of the raw file) as prefix + Glib::ustring rPath = expandRelativePath(procparams_fname, prefix, embedded_fname); + if (rPath.length() >= prefix.length() + && !Glib::file_test(rPath.substr(prefix.length()), Glib::FILE_TEST_IS_REGULAR) + && !procparams_fname2.empty() + && Glib::path_is_absolute(procparams_fname2)) { + // embedded_fname is not a valid path; + // try with procparams_fname2 (the path defined in Preferences) as a prefix + rPath = expandRelativePath(procparams_fname2 + G_DIR_SEPARATOR_S, prefix, embedded_fname); + } + return(rPath); +} + + Glib::ustring relativePathIfInside(const Glib::ustring &procparams_fname, bool fnameAbsolute, Glib::ustring embedded_fname) { if (fnameAbsolute || embedded_fname.empty() || !Glib::path_is_absolute(procparams_fname)) { @@ -93,6 +127,25 @@ Glib::ustring relativePathIfInside(const Glib::ustring &procparams_fname, bool f return prefix + embedded_fname.substr(dir1.length()); } +Glib::ustring relativePathIfInside2(const Glib::ustring &procparams_fname, const Glib::ustring &procparams_fname2, bool fnameAbsolute, Glib::ustring embedded_fname) +{ + // try to convert embedded_fname to a path relative to procparams_fname + // (the directory of the raw file) + // (note: fnameAbsolute seems to be always true, so this will never return a relative path) + Glib::ustring rPath = relativePathIfInside(procparams_fname, fnameAbsolute, embedded_fname); + if ((Glib::path_is_absolute(rPath) + || (rPath.length() >= 5 && rPath.substr(0, 5) == "file:" && Glib::path_is_absolute(rPath.substr(5)))) + && !procparams_fname2.empty() + && Glib::path_is_absolute(procparams_fname2)) { + // if path is not relative to the directory of the raw file, + // try to convert embedded_fname to a path relative to procparams_fname2 + // (the path defined in Preferences) + rPath = relativePathIfInside(procparams_fname2 + G_DIR_SEPARATOR_S, false, embedded_fname); + } + return(rPath); +} + + void getFromKeyfile( const Glib::KeyFile& keyfile, const Glib::ustring& group_name, @@ -6328,7 +6381,7 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo // Lens profile saveToKeyfile(!pedited || pedited->lensProf.lcMode, "LensProfile", "LcMode", lensProf.getMethodString(lensProf.lcMode), keyFile); - saveToKeyfile(!pedited || pedited->lensProf.lcpFile, "LensProfile", "LCPFile", relativePathIfInside(fname, fnameAbsolute, lensProf.lcpFile), keyFile); + saveToKeyfile(!pedited || pedited->lensProf.lcpFile, "LensProfile", "LCPFile", relativePathIfInside2(fname, options.rtSettings.lensProfilesPath, fnameAbsolute, lensProf.lcpFile), keyFile); saveToKeyfile(!pedited || pedited->lensProf.useDist, "LensProfile", "UseDistortion", lensProf.useDist, keyFile); saveToKeyfile(!pedited || pedited->lensProf.useVign, "LensProfile", "UseVignette", lensProf.useVign, keyFile); saveToKeyfile(!pedited || pedited->lensProf.useCA, "LensProfile", "UseCA", lensProf.useCA, keyFile); @@ -7149,7 +7202,7 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->prsharpening.deconviter, "PostResizeSharpening", "DeconvIterations", prsharpening.deconviter, keyFile); // Color management - saveToKeyfile(!pedited || pedited->icm.inputProfile, "Color Management", "InputProfile", relativePathIfInside(fname, fnameAbsolute, icm.inputProfile), keyFile); + saveToKeyfile(!pedited || pedited->icm.inputProfile, "Color Management", "InputProfile", relativePathIfInside2(fname, options.rtSettings.cameraProfilesPath, fnameAbsolute, icm.inputProfile), keyFile); saveToKeyfile(!pedited || pedited->icm.toneCurve, "Color Management", "ToneCurve", icm.toneCurve, keyFile); saveToKeyfile(!pedited || pedited->icm.applyLookTable, "Color Management", "ApplyLookTable", icm.applyLookTable, keyFile); saveToKeyfile(!pedited || pedited->icm.applyBaselineExposureOffset, "Color Management", "ApplyBaselineExposureOffset", icm.applyBaselineExposureOffset, keyFile); @@ -7522,9 +7575,9 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->colorToning.labregionsShowMask, "ColorToning", "LabRegionsShowMask", colorToning.labregionsShowMask, keyFile); // Raw - saveToKeyfile(!pedited || pedited->raw.darkFrame, "RAW", "DarkFrame", relativePathIfInside(fname, fnameAbsolute, raw.dark_frame), keyFile); + saveToKeyfile(!pedited || pedited->raw.darkFrame, "RAW", "DarkFrame", relativePathIfInside2(fname, options.rtSettings.darkFramesPath, fnameAbsolute, raw.dark_frame), keyFile); saveToKeyfile(!pedited || pedited->raw.df_autoselect, "RAW", "DarkFrameAuto", raw.df_autoselect, keyFile); - saveToKeyfile(!pedited || pedited->raw.ff_file, "RAW", "FlatFieldFile", relativePathIfInside(fname, fnameAbsolute, raw.ff_file), keyFile); + saveToKeyfile(!pedited || pedited->raw.ff_file, "RAW", "FlatFieldFile", relativePathIfInside2(fname, options.rtSettings.flatFieldsPath, fnameAbsolute, raw.ff_file), keyFile); saveToKeyfile(!pedited || pedited->raw.ff_AutoSelect, "RAW", "FlatFieldAutoSelect", raw.ff_AutoSelect, keyFile); saveToKeyfile(!pedited || pedited->raw.ff_FromMetaData, "RAW", "FlatFieldFromMetaData", raw.ff_FromMetaData, keyFile); saveToKeyfile(!pedited || pedited->raw.ff_BlurRadius, "RAW", "FlatFieldBlurRadius", raw.ff_BlurRadius, keyFile); @@ -8417,7 +8470,7 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) } if (keyFile.has_key("LensProfile", "LCPFile")) { - lensProf.lcpFile = expandRelativePath(fname, "", keyFile.get_string("LensProfile", "LCPFile")); + lensProf.lcpFile = expandRelativePath2(fname, options.rtSettings.lensProfilesPath, "", keyFile.get_string("LensProfile", "LCPFile")); if (pedited) { pedited->lensProf.lcpFile = true; @@ -9456,13 +9509,12 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) if (keyFile.has_group("Color Management")) { if (keyFile.has_key("Color Management", "InputProfile")) { - icm.inputProfile = expandRelativePath(fname, "file:", keyFile.get_string("Color Management", "InputProfile")); + icm.inputProfile = expandRelativePath2(fname, options.rtSettings.cameraProfilesPath, "file:", keyFile.get_string("Color Management", "InputProfile")); if (pedited) { pedited->icm.inputProfile = true; } - } - + } assignFromKeyfile(keyFile, "Color Management", "ToneCurve", pedited, icm.toneCurve, pedited->icm.toneCurve); assignFromKeyfile(keyFile, "Color Management", "ApplyLookTable", pedited, icm.applyLookTable, pedited->icm.applyLookTable); assignFromKeyfile(keyFile, "Color Management", "ApplyBaselineExposureOffset", pedited, icm.applyBaselineExposureOffset, pedited->icm.applyBaselineExposureOffset); @@ -10044,7 +10096,23 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) if (keyFile.has_group("Film Simulation")) { assignFromKeyfile(keyFile, "Film Simulation", "Enabled", pedited, filmSimulation.enabled, pedited->filmSimulation.enabled); - assignFromKeyfile(keyFile, "Film Simulation", "ClutFilename", pedited, filmSimulation.clutFilename, pedited->filmSimulation.clutFilename); + assignFromKeyfile(keyFile, "Film Simulation", "ClutFilename", pedited, filmSimulation.clutFilename, pedited->filmSimulation.clutFilename); + #if defined (WIN32) + // if this is Windows, replace any "/" in the filename with "\\" + size_t pos = filmSimulation.clutFilename.find("/"); + while (pos != string::npos) { + filmSimulation.clutFilename.replace(pos, 1, "\\"); + pos = filmSimulation.clutFilename.find("/", pos); + } + #endif + #if !defined (WIN32) + // if this is not Windows, replace any "\\" in the filename with "/" + size_t pos = filmSimulation.clutFilename.find("\\"); + while (pos != string::npos) { + filmSimulation.clutFilename.replace(pos, 1, "/"); + pos = filmSimulation.clutFilename.find("\\", pos); + } + #endif if (keyFile.has_key("Film Simulation", "Strength")) { if (ppVersion < 321) { @@ -10219,23 +10287,20 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) if (keyFile.has_group("RAW")) { if (keyFile.has_key("RAW", "DarkFrame")) { - raw.dark_frame = expandRelativePath(fname, "", keyFile.get_string("RAW", "DarkFrame")); + raw.dark_frame = expandRelativePath2(fname, options.rtSettings.darkFramesPath, "", keyFile.get_string("RAW", "DarkFrame")); if (pedited) { pedited->raw.darkFrame = true; } } - assignFromKeyfile(keyFile, "RAW", "DarkFrameAuto", pedited, raw.df_autoselect, pedited->raw.df_autoselect); - if (keyFile.has_key("RAW", "FlatFieldFile")) { - raw.ff_file = expandRelativePath(fname, "", keyFile.get_string("RAW", "FlatFieldFile")); + raw.ff_file = expandRelativePath2(fname, options.rtSettings.flatFieldsPath, "", keyFile.get_string("RAW", "FlatFieldFile")); if (pedited) { pedited->raw.ff_file = true; } } - assignFromKeyfile(keyFile, "RAW", "FlatFieldAutoSelect", pedited, raw.ff_AutoSelect, pedited->raw.ff_AutoSelect); assignFromKeyfile(keyFile, "RAW", "FlatFieldFromMetaData", pedited, raw.ff_FromMetaData, pedited->raw.ff_FromMetaData); assignFromKeyfile(keyFile, "RAW", "FlatFieldBlurRadius", pedited, raw.ff_BlurRadius, pedited->raw.ff_BlurRadius); diff --git a/rtengine/settings.h b/rtengine/settings.h index e831fa1a4..c920a355d 100644 --- a/rtengine/settings.h +++ b/rtengine/settings.h @@ -48,6 +48,8 @@ public: bool verbose; Glib::ustring darkFramesPath; ///< The default directory for dark frames Glib::ustring flatFieldsPath; ///< The default directory for flat fields + Glib::ustring cameraProfilesPath; ///< The default directory for camera profiles + Glib::ustring lensProfilesPath; ///< The default directory for lens profiles Glib::ustring adobe; // filename of AdobeRGB1998 profile (default to the bundled one) Glib::ustring prophoto; // filename of Prophoto profile (default to the bundled one) diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index ffe13fea3..420717b7e 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -2250,6 +2250,7 @@ void EditorPanel::sendToExternalPressed() } else { struct ExternalEditor editor = options.externalEditors.at(options.externalEditorIndex); external_editor_info = Gio::AppInfo::create_from_commandline(editor.command, editor.name, Gio::APP_INFO_CREATE_NONE); + external_editor_native_command = editor.native_command; sendToExternal(); } } @@ -2415,7 +2416,7 @@ bool EditorPanel::idle_sentToGimp (ProgressConnector *pc, rtengine::IImagef setUserOnlyPermission(Gio::File::create_for_path(filename), false); - success = ExtProgStore::openInExternalEditor(filename, external_editor_info); + success = ExtProgStore::openInExternalEditor(filename, external_editor_info, external_editor_native_command); if (!success) { Gtk::MessageDialog msgd (*parent, M ("MAIN_MSG_CANNOTSTARTEDITOR"), false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); @@ -2447,6 +2448,7 @@ void EditorPanel::onAppChooserDialogResponse(int responseId) case Gtk::RESPONSE_OK: getAppChooserDialog()->close(); external_editor_info = getAppChooserDialog()->get_app_info(); + external_editor_native_command = false; sendToExternal(); break; case Gtk::RESPONSE_CANCEL: diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index 98a475a7c..309f14e8c 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -253,6 +253,7 @@ private: Gtk::Button* navNext; Gtk::Button* navPrev; Glib::RefPtr external_editor_info; + bool external_editor_native_command; std::unique_ptr app_chooser_dialog; ExternalEditorChangedSignal *externalEditorChangedSignal; sigc::connection externalEditorChangedSignalConnection; diff --git a/rtgui/externaleditorpreferences.cc b/rtgui/externaleditorpreferences.cc index 79dac52d2..5d9b45c5d 100644 --- a/rtgui/externaleditorpreferences.cc +++ b/rtgui/externaleditorpreferences.cc @@ -37,6 +37,7 @@ ExternalEditorPreferences::ExternalEditorPreferences(): list_view = Gtk::manage(new Gtk::TreeView()); list_view->set_model(list_model); list_view->append_column(*Gtk::manage(makeAppColumn())); + list_view->append_column(*Gtk::manage(makeNativeCommandColumn())); list_view->append_column(*Gtk::manage(makeCommandColumn())); for (auto &&column : list_view->get_columns()) { @@ -90,19 +91,19 @@ ExternalEditorPreferences::ExternalEditorPreferences(): std::vector ExternalEditorPreferences::getEditors() const { - std::vector editors; + 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_serialized = icon == nullptr ? "" : icon->serialize().print(); - editors.push_back(ExternalEditorPreferences::EditorInfo( - rowIter->get_value(model_columns.name), - rowIter->get_value(model_columns.command), - icon_serialized, - rowIter->get_value(model_columns.other_data) - )); + editors.emplace_back( + rowIter->get_value(model_columns.name), + rowIter->get_value(model_columns.command), + icon_serialized, + rowIter->get_value(model_columns.native_command), + rowIter->get_value(model_columns.other_data)); } return editors; @@ -113,7 +114,7 @@ void ExternalEditorPreferences::setEditors( { list_model->clear(); - for (const ExternalEditorPreferences::EditorInfo & editor : editors) { + for (const EditorInfo & editor : editors) { auto row = *list_model->append(); Glib::RefPtr icon; @@ -138,6 +139,7 @@ void ExternalEditorPreferences::setEditors( row[model_columns.name] = editor.name; row[model_columns.icon] = icon; row[model_columns.command] = editor.command; + row[model_columns.native_command] = editor.native_command; row[model_columns.other_data] = editor.other_data; } } @@ -167,8 +169,8 @@ Gtk::TreeViewColumn *ExternalEditorPreferences::makeAppColumn() 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->add_attribute(icon_renderer->property_gicon(), model_columns.icon); + col->add_attribute(name_renderer->property_text(), model_columns.name); col->set_min_width(20); name_renderer->property_editable() = true; @@ -185,7 +187,7 @@ Gtk::TreeViewColumn *ExternalEditorPreferences::makeCommandColumn() col->set_title(M("PREFERENCES_EXTERNALEDITOR_COLUMN_COMMAND")); col->pack_start(*command_renderer); - col->add_attribute(*command_renderer, "text", model_columns.command); + col->add_attribute(command_renderer->property_text(), model_columns.command); command_renderer->property_editable() = true; command_renderer->signal_edited().connect( @@ -194,6 +196,24 @@ Gtk::TreeViewColumn *ExternalEditorPreferences::makeCommandColumn() return col; } +Gtk::TreeViewColumn *ExternalEditorPreferences::makeNativeCommandColumn() +{ + auto toggle_renderer = Gtk::manage(new Gtk::CellRendererToggle()); + auto col = Gtk::manage(new Gtk::TreeViewColumn()); + + col->set_title(M("PREFERENCES_EXTERNALEDITOR_COLUMN_NATIVE_COMMAND")); + col->pack_start(*toggle_renderer); + col->add_attribute(toggle_renderer->property_active(), model_columns.native_command); + + toggle_renderer->signal_toggled().connect([this](const Glib::ustring &path) { + const auto row_iter = list_model->get_iter(path); + bool new_value = !row_iter->get_value(model_columns.native_command); + row_iter->set_value(model_columns.native_command, new_value); + }); + + return col; +} + void ExternalEditorPreferences::onAppChooserDialogResponse( int response_id, RTAppChooserDialog *dialog) { @@ -224,6 +244,7 @@ void ExternalEditorPreferences::onFileChooserDialogResponse( for (const auto &selected : selection) { auto row = *list_model->get_iter(selected); row[model_columns.icon] = Glib::RefPtr(nullptr); + row[model_columns.native_command] = false; row[model_columns.command] = #ifdef WIN32 '"' + dialog->get_filename() + '"'; @@ -313,6 +334,7 @@ void ExternalEditorPreferences::setApp(const Glib::RefPtr app_info row[model_columns.icon] = app_info->get_icon(); row[model_columns.name] = app_info->get_name(); row[model_columns.command] = app_info->get_commandline(); + row[model_columns.native_command] = false; } } @@ -344,8 +366,16 @@ void ExternalEditorPreferences::updateToolbarSensitivity() } ExternalEditorPreferences::EditorInfo::EditorInfo( - Glib::ustring name, Glib::ustring command, Glib::ustring icon_serialized, void *other_data -) : name(name), icon_serialized(icon_serialized), command(command), other_data(other_data) + const Glib::ustring &name, + const Glib::ustring &command, + const Glib::ustring &icon_serialized, + bool native_command, + EditorTag other_data) : + name(name), + icon_serialized(icon_serialized), + command(command), + native_command(native_command), + other_data(other_data) { } @@ -354,5 +384,6 @@ ExternalEditorPreferences::ModelColumns::ModelColumns() add(name); add(icon); add(command); + add(native_command); add(other_data); } diff --git a/rtgui/externaleditorpreferences.h b/rtgui/externaleditorpreferences.h index 34658d942..a1e3c7e74 100644 --- a/rtgui/externaleditorpreferences.h +++ b/rtgui/externaleditorpreferences.h @@ -42,15 +42,22 @@ class FileChooserDialog; class ExternalEditorPreferences : public Gtk::Box { public: + struct EditorTag { + bool selected; + EditorTag(): selected(false) {} + explicit EditorTag(bool selected): selected(selected) {} + }; + /** * 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 + const Glib::ustring &name = Glib::ustring(), + const Glib::ustring &command = Glib::ustring(), + const Glib::ustring &icon_serialized = Glib::ustring(), + bool native_command = false, + EditorTag other_data = EditorTag() ); /** * Name of the external editor. @@ -65,11 +72,15 @@ public: * Gio::AppInfo::get_commandline() */ Glib::ustring command; + /** + * Use the OS native launcher instead of Gio. + */ + bool native_command; /** * Holds any other data associated with the editor. For example, it can * be used as a tag to uniquely identify the editor. */ - void *other_data; + EditorTag other_data; }; ExternalEditorPreferences(); @@ -96,7 +107,8 @@ private: Gtk::TreeModelColumn name; Gtk::TreeModelColumn> icon; Gtk::TreeModelColumn command; - Gtk::TreeModelColumn other_data; + Gtk::TreeModelColumn native_command; + Gtk::TreeModelColumn other_data; }; ModelColumns model_columns; @@ -124,6 +136,10 @@ private: * Constructs the column for displaying an editable commandline. */ Gtk::TreeViewColumn *makeCommandColumn(); + /** + * Constructs the column for displaying the native command toggle. + */ + Gtk::TreeViewColumn *makeNativeCommandColumn(); /** * Called when the user is done interacting with the app chooser dialog. * Closes the dialog and updates the selected entry if an app was chosen. diff --git a/rtgui/extprog.cc b/rtgui/extprog.cc index 9ec87c548..ea1800638 100644 --- a/rtgui/extprog.cc +++ b/rtgui/extprog.cc @@ -344,8 +344,20 @@ bool ExtProgStore::openInCustomEditor (const Glib::ustring& fileName, const Glib } -bool ExtProgStore::openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr &editorInfo) +bool ExtProgStore::openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr &editorInfo, bool nativeCommand) { + if (nativeCommand) { + if (rtengine::settings->verbose) { + std::cout << "Launching external editor as native command." << std::endl; + } + const Glib::ustring command = editorInfo->get_commandline(); + return openInCustomEditor(fileName, &command); + } + + if (rtengine::settings->verbose) { + std::cout << "Launching external editor with Gio." << std::endl; + } + bool success = false; try { diff --git a/rtgui/extprog.h b/rtgui/extprog.h index 6547896ef..5336c4703 100644 --- a/rtgui/extprog.h +++ b/rtgui/extprog.h @@ -70,7 +70,7 @@ public: static bool openInGimp (const Glib::ustring& fileName); static bool openInPhotoshop (const Glib::ustring& fileName); static bool openInCustomEditor (const Glib::ustring& fileName, const Glib::ustring* command = nullptr); - static bool openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr &editorInfo); + static bool openInExternalEditor(const Glib::ustring &fileName, const Glib::RefPtr &editorInfo, bool nativeCommand); }; #define extProgStore ExtProgStore::getInstance() diff --git a/rtgui/options.cc b/rtgui/options.cc index 7d823eda4..d6c3f7d25 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -570,6 +570,9 @@ void Options::setDefaults() rtSettings.darkFramesPath = ""; rtSettings.flatFieldsPath = ""; + rtSettings.cameraProfilesPath = ""; + rtSettings.lensProfilesPath = ""; + #ifdef WIN32 const gchar* sysRoot = g_getenv("SystemRoot"); // Returns e.g. "c:\Windows" @@ -651,13 +654,15 @@ void Options::setDefaults() rtSettings.leveldnliss = 0; rtSettings.leveldnautsimpl = 0; -// rtSettings.colortoningab =0.7; -//rtSettings.decaction =0.3; +// rtSettings.colortoningab =0.7; +// rtSettings.decaction =0.3; // rtSettings.ciebadpixgauss=false; rtSettings.rgbcurveslumamode_gamut = true; lastIccDir = rtSettings.iccDirectory; lastDarkframeDir = rtSettings.darkFramesPath; lastFlatfieldDir = rtSettings.flatFieldsPath; + lastCameraProfilesDir = rtSettings.cameraProfilesPath; + lastLensProfilesDir = rtSettings.lensProfilesPath; // rtSettings.bw_complementary = true; // There is no reasonable default for curves. We can still suppose that they will take place // in a subdirectory of the user's own ProcParams presets, i.e. in a subdirectory @@ -791,6 +796,14 @@ void Options::readFromFile(Glib::ustring fname) rtSettings.flatFieldsPath = keyFile.get_string("General", "FlatFieldsPath"); } + if (keyFile.has_key("General", "CameraProfilesPath")) { + rtSettings.cameraProfilesPath = keyFile.get_string("General", "CameraProfilesPath"); + } + + if (keyFile.has_key("General", "LensProfilesPath")) { + rtSettings.lensProfilesPath = keyFile.get_string("General", "LensProfilesPath"); + } + if (keyFile.has_key("General", "Verbose")) { rtSettings.verbose = keyFile.get_boolean("General", "Verbose"); } @@ -860,6 +873,7 @@ void Options::readFromFile(Glib::ustring fname) if (keyFile.has_group("External Editor")) { if (keyFile.has_key("External Editor", "Names") || keyFile.has_key("External Editor", "Commands") + || keyFile.has_key("External Editor", "NativeCommands") || keyFile.has_key("External Editor", "IconsSerialized")) { // Multiple external editors. @@ -873,6 +887,11 @@ void Options::readFromFile(Glib::ustring fname) std::vector() : static_cast>( keyFile.get_string_list("External Editor", "Commands")); + const auto & native_commands = + !keyFile.has_key("External Editor", "NativeCommands") ? + std::vector() : + static_cast>( + keyFile.get_boolean_list("External Editor", "NativeCommands")); const auto & icons_serialized = !keyFile.has_key("External Editor", "IconsSerialized") ? std::vector() : @@ -886,6 +905,9 @@ void Options::readFromFile(Glib::ustring fname) for (unsigned i = 0; i < commands.size(); i++) { externalEditors[i].command = commands[i]; } + for (unsigned i = 0; i < native_commands.size(); i++) { + externalEditors[i].native_command = native_commands[i]; + } for (unsigned i = 0; i < icons_serialized.size(); i++) { externalEditors[i].icon_serialized = icons_serialized[i]; } @@ -925,7 +947,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", getIconSerialized(executable))); + externalEditors.emplace_back("GIMP", executable, true, getIconSerialized(executable)); } else { for (auto ver = 12; ver >= 0; --ver) { executable = Glib::build_filename(gimpDir, "bin", Glib::ustring::compose(Glib::ustring("gimp-2.%1.exe"), ver)); @@ -933,7 +955,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "\"" + executable + "\"", getIconSerialized(executable))); + externalEditors.emplace_back("GIMP", executable, true, getIconSerialized(executable)); break; } } @@ -948,7 +970,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 2) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("Photoshop", "\"" + executable + "\"", getIconSerialized(executable))); + externalEditors.emplace_back("Photoshop", executable, true, getIconSerialized(executable)); } if (keyFile.has_key("External Editor", "CustomEditor")) { @@ -957,20 +979,20 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 3) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("-", "\"" + executable + "\"", "")); + externalEditors.emplace_back("-", executable, true, ""); } } #elif defined __APPLE__ if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "open -a GIMP", "gimp")); - externalEditors.push_back(ExternalEditor("GIMP-dev", "open -a GIMP-dev", "gimp")); + externalEditors.emplace_back("GIMP", "open -a GIMP", true, ""); + externalEditors.emplace_back("GIMP-dev", "open -a GIMP-dev", true, ""); if (editorToSendTo == 2) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("Photoshop", "open -a Photoshop", "")); + externalEditors.emplace_back("Photoshop", "open -a Photoshop", true, ""); if (keyFile.has_key("External Editor", "CustomEditor")) { auto executable = keyFile.get_string("External Editor", "CustomEditor"); @@ -978,20 +1000,21 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 3) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("-", executable, "")); + externalEditors.emplace_back("-", executable, true, ""); } } #else + const Glib::ustring gimp_icon_serialized = "('themed', <['gimp', 'gimp-symbolic']>)"; if (Glib::find_program_in_path("gimp").compare("")) { if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "gimp", "gimp")); + externalEditors.emplace_back("GIMP", "gimp", true, gimp_icon_serialized); } else if (Glib::find_program_in_path("gimp-remote").compare("")) { if (editorToSendTo == 1) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("GIMP", "gimp-remote", "gimp")); + externalEditors.emplace_back("GIMP", "gimp-remote", true, gimp_icon_serialized); } if (keyFile.has_key("External Editor", "CustomEditor")) { @@ -1000,7 +1023,7 @@ void Options::readFromFile(Glib::ustring fname) if (editorToSendTo == 3) { externalEditorIndex = externalEditors.size(); } - externalEditors.push_back(ExternalEditor("-", executable, "")); + externalEditors.emplace_back("-", executable, true, ""); } } #endif @@ -2188,6 +2211,8 @@ void Options::readFromFile(Glib::ustring fname) safeDirGet(keyFile, "Dialogs", "LastIccDir", lastIccDir); safeDirGet(keyFile, "Dialogs", "LastDarkframeDir", lastDarkframeDir); safeDirGet(keyFile, "Dialogs", "LastFlatfieldDir", lastFlatfieldDir); + safeDirGet(keyFile, "Dialogs", "LastCameraProfilesDir", lastCameraProfilesDir); + safeDirGet(keyFile, "Dialogs", "LastLensProfilesDir", lastLensProfilesDir); safeDirGet(keyFile, "Dialogs", "LastRgbCurvesDir", lastRgbCurvesDir); safeDirGet(keyFile, "Dialogs", "LastLabCurvesDir", lastLabCurvesDir); safeDirGet(keyFile, "Dialogs", "LastRetinexDir", lastRetinexDir); @@ -2291,6 +2316,8 @@ void Options::saveToFile(Glib::ustring fname) keyFile.set_string("General", "Version", RTVERSION); keyFile.set_string("General", "DarkFramesPath", rtSettings.darkFramesPath); keyFile.set_string("General", "FlatFieldsPath", rtSettings.flatFieldsPath); + keyFile.set_string("General", "CameraProfilesPath", rtSettings.cameraProfilesPath); + keyFile.set_string("General", "LensProfilesPath", rtSettings.lensProfilesPath); keyFile.set_boolean("General", "Verbose", rtSettings.verbose); keyFile.set_integer("General", "Cropsleep", rtSettings.cropsleep); keyFile.set_double("General", "Reduchigh", rtSettings.reduchigh); @@ -2311,16 +2338,19 @@ void Options::saveToFile(Glib::ustring fname) { std::vector names; std::vector commands; + std::vector native_commands; std::vector icons_serialized; for (const auto & editor : externalEditors) { names.push_back(editor.name); commands.push_back(editor.command); + native_commands.push_back(editor.native_command); icons_serialized.push_back(editor.icon_serialized); } keyFile.set_string_list("External Editor", "Names", names); keyFile.set_string_list("External Editor", "Commands", commands); + keyFile.set_boolean_list("External Editor", "NativeCommands", native_commands); keyFile.set_string_list("External Editor", "IconsSerialized", icons_serialized); keyFile.set_integer("External Editor", "EditorIndex", externalEditorIndex); @@ -2640,6 +2670,8 @@ void Options::saveToFile(Glib::ustring fname) keyFile.set_string("Dialogs", "LastIccDir", lastIccDir); keyFile.set_string("Dialogs", "LastDarkframeDir", lastDarkframeDir); keyFile.set_string("Dialogs", "LastFlatfieldDir", lastFlatfieldDir); + keyFile.set_string("Dialogs", "LastCameraProfilesDir", lastCameraProfilesDir); + keyFile.set_string("Dialogs", "LastLensProfilesDir", lastLensProfilesDir); keyFile.set_string("Dialogs", "LastRgbCurvesDir", lastRgbCurvesDir); keyFile.set_string("Dialogs", "LastLabCurvesDir", lastLabCurvesDir); keyFile.set_string("Dialogs", "LastRetinexDir", lastRetinexDir); @@ -2990,15 +3022,15 @@ Glib::ustring Options::getICCProfileCopyright() return Glib::ustring::compose("Copyright RawTherapee %1, CC0", now.get_year()); } -ExternalEditor::ExternalEditor() {} +ExternalEditor::ExternalEditor() = default; ExternalEditor::ExternalEditor( - const Glib::ustring &name, const Glib::ustring &command, const Glib::ustring &icon_serialized -): name(name), command(command), icon_serialized(icon_serialized) {} + const Glib::ustring &name, const Glib::ustring &command, bool native_command, const Glib::ustring &icon_serialized +): name(name), command(command), native_command(native_command), icon_serialized(icon_serialized) {} bool ExternalEditor::operator==(const ExternalEditor &other) const { - return this->name == other.name && this->command == other.command && this->icon_serialized == other.icon_serialized; + return this->name == other.name && this->command == other.command && this->native_command == other.native_command && this->icon_serialized == other.icon_serialized; } bool ExternalEditor::operator!=(const ExternalEditor &other) const diff --git a/rtgui/options.h b/rtgui/options.h index 5fb4e4f8b..78059357e 100644 --- a/rtgui/options.h +++ b/rtgui/options.h @@ -54,9 +54,10 @@ struct ExternalEditor { ExternalEditor(); - ExternalEditor(const Glib::ustring &name, const Glib::ustring &command, const Glib::ustring &icon_serialized); + ExternalEditor(const Glib::ustring &name, const Glib::ustring &command, bool native_command, const Glib::ustring &icon_serialized); Glib::ustring name; Glib::ustring command; + bool native_command; Glib::ustring icon_serialized; bool operator==(const ExternalEditor & other) const; @@ -450,6 +451,8 @@ public: Glib::ustring lastIccDir; Glib::ustring lastDarkframeDir; Glib::ustring lastFlatfieldDir; + Glib::ustring lastCameraProfilesDir; + Glib::ustring lastLensProfilesDir; Glib::ustring lastRgbCurvesDir; Glib::ustring lastLabCurvesDir; Glib::ustring lastRetinexDir; diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 87e0573f7..049fcd2e3 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -610,6 +610,7 @@ Gtk::Widget* Preferences::getImageProcessingPanel () Gtk::Grid* dirgrid = Gtk::manage(new Gtk::Grid()); setExpandAlignProperties(dirgrid, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); + // Dark Frames Dir Gtk::Label *dfLab = Gtk::manage(new Gtk::Label(M("PREFERENCES_DIRDARKFRAMES") + ":")); setExpandAlignProperties(dfLab, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); darkFrameDir = Gtk::manage(new MyFileChooserButton(M("PREFERENCES_DIRDARKFRAMES"), Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER)); @@ -623,7 +624,7 @@ Gtk::Widget* Preferences::getImageProcessingPanel () dfconn = darkFrameDir->signal_selection_changed().connect ( sigc::mem_fun (*this, &Preferences::darkFrameChanged)); - // FLATFIELD + // Flatfield Dir Gtk::Label *ffLab = Gtk::manage(new Gtk::Label(M("PREFERENCES_FLATFIELDSDIR") + ":")); setExpandAlignProperties(ffLab, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); flatFieldDir = Gtk::manage(new MyFileChooserButton(M("PREFERENCES_FLATFIELDSDIR"), Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER)); @@ -649,6 +650,25 @@ Gtk::Widget* Preferences::getImageProcessingPanel () dirgrid->attach_next_to(*clutsDir, *clutsDirLabel, Gtk::POS_RIGHT, 1, 1); dirgrid->attach_next_to(*clutsRestartNeeded, *clutsDir, Gtk::POS_RIGHT, 1, 1); + //Camera Profiles Dir + Gtk::Label *cameraProfilesDirLabel = Gtk::manage(new Gtk::Label(M("PREFERENCES_CAMERAPROFILESDIR") + ":")); + setExpandAlignProperties(cameraProfilesDirLabel, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); + cameraProfilesDir = Gtk::manage(new MyFileChooserButton(M("PREFERENCES_CAMERAPROFILESDIR"), Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER)); + setExpandAlignProperties(cameraProfilesDir, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); + + dirgrid->attach_next_to(*cameraProfilesDirLabel, *clutsDirLabel, Gtk::POS_BOTTOM, 1, 1); + dirgrid->attach_next_to(*cameraProfilesDir, *cameraProfilesDirLabel, Gtk::POS_RIGHT, 1, 1); + + //Lens Profiles Dir + Gtk::Label *lensProfilesDirLabel = Gtk::manage(new Gtk::Label(M("PREFERENCES_LENSPROFILESDIR") + ":")); + setExpandAlignProperties(lensProfilesDirLabel, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); + lensProfilesDir = Gtk::manage(new MyFileChooserButton(M("PREFERENCES_LENSPROFILESDIR"), Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER)); + setExpandAlignProperties(lensProfilesDir, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); + + dirgrid->attach_next_to(*lensProfilesDirLabel, *cameraProfilesDirLabel, Gtk::POS_BOTTOM, 1, 1); + dirgrid->attach_next_to(*lensProfilesDir, *lensProfilesDirLabel, Gtk::POS_RIGHT, 1, 1); + + //Pack directories to Image Processing panel cdf->add(*dirgrid); vbImageProcessing->pack_start (*cdf, Gtk::PACK_SHRINK, 4 ); @@ -1733,8 +1753,8 @@ void Preferences::storePreferences() moptions.externalEditorIndex = -1; for (unsigned i = 0; i < editors.size(); i++) { moptions.externalEditors[i] = (ExternalEditor( - editors[i].name, editors[i].command, editors[i].icon_serialized)); - if (editors[i].other_data) { + editors[i].name, editors[i].command, editors[i].native_command, editors[i].icon_serialized)); + if (editors[i].other_data.selected) { // 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; @@ -1848,8 +1868,9 @@ void Preferences::storePreferences() moptions.rtSettings.darkFramesPath = darkFrameDir->get_filename(); moptions.rtSettings.flatFieldsPath = flatFieldDir->get_filename(); - moptions.clutsDir = clutsDir->get_filename(); + moptions.rtSettings.cameraProfilesPath = cameraProfilesDir->get_filename(); + moptions.rtSettings.lensProfilesPath = lensProfilesDir->get_filename(); moptions.baBehav.resize(ADDSET_PARAM_NUM); @@ -2014,12 +2035,11 @@ void Preferences::fillPreferences() std::vector editorInfos; for (const auto &editor : moptions.externalEditors) { - editorInfos.push_back(ExternalEditorPreferences::EditorInfo( - editor.name, editor.command, editor.icon_serialized)); + editorInfos.emplace_back(editor.name, editor.command, editor.icon_serialized, editor.native_command); } if (moptions.externalEditorIndex >= 0) { // Mark the current editor so we can track it. - editorInfos[moptions.externalEditorIndex].other_data = (void *)1; + editorInfos[moptions.externalEditorIndex].other_data.selected = true; } externalEditors->setEditors(editorInfos); @@ -2106,6 +2126,10 @@ void Preferences::fillPreferences() flatFieldChanged(); clutsDir->set_current_folder(moptions.clutsDir); + + cameraProfilesDir->set_current_folder(moptions.rtSettings.cameraProfilesPath); + + lensProfilesDir->set_current_folder(moptions.rtSettings.lensProfilesPath); addc.block(true); setc.block(true); @@ -2512,7 +2536,10 @@ void Preferences::workflowUpdate() } if (changed) { // Update the send to external editor widget. - parent->updateExternalEditorWidget(moptions.externalEditorIndex, moptions.externalEditors); + int selected_index = moptions.externalEditorIndex >= 0 + ? moptions.externalEditorIndex + : static_cast(moptions.externalEditors.size()); + parent->updateExternalEditorWidget(selected_index, moptions.externalEditors); } if (moptions.cloneFavoriteTools != options.cloneFavoriteTools || diff --git a/rtgui/preferences.h b/rtgui/preferences.h index 039c4d58a..6e31761a4 100644 --- a/rtgui/preferences.h +++ b/rtgui/preferences.h @@ -115,6 +115,8 @@ class Preferences final : MyFileChooserButton* darkFrameDir; MyFileChooserButton* flatFieldDir; MyFileChooserButton* clutsDir; + MyFileChooserButton* cameraProfilesDir; + MyFileChooserButton* lensProfilesDir; Gtk::Label *dfLabel; Gtk::Label *ffLabel;