rawTherapee/rtgui/profilepanel.cc
Hombre 8b2eac9a3d Pipette and "On Preview Widgets" branch. See issue 227
The pipette part is already working quite nice but need to be finished. The widgets part needs more work...
2014-01-21 23:37:36 +01:00

674 lines
23 KiB
C++

/*
* This file is part of RawTherapee.
*
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "profilepanel.h"
#include "options.h"
#include "profilestore.h"
#include "clipboard.h"
#include "multilangmgr.h"
#include "../rtengine/safegtk.h"
#include "rtimage.h"
using namespace rtengine;
using namespace rtengine::procparams;
PartialPasteDlg* ProfilePanel::partialProfileDlg;
void ProfilePanel::init () {
partialProfileDlg = new PartialPasteDlg("Foo");
}
void ProfilePanel::cleanup () {
delete partialProfileDlg;
}
ProfilePanel::ProfilePanel (bool readOnly) : storedPProfile(NULL), lastFilename(""), imagePath("") {
tpc = NULL;
profileFillModeOnImage = new RTImage("profile-filled.png");
profileFillModeOffImage = new RTImage("profile-partial.png");
fillMode = Gtk::manage (new Gtk::ToggleButton());
fillMode->set_active(options.filledProfile);
fillMode->add( options.filledProfile ? *profileFillModeOnImage : *profileFillModeOffImage );
fillMode->signal_toggled().connect ( sigc::mem_fun(*this, &ProfilePanel::profileFillModeToggled) );
fillMode->set_tooltip_text(M("PROFILEPANEL_MODE_TIP"));
// Create the Combobox
profiles = Gtk::manage (new ProfileStoreComboBox ());
Gtk::HBox* hbox = Gtk::manage (new Gtk::HBox ());
hbox->show ();
// pack_start (*profiles, Gtk::PACK_SHRINK, 4);
pack_start (*hbox, Gtk::PACK_SHRINK, 4);
load = Gtk::manage (new Gtk::Button ());
load->add (*Gtk::manage (new RTImage ("gtk-open.png")));
if (!readOnly) save = Gtk::manage (new Gtk::Button ());
if (!readOnly) save->add (*Gtk::manage (new RTImage ("gtk-save-large.png")));
if (!readOnly) copy = Gtk::manage (new Gtk::Button ());
if (!readOnly) copy->add (*Gtk::manage (new RTImage ("edit-copy.png")));
paste = Gtk::manage (new Gtk::Button ());
paste->add (*Gtk::manage (new RTImage ("edit-paste.png")));
hbox->pack_start (*fillMode, Gtk::PACK_SHRINK, 1);
hbox->pack_start (*profiles);
hbox->pack_start (*load, Gtk::PACK_SHRINK, 1);
if (!readOnly) hbox->pack_start (*save, Gtk::PACK_SHRINK, 1);
hbox->pack_start (*copy, Gtk::PACK_SHRINK, 1);
if (!readOnly) hbox->pack_start (*paste, Gtk::PACK_SHRINK, 1);
load->signal_button_release_event().connect_notify( sigc::mem_fun(*this, &ProfilePanel::load_clicked) );
if (!readOnly) save->signal_button_release_event().connect_notify( sigc::mem_fun(*this, &ProfilePanel::save_clicked) );
if (!readOnly) copy->signal_button_release_event().connect_notify( sigc::mem_fun(*this, &ProfilePanel::copy_clicked) );
paste->signal_button_release_event().connect_notify( sigc::mem_fun(*this, &ProfilePanel::paste_clicked) );
custom = NULL;
lastsaved = NULL;
dontupdate = false;
profileStore.addListener(this);
changeconn = profiles->signal_changed().connect( sigc::mem_fun(*this, &ProfilePanel::selection_changed) );
load->set_tooltip_markup (M("PROFILEPANEL_TOOLTIPLOAD"));
if (!readOnly) save->set_tooltip_markup (M("PROFILEPANEL_TOOLTIPSAVE"));
if (!readOnly) copy->set_tooltip_markup (M("PROFILEPANEL_TOOLTIPCOPY"));
paste->set_tooltip_markup (M("PROFILEPANEL_TOOLTIPPASTE"));
show_all_children ();
}
ProfilePanel::~ProfilePanel () {
profileStore.removeListener(this);
if (custom) { custom->deleteInstance(); delete custom; }
if (lastsaved) { lastsaved->deleteInstance(); delete lastsaved; }
delete profileFillModeOnImage;
delete profileFillModeOffImage;
}
bool ProfilePanel::isCustomSelected() {
if (profiles->getCurrentLabel() == Glib::ustring ("(" + M("PROFILEPANEL_PCUSTOM") + ")"))
return true;
return false;
}
bool ProfilePanel::isLastSavedSelected() {
if (profiles->getCurrentLabel() == Glib::ustring ("(" + M("PROFILEPANEL_PLASTSAVED") + ")"))
return true;
return false;
}
Gtk::TreeIter ProfilePanel::getCustomRow() {
Gtk::TreeIter row;
if (custom)
row = profiles->getRowFromLabel(Glib::ustring ("(" + M("PROFILEPANEL_PCUSTOM") + ")"));
return row;
}
Gtk::TreeIter ProfilePanel::getLastSavedRow() {
Gtk::TreeIter row;
if (lastsaved) {
row = profiles->getRowFromLabel(Glib::ustring ("(" + M("PROFILEPANEL_PLASTSAVED") + ")"));
}
return row;
}
Gtk::TreeIter ProfilePanel::addCustomRow() {
const ProfileStoreEntry *customPSE = new ProfileStoreEntry(Glib::ustring ("(" + M("PROFILEPANEL_PCUSTOM") + ")"), PSET_FILE, 0, 0);
Gtk::TreeIter newEntry = profiles->addRow(customPSE);
return newEntry;
}
Gtk::TreeIter ProfilePanel::addLastSavedRow() {
const ProfileStoreEntry *lastSavedPSE = new ProfileStoreEntry(Glib::ustring ("(" + M("PROFILEPANEL_PLASTSAVED") + ")"), PSET_FILE, 0, 0);
Gtk::TreeIter newEntry = profiles->addRow(lastSavedPSE);
return newEntry;
}
void ProfilePanel::storeCurrentValue () {
// TODO: Find a way to get and restore the current selection; the following line can't work anymore
storedValue = profiles->getFullPathFromActiveRow();
if (!isCustomSelected() && !isLastSavedSelected()) {
// storing the current entry's procparams, if not "Custom" or "LastSaved"
// for now, the storedPProfile has default internal values
const ProfileStoreEntry *entry = profiles->getSelectedEntry();
const PartialProfile *currProfile;
if (entry && (currProfile = profileStore.getProfile(entry))!=NULL) {
// now storedPProfile has the current entry's values
storedPProfile = new PartialProfile(currProfile->pparams, currProfile->pedited, true);
}
else
storedPProfile = new PartialProfile(true);
}
}
/* Get the ProfileStore's entry list and recreate the combobox entries
* If you want want to update the ProfileStore list itself (rescan the dir tree), use its "parseProfiles" method instead
*/
void ProfilePanel::updateProfileList () {
bool ccPrevState = changeconn.block(true);
// rescan file tree
profiles->updateProfileList();
if (custom)
addCustomRow();
if (lastsaved)
addLastSavedRow();
changeconn.block (ccPrevState);
}
void ProfilePanel::restoreValue () {
bool ccPrevState = changeconn.block(true);
if (!profiles->setActiveRowFromFullPath(storedValue) && storedPProfile) {
if (custom) delete custom;
custom = new PartialProfile (storedPProfile->pparams, storedPProfile->pedited, true);
Gtk::TreeIter custRow = getCustomRow();
if (custRow)
profiles->set_active(custRow);
else
profiles->set_active (addCustomRow());
}
currRow = profiles->get_active();
changeconn.block (ccPrevState);
storedValue = "";
if (storedPProfile) {
storedPProfile->deleteInstance();
delete storedPProfile;
storedPProfile = NULL;
}
}
void ProfilePanel::save_clicked (GdkEventButton* event) {
if (event->button != 1)
return;
Gtk::FileChooserDialog dialog(M("PROFILEPANEL_SAVEDLGLABEL"), Gtk::FILE_CHOOSER_ACTION_SAVE);
FileChooserLastFolderPersister persister( &dialog, options.loadSaveProfilePath );
dialog.set_current_name (lastFilename);
//Add the user's default (or global if multiuser=false) profile path to the Shortcut list
#ifdef WIN32
// Dirty workaround, waiting for a clean solution by using exceptions!
if (!safe_is_shortcut_dir(options.getPreferredProfilePath()))
#endif
try {
dialog.add_shortcut_folder(options.getPreferredProfilePath());
}
catch (Gtk::FileChooserError &err) {}
//Add the image's path to the Shortcut list
#ifdef WIN32
// Dirty workaround, waiting for a clean solution by using exceptions!
if (!safe_is_shortcut_dir(imagePath))
#endif
try {
dialog.add_shortcut_folder(imagePath);
}
catch (Gtk::FileChooserError &err) {}
//Add response buttons the the dialog:
dialog.add_button(Gtk::StockID("gtk-cancel"), Gtk::RESPONSE_CANCEL);
dialog.add_button(Gtk::StockID("gtk-save"), Gtk::RESPONSE_OK);
//Add filters, so that only certain file types can be selected:
Gtk::FileFilter filter_pp;
filter_pp.set_name(M("PROFILEPANEL_FILEDLGFILTERPP"));
filter_pp.add_pattern("*"+paramFileExtension);
dialog.add_filter(filter_pp);
Gtk::FileFilter filter_any;
filter_any.set_name(M("PROFILEPANEL_FILEDLGFILTERANY"));
filter_any.add_pattern("*");
dialog.add_filter(filter_any);
// dialog.set_do_overwrite_confirmation (true);
bool done = false;
do {
if (dialog.run()==Gtk::RESPONSE_OK) {
std::string fname = dialog.get_filename();
Glib::ustring ext = getExtension (fname);
if (("." + ext) != paramFileExtension)
fname += paramFileExtension;
if (!confirmOverwrite (dialog, fname))
continue;
lastFilename = Glib::path_get_basename (fname);
const PartialProfile* toSave;
if (isCustomSelected())
toSave = custom;
else if (isLastSavedSelected())
toSave = lastsaved;
else {
const ProfileStoreEntry* entry = profiles->getSelectedEntry();
toSave = entry ? profileStore.getProfile (profiles->getSelectedEntry()) : NULL;
}
if (toSave) {
if (event->state & Gdk::CONTROL_MASK) {
// opening the partial paste dialog window
partialProfileDlg->set_title(M("PROFILEPANEL_SAVEPPASTE"));
int i = partialProfileDlg->run();
partialProfileDlg->hide();
if (i != Gtk::RESPONSE_OK)
return;
// saving the partial profile
PartialProfile ppTemp(true);
partialProfileDlg->applyPaste (ppTemp.pparams, ppTemp.pedited, toSave->pparams, toSave->pedited);
int retCode = ppTemp.pparams->save (fname, "", true, ppTemp.pedited);
ppTemp.deleteInstance();
if (retCode)
writeFailed(dialog, fname);
else {
done=true;
bool ccPrevState = changeconn.block(true);
profileStore.parseProfiles();
changeconn.block (ccPrevState);
}
}
else {
// saving a full profile
int retCode = toSave->pparams->save (fname);
if (retCode)
writeFailed(dialog, fname);
else {
done=true;
bool ccPrevState = changeconn.block(true);
profileStore.parseProfiles();
changeconn.block (ccPrevState);
}
}
}
else done = true;
}
else done = true;
} while (!done);
return;
}
/*
* Copy the actual full profile to the clipboard
*/
void ProfilePanel::copy_clicked (GdkEventButton* event) {
if (event->button != 1)
return;
const PartialProfile* toSave;
if (isCustomSelected())
toSave = custom;
else if (isLastSavedSelected())
toSave = lastsaved;
else {
const ProfileStoreEntry* entry = profiles->getSelectedEntry();
toSave = entry ? profileStore.getProfile (profiles->getSelectedEntry()) : NULL;
}
// toSave has to be a complete procparams
if (toSave) {
if (event->state & Gdk::CONTROL_MASK) {
// opening the partial paste dialog window
partialProfileDlg->set_title(M("PROFILEPANEL_COPYPPASTE"));
int i = partialProfileDlg->run();
partialProfileDlg->hide();
if (i != Gtk::RESPONSE_OK)
return;
// saving a partial profile
PartialProfile ppTemp(true);
partialProfileDlg->applyPaste (ppTemp.pparams, ppTemp.pedited, toSave->pparams, toSave->pedited);
clipboard.setPartialProfile(ppTemp);
ppTemp.deleteInstance();
}
else
clipboard.setProcParams (*toSave->pparams);
}
return;
}
/*
* Load a potentially partial profile
*/
void ProfilePanel::load_clicked (GdkEventButton* event) {
if (event->button != 1)
return;
Gtk::FileChooserDialog dialog(M("PROFILEPANEL_LOADDLGLABEL"), Gtk::FILE_CHOOSER_ACTION_OPEN);
FileChooserLastFolderPersister persister( &dialog, options.loadSaveProfilePath );
//Add the user's default (or global if multiuser=false) profile path to the Shortcut list
#ifdef WIN32
// Dirty workaround, waiting for a clean solution by using exceptions!
if (!safe_is_shortcut_dir(options.getPreferredProfilePath()))
#endif
try {
dialog.add_shortcut_folder(options.getPreferredProfilePath());
}
catch (Gtk::FileChooserError &err) {}
//Add the image's path to the Shortcut list
#ifdef WIN32
// Dirty workaround, waiting for a clean solution by using exceptions!
if (!safe_is_shortcut_dir(imagePath))
#endif
try {
dialog.add_shortcut_folder(imagePath);
}
catch (Gtk::FileChooserError &err) {}
//Add response buttons the the dialog:
dialog.add_button(Gtk::StockID("gtk-cancel"), Gtk::RESPONSE_CANCEL);
dialog.add_button(Gtk::StockID("gtk-open"), Gtk::RESPONSE_OK);
//Add filters, so that only certain file types can be selected:
Gtk::FileFilter filter_pp;
filter_pp.set_name(M("PROFILEPANEL_FILEDLGFILTERPP"));
filter_pp.add_pattern("*"+paramFileExtension);
dialog.add_filter(filter_pp);
Gtk::FileFilter filter_any;
filter_any.set_name(M("PROFILEPANEL_FILEDLGFILTERANY"));
filter_any.add_pattern("*");
dialog.add_filter(filter_any);
int result = dialog.run();
dialog.hide();
if (result==Gtk::RESPONSE_OK) {
Glib::ustring fname = dialog.get_filename();
if (event->state & Gdk::CONTROL_MASK) {
// opening the partial paste dialog window
partialProfileDlg->set_title(M("PROFILEPANEL_LOADPPASTE"));
int i = partialProfileDlg->run();
partialProfileDlg->hide();
if (i != Gtk::RESPONSE_OK)
return;
}
bool customCreated = false;
if (!custom) {
custom = new PartialProfile (true);
custom->set(true);
customCreated = true;
}
else if (fillMode->get_active())
custom->pparams->setDefaults();
int err = custom->load (fname);
if (!err) {
bool prevState = changeconn.block(true);
Gtk::TreeIter newEntry = addCustomRow();
profiles->set_active (newEntry);
currRow = profiles->get_active();
changeconn.block(prevState);
if (event->state & Gdk::CONTROL_MASK) {
// applying partial profile
PartialProfile ppTemp(true);
// the 2 next line modify custom->pedited without modifying custom->pparams
partialProfileDlg->applyPaste (ppTemp.pparams, ppTemp.pedited, custom->pparams, custom->pedited);
if (fillMode->get_active())
*custom->pparams = *ppTemp.pparams;
*custom->pedited = *ppTemp.pedited;
ppTemp.deleteInstance();
}
if (fillMode->get_active())
custom->pedited->set(true);
changeTo (custom, M("PROFILEPANEL_PFILE"));
}
else if (customCreated) {
// we delete custom
custom->deleteInstance();
delete custom; custom = NULL;
}
}
return;
}
/*
* Paste a full profile from the clipboard
*/
void ProfilePanel::paste_clicked (GdkEventButton* event) {
if (event->button != 1)
return;
if (!clipboard.hasProcParams())
return;
if (event->state & Gdk::CONTROL_MASK) {
partialProfileDlg->set_title(M("PROFILEPANEL_PASTEPPASTE"));
int i = partialProfileDlg->run();
partialProfileDlg->hide();
if (i != Gtk::RESPONSE_OK)
return;
}
bool prevState = changeconn.block(true);
if (!custom) {
custom = new PartialProfile (true);
custom->pedited->set(true);
profiles->set_active (addCustomRow());
currRow = profiles->get_active();
}
else {
profiles->set_active(getCustomRow());
currRow = profiles->get_active();
}
ProcParams pp = clipboard.getProcParams ();
*custom->pparams = pp;
changeconn.block(prevState);
if (event->state & Gdk::CONTROL_MASK) {
// applying partial profile
PartialProfile ppTemp(true);
// the 2 next line modify custom->pedited without modifying custom->pparams
partialProfileDlg->applyPaste (ppTemp.pparams, ppTemp.pedited, custom->pparams, custom->pedited);
*custom->pedited = *ppTemp.pedited;
ppTemp.deleteInstance();
}
changeTo (custom, M("HISTORY_FROMCLIPBOARD"));
return;
}
void ProfilePanel::changeTo (const PartialProfile* newpp, Glib::ustring profname) {
if (!newpp)
return;
if (tpc)
tpc->profileChange (newpp, EvProfileChanged, profname);
}
void ProfilePanel::selection_changed () {
if (isCustomSelected()) {
if (!dontupdate)
changeTo (custom, Glib::ustring ("(" + M("PROFILEPANEL_PCUSTOM") + ")"));
}
else if (isLastSavedSelected())
changeTo (lastsaved, Glib::ustring ("(" + M("PROFILEPANEL_PLASTSAVED") + ")"));
else {
const ProfileStoreEntry *pse = profiles->getSelectedEntry();
if (pse->type == PSET_FOLDER) {
// this entry is invalid, restoring the old value
bool ccPrevState = changeconn.block(true);
profiles->set_active(currRow);
changeconn.block(ccPrevState);
dontupdate = false;
return;
}
else
currRow = profiles->get_active();
const PartialProfile* s = profileStore.getProfile (pse);
if (s) {
if (fillMode->get_active() && s->pedited) {
ParamsEdited pe;
pe.set(true);
PartialProfile s2(s->pparams, &pe, false);
changeTo (&s2, pse->label+"+");
}
else
changeTo (s, pse->label);
}
}
dontupdate = false;
}
void ProfilePanel::procParamsChanged (rtengine::procparams::ProcParams* p, rtengine::ProcEvent ev, Glib::ustring descr, ParamsEdited* paramsEdited) {
// to prevent recursion, filter out the events caused by the profilepanel
if (ev==EvProfileChanged || ev==EvPhotoLoaded)
return;
if (!isCustomSelected()) {
dontupdate = true;
if (!custom) {
custom = new PartialProfile (true);
custom->set(true);
profiles->set_active (addCustomRow());
currRow = profiles->get_active();
}
else {
profiles->set_active(getCustomRow());
currRow = profiles->get_active();
}
}
*custom->pparams = *p;
}
/** @brief Initialize the Profile panel with a default profile, overridden by the last saved profile if provided
*
* The file tree has already been created on object's construction. We add here the Custom, LastSaved and/or Internal item.
*
* @param profileFullPath full path of the profile; must start by the virtual root (${G} or ${U}, and without suffix
* @param lastSaved pointer to the last saved ProcParam; may be NULL
*/
void ProfilePanel::initProfile (const Glib::ustring& profileFullPath, ProcParams* lastSaved) {
const ProfileStoreEntry *pse = NULL;
const PartialProfile *defprofile = NULL;
bool ccPrevState = changeconn.block(true);
if (custom) {
custom->deleteInstance();
delete custom; custom = NULL;
}
if (lastsaved) {
lastsaved->deleteInstance();
delete lastsaved; lastsaved = NULL;
}
if (lastSaved) {
ParamsEdited* pe = new ParamsEdited();
pe->set(true);
// copying the provided last saved profile to ProfilePanel::lastsaved
lastsaved = new PartialProfile(lastSaved, pe);
}
// update the content of the combobox; will add 'custom' and 'lastSaved' if necessary
updateProfileList();
Gtk::TreeIter lasSavedEntry;
// adding the Last Saved combobox entry, if needed
if (lastsaved) {
defprofile = lastsaved;
lasSavedEntry = getLastSavedRow();
}
if (!(pse = profileStore.findEntryFromFullPath(profileFullPath))) {
// entry not found, pse = the Internal ProfileStoreEntry
pse = profileStore.getInternalDefaultPSE();
}
defprofile = profileStore.getProfile (pse);
// selecting the "Internal" entry
profiles->setInternalEntry ();
currRow = profiles->get_active();
if (lastsaved) {
if (lasSavedEntry) profiles->set_active (lasSavedEntry);
currRow = profiles->get_active();
if (tpc) {
tpc->setDefaults (lastsaved->pparams);
tpc->profileChange (lastsaved, EvPhotoLoaded, profiles->getSelectedEntry()->label);
}
}
else {
if (pse) {
profiles->setActiveRowFromEntry(pse);
currRow = profiles->get_active();
}
if (tpc) {
tpc->setDefaults (defprofile->pparams);
tpc->profileChange (defprofile, EvPhotoLoaded, profiles->getSelectedEntry()->label);
}
}
changeconn.block (ccPrevState);
}
void ProfilePanel::setInitialFileName (const Glib::ustring& filename) {
lastFilename = Glib::path_get_basename(filename) + paramFileExtension;
imagePath = Glib::path_get_dirname(filename);
}
void ProfilePanel::profileFillModeToggled() {
if (fillMode->get_active()) {
// The button is pressed, we'll use the profileFillModeOnImage
fillMode->set_image(*profileFillModeOnImage);
}
else {
// The button is released, we'll use the profileFillModeOffImage
fillMode->set_image(*profileFillModeOffImage);
}
}
void ProfilePanel::writeOptions() {
options.filledProfile = fillMode->get_active();
}