1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2009-08-11
7  * Description : a combo box containing ICC profiles
8  *
9  * Copyright (C) 2009-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10  *
11  * This program is free software; you can redistribute it
12  * and/or modify it under the terms of the GNU General
13  * Public License as published by the Free Software Foundation;
14  * either version 2, or (at your option)
15  * any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * ============================================================ */
23 
24 #include "iccprofilescombobox.h"
25 
26 // Qt includes
27 
28 #include <QFileInfo>
29 #include <QSet>
30 #include <QMenu>
31 #include <QIcon>
32 #include <QAction>
33 
34 // KDE includes
35 
36 #include <klocalizedstring.h>
37 
38 // Local includes
39 
40 #include "icctransform.h"
41 
42 namespace Digikam
43 {
44 
IccProfilesComboBox(QWidget * const parent)45 IccProfilesComboBox::IccProfilesComboBox(QWidget* const parent)
46     : SqueezedComboBox(parent)
47 {
48 }
49 
~IccProfilesComboBox()50 IccProfilesComboBox::~IccProfilesComboBox()
51 {
52 }
53 
iccProfileLessThan(IccProfile a,IccProfile b)54 bool iccProfileLessThan(IccProfile a, IccProfile b)
55 {
56     return (a.description() < b.description());
57 }
58 
59 // if needed outside this class, make it a public static method in a namespace
profileUserString(const IccProfile & p)60 static QString profileUserString(const IccProfile& p)
61 {
62     IccProfile profile(p);
63     QFileInfo info(profile.filePath());
64     QString fileName    = info.fileName();
65 
66     QString description = profile.description();
67 
68     if      (!description.isEmpty() && !fileName.isEmpty())
69     {
70         return i18nc("<Profile Description> (<File Name>)", "%1 (%2)", description, fileName);
71     }
72     else if (!fileName.isEmpty())
73     {
74         return fileName;
75     }
76     else
77     {
78         return QString();
79     }
80 }
81 
82 /**
83  * NOTE: if needed outside this class, make it a public static method in a namespace
84  */
formatProfiles(const QList<IccProfile> & givenProfiles,QList<IccProfile> * const returnedProfiles,QStringList * const userText)85 static void formatProfiles(const QList<IccProfile>& givenProfiles, QList<IccProfile>* const returnedProfiles, QStringList* const userText)
86 {
87     QList<IccProfile> profiles;
88     QSet<QString>     filePaths;
89 
90     foreach (IccProfile profile, givenProfiles) // krazy:exclude=foreach
91     {
92         QString filePath = profile.filePath();
93 
94         if (!profile.description().isNull() && (filePath.isNull() || !filePaths.contains(filePath)) )
95         {
96             profiles  << profile;
97             filePaths << filePath;
98         }
99     }
100 
101     std::sort(profiles.begin(), profiles.end(), iccProfileLessThan);
102 
103     foreach (IccProfile profile, profiles) // krazy:exclude=foreach
104     {
105         QString description = profileUserString(profile);
106 
107         if (description.isNull())
108         {
109             continue;
110         }
111 
112         *returnedProfiles << profile;
113         *userText         << description;
114     }
115 }
116 
addProfilesSqueezed(const QList<IccProfile> & givenProfiles)117 void IccProfilesComboBox::addProfilesSqueezed(const QList<IccProfile>& givenProfiles)
118 {
119     QList<IccProfile> profiles;
120     QStringList       userDescription;
121     formatProfiles(givenProfiles, &profiles, &userDescription);
122 
123     for (int i = 0 ; i < profiles.size() ; ++i)
124     {
125         addSqueezedItem(userDescription.at(i), QVariant::fromValue(profiles.at(i)));
126     }
127 }
128 
addProfileSqueezed(const IccProfile & profile,const QString & d)129 void IccProfilesComboBox::addProfileSqueezed(const IccProfile& profile, const QString& d)
130 {
131     QString description = d;
132 
133     if (description.isNull())
134     {
135         description = profileUserString(profile);
136     }
137 
138     addSqueezedItem(description, QVariant::fromValue(profile));
139 }
140 
replaceProfilesSqueezed(const QList<IccProfile> & profiles)141 void IccProfilesComboBox::replaceProfilesSqueezed(const QList<IccProfile>& profiles)
142 {
143     IccProfile current = currentProfile();
144     clear();
145     addProfilesSqueezed(profiles);
146     setCurrentProfile(current);
147 }
148 
setNoProfileIfEmpty(const QString & message)149 void IccProfilesComboBox::setNoProfileIfEmpty(const QString& message)
150 {
151     if (count() == 0)
152     {
153         setEnabled(false);
154         addSqueezedItem(message);
155         setCurrentIndex(0);
156     }
157 }
158 
currentProfile() const159 IccProfile IccProfilesComboBox::currentProfile() const
160 {
161     return itemData(currentIndex()).value<IccProfile>();
162 }
163 
setCurrentProfile(const IccProfile & profile)164 void IccProfilesComboBox::setCurrentProfile(const IccProfile& profile)
165 {
166     if (profile.isNull())
167     {
168         setCurrentIndex(-1);
169         return;
170     }
171 
172     const int size = count();
173 
174     for (int i = 0 ; i < size ; ++i)
175     {
176         if (itemData(i).value<IccProfile>() == profile)
177         {
178             setCurrentIndex(i);
179             return;
180         }
181     }
182 
183     setCurrentIndex(-1);
184 }
185 
186 // ------------------------------------------------------------------------------------------
187 
IccProfilesMenuAction(const QIcon & icon,const QString & text,QObject * const parent)188 IccProfilesMenuAction::IccProfilesMenuAction(const QIcon& icon, const QString& text, QObject* const parent)
189     : QMenu   (text),
190       m_parent(parent)
191 {
192     setIcon(icon);
193 }
194 
IccProfilesMenuAction(const QString & text,QObject * const parent)195 IccProfilesMenuAction::IccProfilesMenuAction(const QString& text, QObject* const parent)
196     : QMenu   (text),
197       m_parent(parent)
198 {
199 }
200 
replaceProfiles(const QList<IccProfile> & profiles)201 void IccProfilesMenuAction::replaceProfiles(const QList<IccProfile>& profiles)
202 {
203     clear();
204     addProfiles(profiles);
205 }
206 
addProfiles(const QList<IccProfile> & givenProfiles)207 void IccProfilesMenuAction::addProfiles(const QList<IccProfile>& givenProfiles)
208 {
209     QList<IccProfile> profiles;
210     QStringList userDescription;
211     formatProfiles(givenProfiles, &profiles, &userDescription);
212 
213     for (int i = 0 ; i < profiles.size() ; ++i)
214     {
215         addProfile(profiles.at(i), userDescription.at(i));
216     }
217 }
218 
addProfile(const IccProfile & profile,const QString & d)219 void IccProfilesMenuAction::addProfile(const IccProfile& profile, const QString& d)
220 {
221     QString description = d;
222 
223     if (description.isNull())
224     {
225         description = profileUserString(profile);
226     }
227 
228     QAction* const action = new QAction(d.left(50), m_parent);
229     action->setData(QVariant::fromValue(profile));
230     addAction(action);
231 
232     connect(action, &QAction::triggered,
233             this, [this, action]() { slotTriggered(action); });
234 }
235 
disableIfEmpty()236 void IccProfilesMenuAction::disableIfEmpty()
237 {
238     if (isEmpty())
239     {
240         setEnabled(false);
241     }
242 }
243 
slotTriggered(QObject * obj)244 void IccProfilesMenuAction::slotTriggered(QObject* obj)
245 {
246     QAction* const action = static_cast<QAction*>(obj);
247     IccProfile profile    = action->data().value<IccProfile>();
248 
249     if (!profile.isNull())
250     {
251         emit triggered(profile);
252     }
253 }
254 
parentObject() const255 QObject* IccProfilesMenuAction::parentObject() const
256 {
257     return m_parent;
258 }
259 
260 // ------------------------------------------------------------------------------------------
261 
IccRenderingIntentComboBox(QWidget * const parent)262 IccRenderingIntentComboBox::IccRenderingIntentComboBox(QWidget* const parent)
263     : QComboBox(parent)
264 {
265     addItem(i18n("Perceptual"),            IccTransform::Perceptual);
266     addItem(i18n("Relative Colorimetric"), IccTransform::RelativeColorimetric);
267     addItem(i18n("Absolute Colorimetric"), IccTransform::AbsoluteColorimetric);
268     addItem(i18n("Saturation"),            IccTransform::Saturation);
269     setWhatsThis(i18n("<ul><li><p><b>Perceptual intent</b> causes the full gamut of the image to be "
270                       "compressed or expanded to fill the gamut of the destination device, so that gray balance is "
271                       "preserved but colorimetric accuracy may not be preserved.</p>"
272                       "<p>In other words, if certain colors in an image fall outside of the range of colors that the output "
273                       "device can render, the image intent will cause all the colors in the image to be adjusted so that "
274                       "the every color in the image falls within the range that can be rendered and so that the relationship "
275                       "between colors is preserved as much as possible.</p>"
276                       "<p>This intent is most suitable for display of photographs and images, and is the default intent.</p></li>"
277                       "<li><p><b>Absolute Colorimetric intent</b> causes any colors that fall outside the range that the output device "
278                       "can render to be adjusted to the closest color that can be rendered, while all other colors are "
279                       "left unchanged.</p>"
280                       "<p>This intent preserves the white point and is most suitable for spot colors (Pantone, TruMatch, "
281                       "logo colors, ....)</p></li>"
282                       "<li><p><b>Relative Colorimetric intent</b> is defined such that any colors that fall outside the range that the "
283                       "output device can render are adjusted to the closest color that can be rendered, while all other colors "
284                       "are left unchanged. Proof intent does not preserve the white point.</p></li>"
285                       "<li><p><b>Saturation intent</b> preserves the saturation of colors in the image at the possible expense of "
286                       "hue and lightness.</p>"
287                       "<p>Implementation of this intent remains somewhat problematic, and the ICC is still working on methods to "
288                       "achieve the desired effects.</p>"
289                       "<p>This intent is most suitable for business graphics such as charts, where it is more important that the "
290                       "colors be vivid and contrast well with each other rather than a specific color.</p></li></ul>"));
291 }
292 
setIntent(int intent)293 void IccRenderingIntentComboBox::setIntent(int intent)
294 {
295     const int size = count();
296 
297     for (int i = 0 ; i < size ; ++i)
298     {
299         if (itemData(i).toInt() == intent)
300         {
301             setCurrentIndex(i);
302             return;
303         }
304     }
305 
306     setCurrentIndex(-1);
307 }
308 
intent() const309 int IccRenderingIntentComboBox::intent() const
310 {
311     return itemData(currentIndex()).toInt();
312 }
313 
314 } // namespace Digikam
315