tcp: Fix 64 bit build with debugging features enabled.
[haiku.git] / src / preferences / time / ZoneView.cpp
blobd62bbef6167bcff9e2dfeb2685436f5f09118d88
1 /*
2 * Copyright 2004-2013 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Mike Berg, mike@berg-net.us
7 * Adrien Destugues, pulkomandy@pulkomandy.ath.cx
8 * Julun, host.haiku@gmx.de
9 * Hamish Morrison, hamish@lavabit.com
10 * Philippe Saint-Pierre, stpere@gmail.com
11 * John Scipione, jscipione@gmail.com
12 * Oliver Tappe, zooey@hirschkaefer.de
16 #include <unicode/uversion.h>
17 #include "ZoneView.h"
19 #include <stdlib.h>
20 #include <syscalls.h>
22 #include <map>
23 #include <new>
24 #include <vector>
26 #include <AutoDeleter.h>
27 #include <Button.h>
28 #include <Catalog.h>
29 #include <Collator.h>
30 #include <ControlLook.h>
31 #include <Country.h>
32 #include <Directory.h>
33 #include <Entry.h>
34 #include <File.h>
35 #include <FindDirectory.h>
36 #include <ListItem.h>
37 #include <Locale.h>
38 #include <MutableLocaleRoster.h>
39 #include <OutlineListView.h>
40 #include <Path.h>
41 #include <RadioButton.h>
42 #include <ScrollView.h>
43 #include <StorageDefs.h>
44 #include <String.h>
45 #include <StringView.h>
46 #include <TimeZone.h>
47 #include <View.h>
48 #include <Window.h>
50 #include <unicode/datefmt.h>
51 #include <unicode/utmscale.h>
52 #include <ICUWrapper.h>
54 #include "TimeMessages.h"
55 #include "TimeZoneListItem.h"
56 #include "TimeZoneListView.h"
57 #include "TZDisplay.h"
60 #undef B_TRANSLATION_CONTEXT
61 #define B_TRANSLATION_CONTEXT "Time"
64 using BPrivate::MutableLocaleRoster;
65 using BPrivate::ObjectDeleter;
68 struct TimeZoneItemLess {
69 bool operator()(const BString& first, const BString& second)
71 // sort anything starting with '<' behind anything else
72 if (first.ByteAt(0) == '<') {
73 if (second.ByteAt(0) != '<')
74 return false;
75 } else if (second.ByteAt(0) == '<')
76 return true;
77 return fCollator.Compare(first.String(), second.String()) < 0;
79 private:
80 BCollator fCollator;
85 TimeZoneView::TimeZoneView(const char* name)
87 BGroupView(name, B_HORIZONTAL, B_USE_DEFAULT_SPACING),
88 fGmtTime(NULL),
89 fUseGmtTime(false),
90 fCurrentZoneItem(NULL),
91 fOldZoneItem(NULL),
92 fInitialized(false)
94 _ReadRTCSettings();
95 _InitView();
99 bool
100 TimeZoneView::CheckCanRevert()
102 // check GMT vs Local setting
103 bool enable = fUseGmtTime != fOldUseGmtTime;
105 return enable || fCurrentZoneItem != fOldZoneItem;
109 TimeZoneView::~TimeZoneView()
111 _WriteRTCSettings();
115 void
116 TimeZoneView::AttachedToWindow()
118 BView::AttachedToWindow();
119 if (Parent())
120 SetViewColor(Parent()->ViewColor());
122 if (!fInitialized) {
123 fInitialized = true;
125 fSetZone->SetTarget(this);
126 fZoneList->SetTarget(this);
131 void
132 TimeZoneView::DoLayout()
134 BView::DoLayout();
135 if (fCurrentZoneItem != NULL) {
136 fZoneList->Select(fZoneList->IndexOf(fCurrentZoneItem));
137 fCurrent->SetText(fCurrentZoneItem->Text());
138 fZoneList->ScrollToSelection();
143 void
144 TimeZoneView::MessageReceived(BMessage* message)
146 switch (message->what) {
147 case B_OBSERVER_NOTICE_CHANGE:
149 int32 change;
150 message->FindInt32(B_OBSERVE_WHAT_CHANGE, &change);
151 switch(change) {
152 case H_TM_CHANGED:
153 _UpdateDateTime(message);
154 break;
156 default:
157 BView::MessageReceived(message);
158 break;
160 break;
163 case H_CITY_CHANGED:
164 _UpdatePreview();
165 break;
167 case H_SET_TIME_ZONE:
169 _SetSystemTimeZone();
170 break;
173 case kMsgRevert:
174 _Revert();
175 break;
177 case kRTCUpdate:
178 fUseGmtTime = fGmtTime->Value() == B_CONTROL_ON;
179 _UpdateGmtSettings();
180 _UpdateCurrent();
181 _UpdatePreview();
182 break;
184 default:
185 BGroupView::MessageReceived(message);
186 break;
191 void
192 TimeZoneView::_UpdateDateTime(BMessage* message)
194 // only need to update once every minute
195 int32 minute;
196 if (message->FindInt32("minute", &minute) == B_OK) {
197 if (fLastUpdateMinute != minute) {
198 _UpdateCurrent();
199 _UpdatePreview();
201 fLastUpdateMinute = minute;
207 void
208 TimeZoneView::_InitView()
210 fZoneList = new TimeZoneListView();
211 fZoneList->SetSelectionMessage(new BMessage(H_CITY_CHANGED));
212 fZoneList->SetInvocationMessage(new BMessage(H_SET_TIME_ZONE));
213 _BuildZoneMenu();
214 BScrollView* scrollList = new BScrollView("scrollList", fZoneList,
215 B_FRAME_EVENTS | B_WILL_DRAW, false, true);
216 scrollList->SetExplicitMinSize(BSize(200, 0));
218 fCurrent = new TTZDisplay("currentTime", B_TRANSLATE("Current time:"));
219 fPreview = new TTZDisplay("previewTime", B_TRANSLATE("Preview time:"));
221 fSetZone = new BButton("setTimeZone", B_TRANSLATE("Set time zone"),
222 new BMessage(H_SET_TIME_ZONE));
223 fSetZone->SetEnabled(false);
224 fSetZone->SetExplicitAlignment(
225 BAlignment(B_ALIGN_RIGHT, B_ALIGN_BOTTOM));
227 fLocalTime = new BRadioButton("localTime",
228 B_TRANSLATE("Local time (Windows compatible)"),
229 new BMessage(kRTCUpdate));
230 fGmtTime = new BRadioButton("greenwichMeanTime",
231 B_TRANSLATE("GMT (UNIX compatible)"), new BMessage(kRTCUpdate));
233 if (fUseGmtTime)
234 fGmtTime->SetValue(B_CONTROL_ON);
235 else
236 fLocalTime->SetValue(B_CONTROL_ON);
237 _ShowOrHidePreview();
238 fOldUseGmtTime = fUseGmtTime;
240 const float kIndentSpacing
241 = be_control_look->DefaultItemSpacing() * 2;
242 BLayoutBuilder::Group<>(this)
243 .Add(scrollList)
244 .AddGroup(B_VERTICAL, 0)
245 .Add(new BStringView("clockSetTo",
246 B_TRANSLATE("Hardware clock set to:")))
247 .AddGroup(B_VERTICAL, 0)
248 .Add(fLocalTime)
249 .Add(fGmtTime)
250 .SetInsets(kIndentSpacing, 0, 0, 0)
251 .End()
252 .AddGlue()
253 .AddGroup(B_VERTICAL, B_USE_DEFAULT_SPACING)
254 .Add(fCurrent)
255 .Add(fPreview)
256 .End()
257 .Add(fSetZone)
258 .End()
259 .SetInsets(B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING,
260 B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING);
264 void
265 TimeZoneView::_BuildZoneMenu()
267 BTimeZone defaultTimeZone;
268 BLocaleRoster::Default()->GetDefaultTimeZone(&defaultTimeZone);
270 BLanguage language;
271 BLocale::Default()->GetLanguage(&language);
273 // Group timezones by regions, but filter out unwanted (duplicate) regions
274 // and add an additional region with generic GMT-offset timezones at the end
275 typedef std::map<BString, TimeZoneListItem*, TimeZoneItemLess> ZoneItemMap;
276 ZoneItemMap zoneItemMap;
277 const char* kOtherRegion = B_TRANSLATE_MARK("<Other>");
278 const char* kSupportedRegions[] = {
279 B_TRANSLATE_MARK("Africa"), B_TRANSLATE_MARK("America"),
280 B_TRANSLATE_MARK("Antarctica"), B_TRANSLATE_MARK("Arctic"),
281 B_TRANSLATE_MARK("Asia"), B_TRANSLATE_MARK("Atlantic"),
282 B_TRANSLATE_MARK("Australia"), B_TRANSLATE_MARK("Europe"),
283 B_TRANSLATE_MARK("Indian"), B_TRANSLATE_MARK("Pacific"),
284 kOtherRegion,
285 NULL
288 // Since the zone-map contains translated country-names (we get those from
289 // ICU), we need to use translated region names in the zone-map, too:
290 typedef std::map<BString, BString> TranslatedRegionMap;
291 TranslatedRegionMap regionMap;
292 for (const char** region = kSupportedRegions; *region != NULL; ++region) {
293 BString translatedRegion = B_TRANSLATE_NOCOLLECT(*region);
294 regionMap[*region] = translatedRegion;
296 TimeZoneListItem* regionItem
297 = new TimeZoneListItem(translatedRegion, NULL, NULL);
298 regionItem->SetOutlineLevel(0);
299 zoneItemMap[translatedRegion] = regionItem;
302 // Get all time zones
303 BMessage zoneList;
304 BLocaleRoster::Default()->GetAvailableTimeZonesWithRegionInfo(&zoneList);
306 typedef std::map<BString, std::vector<const char*> > ZonesByCountyMap;
307 ZonesByCountyMap zonesByCountryMap;
308 const char* zoneID;
309 BString timeZoneCode;
310 for (int tz = 0; zoneList.FindString("timeZone", tz, &zoneID) == B_OK
311 && zoneList.FindString("region", tz, &timeZoneCode) == B_OK; tz++) {
312 // From the global ("001") timezones, we only accept the generic GMT
313 // timezones, as all the other world-zones are duplicates of others.
314 if (timeZoneCode == "001" && strncmp(zoneID, "Etc/GMT", 7) != 0)
315 continue;
316 zonesByCountryMap[timeZoneCode].push_back(zoneID);
319 ZonesByCountyMap::const_iterator countryIter = zonesByCountryMap.begin();
320 for (; countryIter != zonesByCountryMap.end(); ++countryIter) {
321 const char* countryCode = countryIter->first.String();
322 if (countryCode == NULL)
323 continue;
325 size_t zoneCountInCountry = countryIter->second.size();
326 for (size_t tz = 0; tz < zoneCountInCountry; tz++) {
327 BString zoneID(countryIter->second[tz]);
328 BTimeZone* timeZone
329 = new(std::nothrow) BTimeZone(zoneID, &language);
330 if (timeZone == NULL)
331 continue;
333 int32 slashPos = zoneID.FindFirst('/');
334 BString region(zoneID, slashPos);
335 if (region == "Etc")
336 region = kOtherRegion;
338 // just accept timezones from our supported regions, others are
339 // aliases and would just make the list even longer
340 TranslatedRegionMap::iterator regionIter = regionMap.find(region);
341 if (regionIter == regionMap.end())
342 continue;
344 BString fullCountryID = regionIter->second;
345 BCountry* country = new(std::nothrow) BCountry(countryCode);
346 if (country == NULL)
347 continue;
349 BString countryName;
350 country->GetName(countryName);
351 bool hasUsedCountry = false;
352 bool countryIsRegion = countryName == regionIter->second
353 || region == kOtherRegion;
354 if (!countryIsRegion)
355 fullCountryID << "/" << countryName;
357 BString timeZoneName;
358 BString fullZoneID = fullCountryID;
359 if (zoneCountInCountry > 1) {
360 // we can't use the country name as timezone name, since there
361 // are more than one timezones in this country - fetch the
362 // localized name of the timezone and use that
363 timeZoneName = timeZone->Name();
364 int32 openParenthesisPos = timeZoneName.FindFirst('(');
365 if (openParenthesisPos >= 0) {
366 timeZoneName.Remove(0, openParenthesisPos + 1);
367 int32 closeParenthesisPos = timeZoneName.FindLast(')');
368 if (closeParenthesisPos >= 0)
369 timeZoneName.Truncate(closeParenthesisPos);
371 fullZoneID << "/" << timeZoneName;
372 } else {
373 timeZoneName = countryName;
374 fullZoneID << "/" << zoneID;
377 // skip duplicates
378 ZoneItemMap::iterator zoneIter = zoneItemMap.find(fullZoneID);
379 if (zoneIter != zoneItemMap.end()) {
380 delete timeZone;
381 continue;
384 TimeZoneListItem* countryItem = NULL;
385 TimeZoneListItem* zoneItem = NULL;
386 if (zoneCountInCountry > 1) {
387 ZoneItemMap::iterator countryIter
388 = zoneItemMap.find(fullCountryID);
389 if (countryIter == zoneItemMap.end()) {
390 countryItem = new TimeZoneListItem(countryName.String(),
391 country, NULL);
392 countryItem->SetOutlineLevel(1);
393 zoneItemMap[fullCountryID] = countryItem;
394 hasUsedCountry = true;
395 } else
396 countryItem = countryIter->second;
398 zoneItem = new TimeZoneListItem(timeZoneName.String(),
399 NULL, timeZone);
400 zoneItem->SetOutlineLevel(countryIsRegion ? 1 : 2);
401 } else {
402 zoneItem = new TimeZoneListItem(timeZoneName.String(),
403 country, timeZone);
404 zoneItem->SetOutlineLevel(1);
405 hasUsedCountry = true;
407 zoneItemMap[fullZoneID] = zoneItem;
409 if (timeZone->ID() == defaultTimeZone.ID()) {
410 fCurrentZoneItem = zoneItem;
411 if (countryItem != NULL)
412 countryItem->SetExpanded(true);
414 ZoneItemMap::iterator regionItemIter
415 = zoneItemMap.find(regionIter->second);
416 if (regionItemIter != zoneItemMap.end())
417 regionItemIter->second->SetExpanded(true);
420 if (!hasUsedCountry)
421 delete country;
425 fOldZoneItem = fCurrentZoneItem;
427 ZoneItemMap::iterator zoneIter;
428 bool lastWasCountryItem = false;
429 TimeZoneListItem* currentItem = NULL;
430 for (zoneIter = zoneItemMap.begin(); zoneIter != zoneItemMap.end();
431 ++zoneIter) {
432 if (zoneIter->second->OutlineLevel() == 2 && lastWasCountryItem) {
433 // Some countries (e.g. Spain and Chile) have their timezones
434 // spread across different regions. As a result, there might still
435 // be country items with only one timezone below them. We manually
436 // filter those country items here.
437 ZoneItemMap::iterator next = zoneIter;
438 ++next;
439 if (next != zoneItemMap.end()
440 && next->second->OutlineLevel() != 2) {
441 fZoneList->RemoveItem(currentItem);
442 zoneIter->second->SetText(currentItem->Text());
443 zoneIter->second->SetCountry(currentItem->HasCountry()
444 ? new(std::nothrow) BCountry(currentItem->Country())
445 : NULL);
446 zoneIter->second->SetTimeZone(currentItem->HasTimeZone()
447 ? new(std::nothrow) BTimeZone(currentItem->TimeZone())
448 : NULL);
449 zoneIter->second->SetOutlineLevel(1);
450 delete currentItem;
454 fZoneList->AddItem(zoneIter->second);
455 if (zoneIter->second->OutlineLevel() == 1) {
456 lastWasCountryItem = true;
457 currentItem = zoneIter->second;
458 } else
459 lastWasCountryItem = false;
464 void
465 TimeZoneView::_Revert()
467 fCurrentZoneItem = fOldZoneItem;
469 if (fCurrentZoneItem != NULL) {
470 int32 currentZoneIndex = fZoneList->IndexOf(fCurrentZoneItem);
471 fZoneList->Select(currentZoneIndex);
472 } else
473 fZoneList->DeselectAll();
474 fZoneList->ScrollToSelection();
476 fUseGmtTime = fOldUseGmtTime;
477 if (fUseGmtTime)
478 fGmtTime->SetValue(B_CONTROL_ON);
479 else
480 fLocalTime->SetValue(B_CONTROL_ON);
481 _ShowOrHidePreview();
483 _UpdateGmtSettings();
484 _SetSystemTimeZone();
485 _UpdatePreview();
486 _UpdateCurrent();
490 void
491 TimeZoneView::_UpdatePreview()
493 int32 selection = fZoneList->CurrentSelection();
494 TimeZoneListItem* item
495 = selection < 0
496 ? NULL
497 : static_cast<TimeZoneListItem*>(fZoneList->ItemAt(selection));
499 if (item == NULL || !item->HasTimeZone()) {
500 fPreview->SetText("");
501 fPreview->SetTime("");
502 return;
505 BString timeString = _FormatTime(item->TimeZone());
506 fPreview->SetText(item->Text());
507 fPreview->SetTime(timeString.String());
509 fSetZone->SetEnabled((strcmp(fCurrent->Text(), item->Text()) != 0));
513 void
514 TimeZoneView::_UpdateCurrent()
516 if (fCurrentZoneItem == NULL)
517 return;
519 BString timeString = _FormatTime(fCurrentZoneItem->TimeZone());
520 fCurrent->SetText(fCurrentZoneItem->Text());
521 fCurrent->SetTime(timeString.String());
525 void
526 TimeZoneView::_SetSystemTimeZone()
528 /* Set system timezone for all different API levels. How to do this?
529 * 1) tell locale-roster about new default timezone
530 * 2) tell kernel about new timezone offset
533 int32 selection = fZoneList->CurrentSelection();
534 if (selection < 0)
535 return;
537 TimeZoneListItem* item
538 = static_cast<TimeZoneListItem*>(fZoneList->ItemAt(selection));
539 if (item == NULL || !item->HasTimeZone())
540 return;
542 fCurrentZoneItem = item;
543 const BTimeZone& timeZone = item->TimeZone();
545 MutableLocaleRoster::Default()->SetDefaultTimeZone(timeZone);
547 _kern_set_timezone(timeZone.OffsetFromGMT(), timeZone.ID().String(),
548 timeZone.ID().Length());
550 fSetZone->SetEnabled(false);
551 fLastUpdateMinute = -1;
552 // just to trigger updating immediately
556 BString
557 TimeZoneView::_FormatTime(const BTimeZone& timeZone)
559 BString result;
561 time_t now = time(NULL);
562 bool rtcIsGMT;
563 _kern_get_real_time_clock_is_gmt(&rtcIsGMT);
564 if (!rtcIsGMT) {
565 int32 currentOffset
566 = fCurrentZoneItem != NULL && fCurrentZoneItem->HasTimeZone()
567 ? fCurrentZoneItem->OffsetFromGMT()
568 : 0;
569 now -= timeZone.OffsetFromGMT() - currentOffset;
571 fTimeFormat.Format(result, now, B_SHORT_TIME_FORMAT, &timeZone);
573 return result;
577 void
578 TimeZoneView::_ReadRTCSettings()
580 BPath path;
581 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
582 return;
584 path.Append("RTC_time_settings");
586 BEntry entry(path.Path());
587 if (entry.Exists()) {
588 BFile file(&entry, B_READ_ONLY);
589 if (file.InitCheck() == B_OK) {
590 char buffer[6];
591 file.Read(buffer, 6);
592 if (strncmp(buffer, "gmt", 3) == 0)
593 fUseGmtTime = true;
599 void
600 TimeZoneView::_WriteRTCSettings()
602 BPath path;
603 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK)
604 return;
606 path.Append("RTC_time_settings");
608 BFile file(path.Path(), B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
609 if (file.InitCheck() == B_OK) {
610 if (fUseGmtTime)
611 file.Write("gmt", 3);
612 else
613 file.Write("local", 5);
618 void
619 TimeZoneView::_UpdateGmtSettings()
621 _WriteRTCSettings();
623 _ShowOrHidePreview();
625 _kern_set_real_time_clock_is_gmt(fUseGmtTime);
629 void
630 TimeZoneView::_ShowOrHidePreview()
632 if (fUseGmtTime) {
633 // Hardware clock uses GMT time, changing timezone will adjust the
634 // offset and we need to display a preview
635 fCurrent->Show();
636 fPreview->Show();
637 } else {
638 // Hardware clock uses local time, changing timezone will adjust the
639 // clock and there is no offset to manage, thus, no preview.
640 fCurrent->Hide();
641 fPreview->Hide();