summaryrefslogtreecommitdiffstats
path: root/intl/locale/mac/OSPreferences_mac.cpp
blob: f8cd910b40367a853c80f237c73d208453009459 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "OSPreferences.h"
#include "mozilla/intl/LocaleService.h"
#include <Carbon/Carbon.h>

using namespace mozilla::intl;

static void LocaleChangedNotificationCallback(CFNotificationCenterRef center,
                                              void* observer, CFStringRef name,
                                              const void* object,
                                              CFDictionaryRef userInfo) {
  if (!::CFEqual(name, kCFLocaleCurrentLocaleDidChangeNotification)) {
    return;
  }
  static_cast<OSPreferences*>(observer)->Refresh();
}

OSPreferences::OSPreferences() {
  ::CFNotificationCenterAddObserver(
      ::CFNotificationCenterGetLocalCenter(), this,
      LocaleChangedNotificationCallback,
      kCFLocaleCurrentLocaleDidChangeNotification, 0,
      CFNotificationSuspensionBehaviorDeliverImmediately);
}

bool OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList) {
  MOZ_ASSERT(aLocaleList.IsEmpty());

  CFArrayRef langs = ::CFLocaleCopyPreferredLanguages();
  for (CFIndex i = 0; i < ::CFArrayGetCount(langs); i++) {
    CFStringRef lang = (CFStringRef)::CFArrayGetValueAtIndex(langs, i);

    AutoTArray<UniChar, 32> buffer;
    int size = ::CFStringGetLength(lang);
    buffer.SetLength(size);

    CFRange range = ::CFRangeMake(0, size);
    ::CFStringGetCharacters(lang, range, buffer.Elements());

    // Convert the locale string to the format that Mozilla expects
    NS_LossyConvertUTF16toASCII locale(
        reinterpret_cast<const char16_t*>(buffer.Elements()), buffer.Length());

    if (CanonicalizeLanguageTag(locale)) {
      aLocaleList.AppendElement(locale);
    }
  }

  ::CFRelease(langs);

  return !aLocaleList.IsEmpty();
}

bool OSPreferences::ReadRegionalPrefsLocales(nsTArray<nsCString>& aLocaleList) {
  // For now we're just taking System Locales since we don't know of any better
  // API for regional prefs.
  return ReadSystemLocales(aLocaleList);
}

static CFDateFormatterStyle ToCFDateFormatterStyle(
    OSPreferences::DateTimeFormatStyle aFormatStyle) {
  switch (aFormatStyle) {
    case OSPreferences::DateTimeFormatStyle::None:
      return kCFDateFormatterNoStyle;
    case OSPreferences::DateTimeFormatStyle::Short:
      return kCFDateFormatterShortStyle;
    case OSPreferences::DateTimeFormatStyle::Medium:
      return kCFDateFormatterMediumStyle;
    case OSPreferences::DateTimeFormatStyle::Long:
      return kCFDateFormatterLongStyle;
    case OSPreferences::DateTimeFormatStyle::Full:
      return kCFDateFormatterFullStyle;
    case OSPreferences::DateTimeFormatStyle::Invalid:
      MOZ_ASSERT_UNREACHABLE("invalid time format");
      return kCFDateFormatterNoStyle;
  }
}

// Given an 8-bit Gecko string, create a corresponding CFLocale;
// if aLocale is empty, returns a copy of the system's current locale.
// May return null on failure.
// Follows Core Foundation's Create rule, so the caller is responsible to
// release the returned reference.
static CFLocaleRef CreateCFLocaleFor(const nsACString& aLocale) {
  nsAutoCString reqLocale;
  nsAutoCString systemLocale;

  OSPreferences::GetInstance()->GetSystemLocale(systemLocale);

  if (aLocale.IsEmpty()) {
    LocaleService::GetInstance()->GetAppLocaleAsBCP47(reqLocale);
  } else {
    reqLocale.Assign(aLocale);
  }

  bool match = LocaleService::LanguagesMatch(reqLocale, systemLocale);
  if (match) {
    return ::CFLocaleCopyCurrent();
  }

  CFStringRef identifier = CFStringCreateWithBytesNoCopy(
      kCFAllocatorDefault, (const uint8_t*)reqLocale.BeginReading(),
      reqLocale.Length(), kCFStringEncodingASCII, false, kCFAllocatorNull);
  if (!identifier) {
    return nullptr;
  }
  CFLocaleRef locale = CFLocaleCreate(kCFAllocatorDefault, identifier);
  CFRelease(identifier);
  return locale;
}

/**
 * Cocoa API maps nicely to our four styles of date/time.
 *
 * The only caveat is that Cocoa takes regional preferences modifications
 * into account only when we pass an empty string as a locale.
 *
 * In all other cases it will return the default pattern for a given locale.
 */
bool OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
                                        DateTimeFormatStyle aTimeStyle,
                                        const nsACString& aLocale,
                                        nsACString& aRetVal) {
  CFLocaleRef locale = CreateCFLocaleFor(aLocale);
  if (!locale) {
    return false;
  }

  CFDateFormatterRef formatter = CFDateFormatterCreate(
      kCFAllocatorDefault, locale, ToCFDateFormatterStyle(aDateStyle),
      ToCFDateFormatterStyle(aTimeStyle));
  if (!formatter) {
    return false;
  }
  CFStringRef format = CFDateFormatterGetFormat(formatter);
  CFRelease(locale);

  CFRange range = CFRangeMake(0, CFStringGetLength(format));
  nsAutoString str;
  str.SetLength(range.length);
  CFStringGetCharacters(format, range,
                        reinterpret_cast<UniChar*>(str.BeginWriting()));
  CFRelease(formatter);

  aRetVal = NS_ConvertUTF16toUTF8(str);
  return true;
}

void OSPreferences::RemoveObservers() {
  ::CFNotificationCenterRemoveObserver(
      ::CFNotificationCenterGetLocalCenter(), this,
      kCTFontManagerRegisteredFontsChangedNotification, 0);
}