1 /****************************************************************************
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtCore module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
42 #include "qsettings.h"
43 #include "qsettings_p.h"
44 #include "qdatetime.h"
46 #include "qvarlengtharray.h"
47 #include "private/qcore_mac_p.h"
51 static const CFStringRef hostNames
[2] = { kCFPreferencesCurrentHost
, kCFPreferencesAnyHost
};
52 static const int numHostNames
= 2;
55 On the Mac, it is more natural to use '.' as the key separator
56 than '/'. Therefore, it makes sense to replace '/' with '.' in
57 keys. Then we replace '.' with middle dots (which we can't show
58 here) and middle dots with '/'. A key like "4.0/BrowserCommand"
59 becomes "4<middot>0.BrowserCommand".
62 enum RotateShift
{ Macify
= 1, Qtify
= 2 };
64 static QString
rotateSlashesDotsAndMiddots(const QString
&key
, int shift
)
66 static const int NumKnights
= 3;
67 static const char knightsOfTheRoundTable
[NumKnights
] = { '/', '.', '\xb7' };
70 for (int i
= 0; i
< result
.size(); ++i
) {
71 for (int j
= 0; j
< NumKnights
; ++j
) {
72 if (result
.at(i
) == QLatin1Char(knightsOfTheRoundTable
[j
])) {
73 result
[i
] = QLatin1Char(knightsOfTheRoundTable
[(j
+ shift
) % NumKnights
]).unicode();
81 static QCFType
<CFStringRef
> macKey(const QString
&key
)
83 return QCFString::toCFStringRef(rotateSlashesDotsAndMiddots(key
, Macify
));
86 static QString
qtKey(CFStringRef cfkey
)
88 return rotateSlashesDotsAndMiddots(QCFString::toQString(cfkey
), Qtify
);
91 static QCFType
<CFPropertyListRef
> macValue(const QVariant
&value
);
93 static CFArrayRef
macList(const QList
<QVariant
> &list
)
96 QVarLengthArray
<QCFType
<CFPropertyListRef
> > cfvalues(n
);
97 for (int i
= 0; i
< n
; ++i
)
98 cfvalues
[i
] = macValue(list
.at(i
));
99 return CFArrayCreate(kCFAllocatorDefault
, reinterpret_cast<const void **>(cfvalues
.data()),
100 CFIndex(n
), &kCFTypeArrayCallBacks
);
103 static QCFType
<CFPropertyListRef
> macValue(const QVariant
&value
)
105 CFPropertyListRef result
= 0;
107 switch (value
.type()) {
108 case QVariant::ByteArray
:
110 QByteArray ba
= value
.toByteArray();
111 result
= CFDataCreate(kCFAllocatorDefault
, reinterpret_cast<const UInt8
*>(ba
.data()),
115 // should be same as below (look for LIST)
117 case QVariant::StringList
:
118 case QVariant::Polygon
:
119 result
= macList(value
.toList());
124 QMap<QString, QVariant> is potentially a multimap,
125 whereas CFDictionary is a single-valued map. To allow
126 for multiple values with the same key, we store
127 multiple values in a CFArray. To avoid ambiguities,
128 we also wrap lists in a CFArray singleton.
130 QMap
<QString
, QVariant
> map
= value
.toMap();
131 QMap
<QString
, QVariant
>::const_iterator i
= map
.constBegin();
133 int maxUniqueKeys
= map
.size();
134 int numUniqueKeys
= 0;
135 QVarLengthArray
<QCFType
<CFPropertyListRef
> > cfkeys(maxUniqueKeys
);
136 QVarLengthArray
<QCFType
<CFPropertyListRef
> > cfvalues(maxUniqueKeys
);
138 while (i
!= map
.constEnd()) {
139 const QString
&key
= i
.key();
140 QList
<QVariant
> values
;
145 } while (i
!= map
.constEnd() && i
.key() == key
);
147 bool singleton
= (values
.count() == 1);
149 switch (values
.first().type()) {
150 // should be same as above (look for LIST)
152 case QVariant::StringList
:
153 case QVariant::Polygon
:
160 cfkeys
[numUniqueKeys
] = QCFString::toCFStringRef(key
);
161 cfvalues
[numUniqueKeys
] = singleton
? macValue(values
.first()) : macList(values
);
165 result
= CFDictionaryCreate(kCFAllocatorDefault
,
166 reinterpret_cast<const void **>(cfkeys
.data()),
167 reinterpret_cast<const void **>(cfvalues
.data()),
168 CFIndex(numUniqueKeys
),
169 &kCFTypeDictionaryKeyCallBacks
,
170 &kCFTypeDictionaryValueCallBacks
);
173 case QVariant::DateTime
:
176 CFDate, unlike QDateTime, doesn't store timezone information.
178 QDateTime dt
= value
.toDateTime();
179 if (dt
.timeSpec() == Qt::LocalTime
) {
181 reference
.setTime_t((uint
)kCFAbsoluteTimeIntervalSince1970
);
182 result
= CFDateCreate(kCFAllocatorDefault
, CFAbsoluteTime(reference
.secsTo(dt
)));
189 result
= value
.toBool() ? kCFBooleanTrue
: kCFBooleanFalse
;
194 int n
= value
.toInt();
195 result
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberIntType
, &n
);
198 case QVariant::Double
:
200 double n
= value
.toDouble();
201 result
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberDoubleType
, &n
);
204 case QVariant::LongLong
:
205 case QVariant::ULongLong
:
207 qint64 n
= value
.toLongLong();
208 result
= CFNumberCreate(0, kCFNumberLongLongType
, &n
);
211 case QVariant::String
:
214 result
= QCFString::toCFStringRef(QSettingsPrivate::variantToString(value
));
219 static QVariant
qtValue(CFPropertyListRef cfvalue
)
224 CFTypeID typeId
= CFGetTypeID(cfvalue
);
227 Sorted grossly from most to least frequent type.
229 if (typeId
== CFStringGetTypeID()) {
230 return QSettingsPrivate::stringToVariant(QCFString::toQString(static_cast<CFStringRef
>(cfvalue
)));
231 } else if (typeId
== CFNumberGetTypeID()) {
232 CFNumberRef cfnumber
= static_cast<CFNumberRef
>(cfvalue
);
233 if (CFNumberIsFloatType(cfnumber
)) {
235 CFNumberGetValue(cfnumber
, kCFNumberDoubleType
, &d
);
241 if (CFNumberGetValue(cfnumber
, kCFNumberIntType
, &i
))
243 CFNumberGetValue(cfnumber
, kCFNumberLongLongType
, &ll
);
246 } else if (typeId
== CFArrayGetTypeID()) {
247 CFArrayRef cfarray
= static_cast<CFArrayRef
>(cfvalue
);
248 QList
<QVariant
> list
;
249 CFIndex size
= CFArrayGetCount(cfarray
);
250 bool metNonString
= false;
251 for (CFIndex i
= 0; i
< size
; ++i
) {
252 QVariant value
= qtValue(CFArrayGetValueAtIndex(cfarray
, i
));
253 if (value
.type() != QVariant::String
)
260 return QVariant(list
).toStringList();
261 } else if (typeId
== CFBooleanGetTypeID()) {
262 return (bool)CFBooleanGetValue(static_cast<CFBooleanRef
>(cfvalue
));
263 } else if (typeId
== CFDataGetTypeID()) {
264 CFDataRef cfdata
= static_cast<CFDataRef
>(cfvalue
);
265 return QByteArray(reinterpret_cast<const char *>(CFDataGetBytePtr(cfdata
)),
266 CFDataGetLength(cfdata
));
267 } else if (typeId
== CFDictionaryGetTypeID()) {
268 CFDictionaryRef cfdict
= static_cast<CFDictionaryRef
>(cfvalue
);
269 CFTypeID arrayTypeId
= CFArrayGetTypeID();
270 int size
= (int)CFDictionaryGetCount(cfdict
);
271 QVarLengthArray
<CFPropertyListRef
> keys(size
);
272 QVarLengthArray
<CFPropertyListRef
> values(size
);
273 CFDictionaryGetKeysAndValues(cfdict
, keys
.data(), values
.data());
275 QMultiMap
<QString
, QVariant
> map
;
276 for (int i
= 0; i
< size
; ++i
) {
277 QString key
= QCFString::toQString(static_cast<CFStringRef
>(keys
[i
]));
279 if (CFGetTypeID(values
[i
]) == arrayTypeId
) {
280 CFArrayRef cfarray
= static_cast<CFArrayRef
>(values
[i
]);
281 CFIndex arraySize
= CFArrayGetCount(cfarray
);
282 for (CFIndex j
= arraySize
- 1; j
>= 0; --j
)
283 map
.insert(key
, qtValue(CFArrayGetValueAtIndex(cfarray
, j
)));
285 map
.insert(key
, qtValue(values
[i
]));
289 } else if (typeId
== CFDateGetTypeID()) {
291 dt
.setTime_t((uint
)kCFAbsoluteTimeIntervalSince1970
);
292 return dt
.addSecs((int)CFDateGetAbsoluteTime(static_cast<CFDateRef
>(cfvalue
)));
297 static QString
comify(const QString
&organization
)
299 for (int i
= organization
.size() - 1; i
>= 0; --i
) {
300 QChar ch
= organization
.at(i
);
301 if (ch
== QLatin1Char('.') || ch
== QChar(0x3002) || ch
== QChar(0xff0e)
302 || ch
== QChar(0xff61)) {
303 QString suffix
= organization
.mid(i
+ 1).toLower();
304 if (suffix
.size() == 2 || suffix
== QLatin1String("com")
305 || suffix
== QLatin1String("org") || suffix
== QLatin1String("net")
306 || suffix
== QLatin1String("edu") || suffix
== QLatin1String("gov")
307 || suffix
== QLatin1String("mil") || suffix
== QLatin1String("biz")
308 || suffix
== QLatin1String("info") || suffix
== QLatin1String("name")
309 || suffix
== QLatin1String("pro") || suffix
== QLatin1String("aero")
310 || suffix
== QLatin1String("coop") || suffix
== QLatin1String("museum")) {
311 QString result
= organization
;
312 result
.replace(QLatin1Char('/'), QLatin1Char(' '));
317 int uc
= ch
.unicode();
318 if ((uc
< 'a' || uc
> 'z') && (uc
< 'A' || uc
> 'Z'))
323 for (int i
= 0; i
< organization
.size(); ++i
) {
324 QChar ch
= organization
.at(i
);
325 int uc
= ch
.unicode();
326 if ((uc
>= 'a' && uc
<= 'z') || (uc
>= '0' && uc
<= '9')) {
328 } else if (uc
>= 'A' && uc
<= 'Z') {
329 domain
+= ch
.toLower();
331 domain
+= QLatin1Char(' ');
334 domain
= domain
.simplified();
335 domain
.replace(QLatin1Char(' '), QLatin1Char('-'));
336 if (!domain
.isEmpty())
337 domain
.append(QLatin1String(".com"));
341 class QMacSettingsPrivate
: public QSettingsPrivate
344 QMacSettingsPrivate(QSettings::Scope scope
, const QString
&organization
,
345 const QString
&application
);
346 ~QMacSettingsPrivate();
348 void remove(const QString
&key
);
349 void set(const QString
&key
, const QVariant
&value
);
350 bool get(const QString
&key
, QVariant
*value
) const;
351 QStringList
children(const QString
&prefix
, ChildSpec spec
) const;
355 bool isWritable() const;
356 QString
fileName() const;
361 CFStringRef userName
;
362 CFStringRef applicationOrSuiteId
;
365 QCFString applicationId
;
368 SearchDomain domains
[6];
372 QMacSettingsPrivate::QMacSettingsPrivate(QSettings::Scope scope
, const QString
&organization
,
373 const QString
&application
)
374 : QSettingsPrivate(QSettings::NativeFormat
, scope
, organization
, application
)
376 QString javaPackageName
;
380 QString domainName
= comify(organization
);
381 if (domainName
.isEmpty()) {
382 setStatus(QSettings::AccessError
);
383 domainName
= QLatin1String("unknown-organization.trolltech.com");
386 while ((nextDot
= domainName
.indexOf(QLatin1Char('.'), curPos
)) != -1) {
387 javaPackageName
.prepend(domainName
.mid(curPos
, nextDot
- curPos
));
388 javaPackageName
.prepend(QLatin1Char('.'));
389 curPos
= nextDot
+ 1;
391 javaPackageName
.prepend(domainName
.mid(curPos
));
392 javaPackageName
= javaPackageName
.toLower();
394 javaPackageName
.prepend(QLatin1String("com."));
395 suiteId
= javaPackageName
;
397 if (scope
== QSettings::SystemScope
)
400 if (application
.isEmpty()) {
401 spec
|= F_Organization
;
403 javaPackageName
+= QLatin1Char('.');
404 javaPackageName
+= application
;
405 applicationId
= javaPackageName
;
409 for (int i
= (spec
& F_System
) ? 1 : 0; i
< 2; ++i
) {
410 for (int j
= (spec
& F_Organization
) ? 1 : 0; j
< 3; ++j
) {
411 SearchDomain
&domain
= domains
[numDomains
++];
412 domain
.userName
= (i
== 0) ? kCFPreferencesCurrentUser
: kCFPreferencesAnyUser
;
414 domain
.applicationOrSuiteId
= applicationId
;
416 domain
.applicationOrSuiteId
= suiteId
;
418 domain
.applicationOrSuiteId
= kCFPreferencesAnyApplication
;
422 hostName
= (scope
== QSettings::SystemScope
) ? kCFPreferencesCurrentHost
: kCFPreferencesAnyHost
;
426 QMacSettingsPrivate::~QMacSettingsPrivate()
430 void QMacSettingsPrivate::remove(const QString
&key
)
432 QStringList keys
= children(key
+ QLatin1Char('/'), AllKeys
);
434 // If i == -1, then delete "key" itself.
435 for (int i
= -1; i
< keys
.size(); ++i
) {
436 QString subKey
= key
;
438 subKey
+= QLatin1Char('/');
439 subKey
+= keys
.at(i
);
441 CFPreferencesSetValue(macKey(subKey
), 0, domains
[0].applicationOrSuiteId
,
442 domains
[0].userName
, hostName
);
446 void QMacSettingsPrivate::set(const QString
&key
, const QVariant
&value
)
448 CFPreferencesSetValue(macKey(key
), macValue(value
), domains
[0].applicationOrSuiteId
,
449 domains
[0].userName
, hostName
);
452 bool QMacSettingsPrivate::get(const QString
&key
, QVariant
*value
) const
454 QCFString k
= macKey(key
);
455 for (int i
= 0; i
< numDomains
; ++i
) {
456 for (int j
= 0; j
< numHostNames
; ++j
) {
457 QCFType
<CFPropertyListRef
> ret
=
458 CFPreferencesCopyValue(k
, domains
[i
].applicationOrSuiteId
, domains
[i
].userName
,
462 *value
= qtValue(ret
);
473 QStringList
QMacSettingsPrivate::children(const QString
&prefix
, ChildSpec spec
) const
475 QMap
<QString
, QString
> result
;
476 int startPos
= prefix
.size();
478 for (int i
= 0; i
< numDomains
; ++i
) {
479 for (int j
= 0; j
< numHostNames
; ++j
) {
480 QCFType
<CFArrayRef
> cfarray
= CFPreferencesCopyKeyList(domains
[i
].applicationOrSuiteId
,
484 CFIndex size
= CFArrayGetCount(cfarray
);
485 for (CFIndex k
= 0; k
< size
; ++k
) {
487 qtKey(static_cast<CFStringRef
>(CFArrayGetValueAtIndex(cfarray
, k
)));
488 if (currentKey
.startsWith(prefix
))
489 processChild(currentKey
.mid(startPos
), spec
, result
);
497 return result
.keys();
500 void QMacSettingsPrivate::clear()
502 QCFType
<CFArrayRef
> cfarray
= CFPreferencesCopyKeyList(domains
[0].applicationOrSuiteId
,
503 domains
[0].userName
, hostName
);
504 CFPreferencesSetMultiple(0, cfarray
, domains
[0].applicationOrSuiteId
, domains
[0].userName
,
508 void QMacSettingsPrivate::sync()
510 for (int i
= 0; i
< numDomains
; ++i
) {
511 for (int j
= 0; j
< numHostNames
; ++j
) {
512 Boolean ok
= CFPreferencesSynchronize(domains
[i
].applicationOrSuiteId
,
513 domains
[i
].userName
, hostNames
[j
]);
514 // only report failures for the primary file (the one we write to)
515 if (!ok
&& i
== 0 && hostNames
[j
] == hostName
&& status
== QSettings::NoError
) {
517 // work around what seems to be a bug in CFPreferences:
518 // don't report an error if there are no preferences for the application
519 QCFType
<CFArrayRef
> appIds
= CFPreferencesCopyApplicationList(domains
[i
].userName
,
522 // iterate through all the applications and see if we're there
523 CFIndex size
= CFArrayGetCount(appIds
);
524 for (CFIndex k
= 0; k
< size
; ++k
) {
525 const void *cfvalue
= CFArrayGetValueAtIndex(appIds
, k
);
526 if (CFGetTypeID(cfvalue
) == CFStringGetTypeID()) {
527 if (CFStringCompare(static_cast<CFStringRef
>(cfvalue
),
528 domains
[i
].applicationOrSuiteId
,
529 kCFCompareCaseInsensitive
) == kCFCompareEqualTo
) {
530 setStatus(QSettings::AccessError
);
536 setStatus(QSettings::AccessError
);
543 void QMacSettingsPrivate::flush()
548 bool QMacSettingsPrivate::isWritable() const
550 QMacSettingsPrivate
*that
= const_cast<QMacSettingsPrivate
*>(this);
551 QString
impossibleKey(QLatin1String("qt_internal/"));
553 QSettings::Status oldStatus
= that
->status
;
554 that
->status
= QSettings::NoError
;
556 that
->set(impossibleKey
, QVariant());
558 bool writable
= (status
== QSettings::NoError
) && that
->get(impossibleKey
, 0);
559 that
->remove(impossibleKey
);
562 that
->status
= oldStatus
;
566 QString
QMacSettingsPrivate::fileName() const
569 if ((spec
& F_System
) == 0)
570 result
= QDir::homePath();
571 result
+= QLatin1String("/Library/Preferences/");
572 result
+= QCFString::toQString(domains
[0].applicationOrSuiteId
);
573 result
+= QLatin1String(".plist");
577 QSettingsPrivate
*QSettingsPrivate::create(QSettings::Format format
,
578 QSettings::Scope scope
,
579 const QString
&organization
,
580 const QString
&application
)
582 if (format
== QSettings::NativeFormat
) {
583 return new QMacSettingsPrivate(scope
, organization
, application
);
585 return new QConfFileSettingsPrivate(format
, scope
, organization
, application
);
589 static QCFType
<CFURLRef
> urlFromFileName(const QString
&fileName
)
591 return CFURLCreateWithFileSystemPath(kCFAllocatorDefault
, QCFString(fileName
),
592 kCFURLPOSIXPathStyle
, false);
595 bool QConfFileSettingsPrivate::readPlistFile(const QString
&fileName
, ParsedSettingsMap
*map
) const
597 QCFType
<CFDataRef
> resource
;
599 if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault
, urlFromFileName(fileName
),
600 &resource
, 0, 0, &code
))
604 QCFType
<CFPropertyListRef
> propertyList
=
605 CFPropertyListCreateFromXMLData(kCFAllocatorDefault
, resource
, kCFPropertyListImmutable
,
610 if (CFGetTypeID(propertyList
) != CFDictionaryGetTypeID())
613 CFDictionaryRef cfdict
=
614 static_cast<CFDictionaryRef
>(static_cast<CFPropertyListRef
>(propertyList
));
615 int size
= (int)CFDictionaryGetCount(cfdict
);
616 QVarLengthArray
<CFPropertyListRef
> keys(size
);
617 QVarLengthArray
<CFPropertyListRef
> values(size
);
618 CFDictionaryGetKeysAndValues(cfdict
, keys
.data(), values
.data());
620 for (int i
= 0; i
< size
; ++i
) {
621 QString key
= qtKey(static_cast<CFStringRef
>(keys
[i
]));
622 map
->insert(QSettingsKey(key
, Qt::CaseSensitive
), qtValue(values
[i
]));
627 bool QConfFileSettingsPrivate::writePlistFile(const QString
&fileName
,
628 const ParsedSettingsMap
&map
) const
630 QVarLengthArray
<QCFType
<CFStringRef
> > cfkeys(map
.size());
631 QVarLengthArray
<QCFType
<CFPropertyListRef
> > cfvalues(map
.size());
633 ParsedSettingsMap::const_iterator j
;
634 for (j
= map
.constBegin(); j
!= map
.constEnd(); ++j
) {
635 cfkeys
[i
] = macKey(j
.key());
636 cfvalues
[i
] = macValue(j
.value());
640 QCFType
<CFDictionaryRef
> propertyList
=
641 CFDictionaryCreate(kCFAllocatorDefault
,
642 reinterpret_cast<const void **>(cfkeys
.data()),
643 reinterpret_cast<const void **>(cfvalues
.data()),
645 &kCFTypeDictionaryKeyCallBacks
,
646 &kCFTypeDictionaryValueCallBacks
);
648 QCFType
<CFDataRef
> xmlData
= CFPropertyListCreateXMLData(kCFAllocatorDefault
, propertyList
);
651 return CFURLWriteDataAndPropertiesToResource(urlFromFileName(fileName
), xmlData
, 0, &code
);